Formatting

Sec has one canonical source style. Formatting is deliberately closer to gofmt than to a configurable style framework: ordinary source formatting is deterministic, repeatable and shared by the compiler tooling, language server and future command-line formatter.

The formatter is allowed to normalize spelling and layout when the parser has already proved the intended construct. It is not allowed to guess program meaning, infer ownership transfers, rewrite behavior or use semantic analysis as an excuse to change valid source into a different program.

Core rule: formatting may change representation, but it must not change Sec semantics. Safe source repair is a separate operation and requires stronger proof.

Purpose

The formatter converts Sec source into the canonical source representation. It owns:

  • indentation and horizontal spacing;
  • line breaks and blank-line normalization;
  • brace and delimiter placement;
  • comment placement and local alignment;
  • struct-field, tag and declaration-table alignment;
  • canonical spelling of parser-proven noncanonical syntax;
  • stable formatting around recoverable syntax errors.

The formatter is not a type checker, optimizer, refactoring engine or ownership inference engine.

One Canonical Style

Sec does not make ordinary style choices project-configurable. Indentation width, brace placement, operator spacing, comma placement, comment spacing, alignment and canonical keyword spelling are language-tooling decisions.

fn Add(a: int, b: int) int {
    return a + b
}

Running the formatter repeatedly must converge immediately. Equivalent source should not oscillate between layouts depending on editor, operating system or invocation path.

Format(Format(source)) == Format(source)

Transformation Layers

Sec separates source transformation into distinct layers.

Canonical formatting

Canonical formatting changes whitespace, layout, alignment and parser-proven canonical spelling. It does not require successful Sema and must preserve comments, literals, identifiers and ownership operators.

Syntax normalization

Syntax normalization converts an accepted, unambiguous alternate spelling into the canonical Sec spelling. It may run during ordinary formatting only when the parser proves the construct.

Safe fixing

Safe fixing repairs invalid source only when the compiler has one unique, machine-applicable correction. It runs before formatting and is exposed separately through fix commands and editor actions.

Important: code cleanup and structural refactoring are not formatting. They may be useful, but they must remain separate user actions.

Shared Formatter

The CLI, LSP, generated-source pipeline and refactoring output must use the same formatter implementation. Formatting rules must not be duplicated in editor extensions or command-specific code.

internal/formatter/
internal/fixes/

The shared formatter is the source of truth for:

  • sec fmt;
  • sec fmt --check;
  • sec fmt --stdin;
  • LSP document, range and on-type formatting;
  • format-on-save;
  • generated Sec source;
  • refactoring output.

Command Model

The canonical CLI surface is:

sec fmt <path>
sec fmt --check <path>
sec fmt --stdin
sec fmt --fix <path>
sec fmt --fix --check <path>

sec fmt

Formats selected source in place. It applies canonical formatting and allowed syntax normalization, but no semantic fixes.

sec fmt --check

Does not modify files. It exits non-zero when selected source is not canonical and reports the affected files.

sec fmt --stdin

Reads Sec source from standard input and writes formatted source to standard output.

sec fmt --fix

Applies only proven safe fixes, then formats the result. It must report the fixes it applied and must not quietly perform discretionary cleanup or refactoring.

File Selection

A formatter path may identify a source file, directory or project root.

Directory and project formatting selects Sec source such as:

*.sec

Formatting should ignore build products, dependency caches, generated read-only files and project-excluded directories.

Configuration

Ordinary formatting style is not configurable.

Project settings may control operational behavior such as:

  • which files are selected;
  • whether generated files are included;
  • whether safe fixes run on save;
  • whether code cleanup runs on save.

They may not choose a different indent width, brace style, operator style or canonical keyword spelling. A Sec project should look like Sec regardless of who formats it.

Formatting Invariants

Canonical formatted source must provide:

  • deterministic output;
  • idempotence;
  • semantic preservation;
  • comment preservation;
  • literal preservation;
  • stable line endings;
  • exactly one final newline for non-empty source;
  • no trailing horizontal whitespace;
  • no emitted tabs.

For valid source, parsing before and after formatting must produce equivalent language semantics.

