lint 格式化器

@ttsc/lint 自带一个 Prettier 风格的格式化器,作为一组 FormatRule 跑在同一个引擎里。它替代 prettier,由 ttsc format 应用。本页讲格式化与 lint 的关系、配置模型、宽度感知重排引擎、以及 print_*.go 的角色。源码 packages/lint/linthost/rules_format_*.goprint_*.goformat.goconfig_format.go

格式化即一组 FormatRule

格式化器不是独立子系统,而是一批实现 FormatRule 接口(IsFormat() bool)的规则(见 规则与注册表)。这让它复用引擎的遍历、Context、autofix 机制:

  • ttsc fix:应用 lint 类 + FormatRule 类的全部 edit。
  • ttsc format:过滤到 FormatRule finding,只应用格式化重写,写回磁盘,类型检查。

引擎用 Finding.IsFormat 标志路由(engine.go:122),isFormatRuleengine.go:74)判定一条规则是否 opt-in 格式化类别。

配置模型:单独的 format block

README(packages/lint/README.md)强调:格式化lint.config.tsformat block 配置,rules map。放进 rulesformat/* id 被忽略。

export default {
  format: {
    printWidth: 100,
    singleQuote: true,
    trailingComma: "all",
    sortImports: { order: ["<BUILTIN_MODULES>", "", "<THIRD_PARTY_MODULES>", "", "^[./]"] },
    jsDoc: true,
  },
} satisfies ITtscLintConfig;

关键语义:

  • format block 存在(哪怕空 format: {})就启用 always-on 格式化规则(Prettier 默认值),让 ttsc format 重写源。
  • severity(默认 "off"):check 时格式化诊断的级别,影响 ttsc formatttsc check 默认不因格式化失败,除非 opt-in format.severity
  • sortImportsopt-in:只在你设了它才生效。其他键 block 一出现就生效。

config_format.goformat_editor_settings.go 解析这个 block,format.go 是格式化入口。

配置键到行为的映射

配置键效果实现文件(约)
semiASI 终止语句加分号rules_format_semi.go rules_format_orphan_semi.go
singleQuote引号风格rules_format_quotes.go
arrowParens单参数箭头加/去括号rules_format_arrow_parens.go
bracketSpacing对象/具名导入括号内空格rules_format_bracket_spacing.go
quoteProps对象属性键引号rules_format_quote_props.go
trailingComma多行列表尾逗号rules_format_trailing_comma.go
printWidth/tabWidth/useTabs/endOfLine列感知换行rules_format_print_width.go rules_format_indent.go
sortImports按 order 分组排序、合并重复模块rules_format_sort_imports.go rules_sort_imports.go
jsDocJSDoc 规范化(默认开)rules_format_jsdoc.go

还有一批 keyless 布局行为随 format block 出现而生效:语句拆分(rules_format_statement_split.go)、缩进、空白规范化(rules_format_whitespace.go)、子句合并(rules_format_clause_join.go)、声明头重排(rules_format_declaration_header.go)、三元 nullish 括号(rules_format_ternary_nullish_parens.go)、leading-semicolon 合并、参数属性拆分(rules_format_parameter_properties.go)。

宽度感知重排引擎(print_*.go)

格式化的难点是列感知换行:对象/数组字面量、call/new 参数、具名 import/export 子句在 flat 形式超出 printWidth 时跨行折断、带尾逗号。这套逻辑在 print_*.go 一组文件里:

按种类拆分的打印文件:print_nodes_array.goprint_nodes_call.goprint_nodes_function.goprint_nodes_imports.goprint_nodes_list.goprint_nodes_object.goprint_nodes_ternary.godisplay_width.go 算显示宽度(全角字符等)。这是一个从 Prettier 的 doc 模型移植来的、独立于 @ttsc/factory printer 的实现(factory 是给代码生成的零依赖 printer,lint 这套是给源码格式化的)。

与 @ttsc/factory printer 的关系

两者都是"宽度感知 printer",但用途不同、代码独立:

lint print_*.go@ttsc/factory TsPrinter
语言GoTypeScript
输入tsgo AST(源文件)factory outline AST(代码生成)
用途格式化用户源码生成新源码
模式同 Prettier 的 flat/break同 Prettier 的 flat/break

它们共享"超 printWidth 就 break + 尾逗号"的理念,但不共享代码。见 @ttsc/factory 模块

测试纪律

格式化器有一类特殊的测试陷阱(.codex/skills/development/SKILL.md 记录的真实事故):只喂规则它自己的规范输出、断言不变,证明的是幂等性而非正确性。一批 formatter 过度匹配就是这么 ship 的——predicate hug 或 break 了 Prettier 不动的形状,但每个测试都喂已正确的例子,什么都没触发。所以每条格式化规则需要:

  • 转换方向:mangled/未格式化输入 → 规范输出(输入≠输出)。
  • 负向 twin:相邻一个属性之差、必须动作的例子。
  • 边界:空、单元素、恰好宽度上限、最深嵌套。
  • oracle 派生期望:期望取自 Prettier 3.8.3 权威输出,不是当前代码碰巧 emit 的。

测试在 packages/lint/test/format/10.8k LOC)与 test/printer/4.6k LOC)。

不变量

  • 格式化只经 format block 配置,rules 里的 format/* 被忽略。
  • ttsc format 写回磁盘无视 severityttsc check 默认不因格式化失败。
  • format block 存在即启用 always-on 规则(Prettier 默认值)。
  • sortImports 必须显式设才生效。

接下来