Line Endings

The formatter preserves a consistently established LF or CRLF style. Mixed line endings normalize to the dominant style; if there is no dominant style, LF is used. sec fmt --stdin uses LF unless a later explicit mode says otherwise.

Indentation

Indentation uses four spaces. Tabs are never emitted for indentation or alignment.

fn main() int {
    if ready {
        return 0
    }

    return 1
}

Multiline blocks, parentheses, brackets, argument lists, parameter lists, literals and declaration groups increase indentation structurally. Delimiters inside strings and comments do not affect indentation.

Blank Lines

Repeated empty lines collapse to one blank line.

Blank lines commonly separate:

  • target directives from the module declaration;
  • module declarations from imports;
  • import groups from declarations;
  • top-level declarations and functions;
  • logical sections inside function bodies when the author already separated them.

No blank line is emitted immediately inside braces, between documentation and its declaration, or between } and a required continuation such as else.

Braces

Opening braces stay on the same line as their construct.

if ready {
    Run()
} else {
    Stop()
}

This applies consistently to functions, types, impl blocks, properties, control-flow blocks, unsafe blocks, asm blocks, match/select constructs and literals with block syntax.

Spacing

Binary and assignment operators use one space on both sides:

a + b
value := 10
value = other
value += 1
left x right

Unary operators have no following space:

!enabled
-value

Member access and calls remain compact:

value.field
Vehicle.FuelType.diesel
Add(1, 2)

There is no space before commas, colons or closing delimiters, and one ordinary space after commas and colons unless an alignment rule owns the group.

Ownership Syntax

Formatting must preserve the semantic distinction between copy/direct initialization and explicit move syntax.

let value := source
let destination :<- source
let destination: Buffer <- source

destination = source
destination <- source

Ordinary formatting never infers ownership transfer. It must not turn := into :<-, or = into <-, based on type information.

Direct initialization from a newly created temporary remains direct initialization; the formatter does not add a move marker merely because the resulting value is move-only.

Syntax Normalization

Parser-proven accepted aliases may be converted into canonical spelling during ordinary formatting.

Function keyword

// accepted input
func Run() void {
}

// canonical output
fn Run() void {
}

An identifier or member named func is not rewritten when it is not the function keyword.

Increment and decrement

// accepted statement aliases
count++
count--

// canonical output
count += 1
count -= 1

These aliases are statement-only. Formatting must not turn an invalid increment expression into a different valid expression.

Safe Fixes

A safe fix may repair invalid source only when the compiler proves one unambiguous correction. Examples include inserting a missing colon or comma, correcting declaration assignment syntax, or changing invalid copy syntax to a legal explicit move when ownership analysis proves that the move has no unintended consequences.

// invalid
fn Parse(value string) Token {
}

// safe fix
fn Parse(value: string) Token {
}

If a proposed move would invalidate a later use, change overload resolution or create a borrow conflict, it is not an automatic safe fix. It becomes an explicit refactoring with consequences.

Comments

Sec supports line comments, block comments and documentation comments.

// line comment
/* block comment */
/** documentation comment */

Formatting preserves comment text and relative attachment. It may adjust indentation, surrounding whitespace and alignment, but it does not reflow ordinary prose by default.

A trailing line comment has at least two spaces before //:

let retryCount := 3  // Maximum retry attempts.

Documentation Comments

Canonical documentation comments use block documentation syntax:

/**
 * Returns true when the value is positive.
 *
 * @param value value to inspect
 * @return true when value is greater than zero
 */
fn IsPositive(value: int) bool {
    return value > 0
}

No blank line separates documentation from its declaration. The formatter preserves the text and tag order; the documentation rulebook owns which tags are valid.

Alignment

Local declaration-like groups may align into readable columns. Alignment uses spaces only and stops at clear group boundaries such as blank lines, standalone comments, multiline items or a change in declaration shape.

Alignment is deliberately local. One unusually long unrelated line must not create huge gaps throughout a file.

Struct Fields and Tags

Struct fields are one per line and every field ends with a comma.

type User struct {
    ID:       int    `json:"id"`,      // Stable identifier.
    Name:     string `json:"name"`,    // Display name.
    Password: string `json:"-"`,       // Never serialize.
}

Within one compatible field group, field types, tags and trailing comments may align. Struct tag contents and key order are preserved exactly. Ordinary formatting never invents, removes or sorts tags.

Enums and Declaration Groups

Enum members remain one per line and end with commas.

enum Permission uint {
    none    = 0,
    read    = 1 << iota,
    write   = 1 << iota,
    execute = 1 << iota,
}

Typed declaration groups may align names and initializer columns:

TokenType (
    ILLEGAL := "ILLEGAL",
    EOF     := "EOF",
    IDENT   := "IDENT",
)

Formatting preserves declaration and enum order. Sorting is a separate action.

Imports

Single imports are one per line:

import "fmt"
import "io"

Grouped imports are indented four spaces:

import (
    "fmt"
    sys "platform/linux/amd64"
)

Ordinary formatting currently preserves import order. Removing unused imports, merging groups, sorting or adding missing imports belongs to organize-imports/code-cleanup behavior.

Top-Level Layout

The formatter preserves top-level declaration order.

#target(os: "linux", arch: "amd64")

module main

import "fmt"

type Percent int range 0..100

fn main() int {
    return 0
}

Recommendations such as one primary public type per file are code-quality guidance. Moving declarations between files is a refactoring, not formatting.

Functions and Declarations

Function parameters use canonical name: Type syntax and comma-space separation on one line.

fn Add(a: int, b: int) int {
    return a + b
}

Variable declarations retain their declared form:

let value := 10
let mut value := 10
let typed: int := 10
let mut pending: int

int mut: a, b, c

Formatting does not merge separate declarations into a declaration table. That is a code cleanup or refactoring.

Types and Literals

The formatter preserves type identity and source-level unit meaning. It normalizes only the punctuation and spacing owned by the grammar.

type Percent int range 0..100
type Money decimal<SEK>

type Box[T] struct {
    value: T,
}

Formatting never replaces a named unit with a structural unit expression, reorders unit factors for semantic canonicalization, changes the numeric carrier or invents a conversion.

Explicit defaults, omitted struct fields and empty collection literals are preserved. The formatter does not expand omitted values or insert defaults.

Error and Cleanup Syntax

Formatting preserves the semantic forms defined by error handling, properties, destruction and cleanup. It must not rewrite one projection or handler form into another when they have different ownership behavior.

return try ReadValue()

discard Calculate()

assert ready
assert ready, "service must be ready"

Explicit discard remains explicit. The formatter neither inserts it for ordinary ignored results nor removes it from source. Likewise, formatting never adds or removes cleanup operations.

Operators and Parentheses

The formatter follows Sec's canonical precedence and associativity rules. Parentheses affecting meaning are always preserved.

(1 + 2) * 3

Redundant outer parentheses may be removed only when meaning is unchanged, parser recovery is not involved and readability is not reduced. Clarifying parentheses around mixed boolean and comparison expressions may remain.

Line Width

Sec 0.1 has not yet locked an exact canonical soft width. The formatter therefore does not impose a hard syntax limit or aggressively reflow source merely to hit a column count.

Until an exact soft target is chosen:

  • preserve sensible existing multiline structure;
  • break constructs only where canonical multiline forms exist;
  • never truncate or reflow string contents;
  • do not create unreadable comment alignment merely to preserve columns.

Recoverable Source

Formatting is not restricted to perfectly valid source. The formatter should preserve and format unaffected structure around recoverable errors such as a missing colon, missing comma or unfinished expression.

It must:

  • never delete unknown tokens;
  • preserve error-node text;
  • format surrounding valid blocks;
  • normalize syntax only when the recovered construct is unambiguous;
  • require a fix operation before inserting missing syntax.

Editor Formatting

Document formatting, range formatting, on-type formatting and format-on-save must use the same shared formatter.

Range formatting expands a requested range to complete safe syntax boundaries such as a statement, declaration, parameter list, field group or block. On-type formatting is intentionally conservative and should perform only small local layout work after triggers such as a closing delimiter, comma, colon or newline.

LSP clients should eventually receive minimal non-overlapping edits when practical, while CLI formatting may simply rewrite the file.

Generated Code

Generated Sec source must also be canonical. Code generators should construct structured source and invoke the shared formatter rather than embedding a second printer with its own style decisions.

Formatter Directives

Sec 0.1 does not require general formatter-disable directives. A future narrowly scoped design may be added only when concrete use cases justify it.

Rule: formatter directives must not become a general escape hatch from canonical Sec style.

Diagnostics

Formatting differences are not language errors. sec fmt --check should identify noncanonical files and may point to differing ranges without pretending the program is semantically invalid.

Formatter/fix diagnostics use stable identities for cases such as:

  • noncanonical source;
  • an unrecoverable region;
  • a refused unsafe fix;
  • ambiguous normalization;
  • a generated file that must not be rewritten.

Transformation Safety

Every source transformation belongs to an explicit safety class:

Formatting
SyntaxNormalization
SafeFix
CodeCleanup
StructuralRefactoring
BehaviorChanging
PotentiallyLossy

Ordinary formatting accepts only Formatting and SyntaxNormalization. --fix may additionally accept SafeFix. No automatic mode may perform behavior-changing or potentially lossy transformations.

Current Implementation Status

The repository already contains a shared initial formatter in internal/formatter, and the LSP uses it for full-document formatting. The current implementation already handles line-ending preservation, four-space indentation, trailing-whitespace removal, final newline normalization, common block indentation, selected comma spacing, func-to-fn normalization, selected attribute placement and format-on-save integration.

Implementation remains partial. Important unfinished areas include:

  • the complete sec fmt CLI;
  • --check and --fix integration;
  • recursive directory/project formatting;
  • range and on-type formatting;
  • minimal edit generation;
  • a fully lossless trivia-aware syntax printer;
  • complete recoverable-syntax formatting;
  • the full local alignment engine for fields, tags, comments and declaration tables;
  • canonical import sorting policy;
  • complete structured formatter diagnostics and fuzz/idempotence coverage.
Status note: this page documents the canonical formatter contract. The current LSP formatter already implements a useful subset, but some examples describe required canonical behavior that the present formatter may not yet reproduce in every syntax form.

Best Practices

  • Format Sec code with the shared formatter rather than editor-specific style rules.
  • Do not hand-tune spacing that canonical formatting will deliberately replace.
  • Use formatter output as the canonical layout for reviews and generated code.
  • Keep semantic fixes separate from ordinary formatting in scripts and editor settings.
  • Do not expect formatting to infer ownership, fix types or reorganize declarations.
  • Preserve comments as semantic documentation even when their indentation changes.
  • Use organize-imports or refactoring actions for reordering, not ordinary formatting.
  • Treat --check as a style/reproducibility gate, not as a language-validity check.
  • Prefer canonical multiline structures over manual alignment tricks that depend on tabs.
  • Expect formatting to remain stable across CLI, LSP and generated-source workflows.

Summary

  • Sec has one canonical source style.
  • Ordinary formatting is deterministic, idempotent and not style-configurable.
  • The CLI, LSP, generators and refactorings must share one formatter implementation.
  • Canonical formatting does not require successful Sema.
  • Syntax normalization is allowed only for parser-proven unambiguous aliases.
  • Safe fixes are separate and require stronger compiler proof.
  • Four spaces are used for indentation; tabs are never emitted.
  • Braces remain on the construct line and else follows the closing brace.
  • Operator, comma, colon, member and call spacing follow one canonical policy.
  • Ownership operators are semantically significant and never inferred by formatting.
  • Comments, documentation and literal text are preserved.
  • Struct fields, tags, enum initializers and declaration groups may use local column alignment.
  • Ordinary formatting preserves import and declaration order.
  • Sec 0.1 does not yet lock a hard or exact soft line width.
  • Recoverable source should remain format-friendly without guessing missing semantics.
  • Generated code must pass through the same formatter.
  • Ordinary formatting never performs code cleanup, structural refactoring or behavior-changing edits.
  • The current formatter implementation is useful but still partial relative to the complete canonical rulebook.