Format rules
The format rule set in @ttsc/lint, semicolons, quotes, trailing commas, import ordering, JSDoc normalization, and column-aware print-width reflow. This is what replaces Prettier in a ttsc project.
The format block (canonical)
The recommended way to configure formatting is a Prettier-style format block in lint.config.ts. Keys mirror .prettierrc:
// lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint";
export default {
format: {
severity: "warning",
printWidth: 100,
singleQuote: true,
trailingComma: "all",
sortImports: {
order: ["<BUILTIN_MODULES>", "", "<THIRD_PARTY_MODULES>", "", "^[./]"],
},
jsDoc: true,
},
rules: {
"no-var": "error",
},
} satisfies ITtscLintConfig;Presence of the block (even empty format: {}) configures the format rules at Prettier defaults for ttsc format. It does not make ttsc check fail on formatting unless you set format.severity. Each format key drives one rule:
format key | Effect |
|---|---|
semi | Insert trailing semicolons on ASI-terminated statements. |
singleQuote | Convert quoted strings to the preferred quote style. |
arrowParens | Add or remove parens around a single arrow parameter. |
bracketSpacing | Spaces inside object and named-import/export braces. |
quoteProps | Quote or unquote object property keys. |
trailingComma | Add trailing commas to multi-line lists. |
printWidth, tabWidth, useTabs, endOfLine | Prettier-style line reflow. |
sortImports (opt-in) | Group imports by order, alphabetize them, and merge duplicate modules. |
jsDoc (on by default) | Normalize JSDoc blocks toward prettier-plugin-jsdoc . |
sortImports is opt-in: it only runs when you set it. Every other format behavior — including JSDoc normalization (set jsDoc: false to opt out) — runs as soon as a format block is present, along with the keyless layout passes (statement splitting, indentation, whitespace, clause joining, declaration headers, ternary and nullish parens, orphan semicolons, and parameter properties).
format.severity
format.severity (default "off") sets the diagnostic level for every format rule in ttsc check. It is a property of the format block, not an external escape hatch:
format: { severity: "off" }"off" keeps format rules out of ttsc check; ttsc format still applies the configured formatter. Use this when formatting should be write-only and never block compilation.
Configuring individual behaviors
Formatting is configured only through the format block. The rules map is for lint rules; format settings placed there are ignored. Turn a behavior off through its format key (for example trailingComma: "none"), not a rules entry.
Individual format keys
semi
Enforce trailing semicolons on statements that need them. Default: required. Autofixable. Driven by format.semi.
singleQuote
One quote style throughout. Pick "double" (default) or "single". Backticks always allowed for template literals. Driven by format.singleQuote.
arrowParens
Parentheses around a single arrow-function parameter. "always" (default) keeps (x) => x; "avoid" strips the parens of a single bare-identifier parameter, giving x => x. A typed, destructured, rest, optional, defaulted, or multi-parameter list keeps its parentheses in both modes. Driven by format.arrowParens.
bracketSpacing
Inner spaces in single-line braces. true (default) keeps the space inside object literals, destructuring patterns, named imports/exports, and type literals ({ a }); false removes it ({a}). Block, class, interface, and enum braces are unaffected. Driven by format.bracketSpacing.
quoteProps
Quoting of object-literal property keys. "as-needed" (default) unquotes keys that are valid identifiers, keeping "__proto__" and non-identifier or numeric keys quoted; "consistent" keeps every key quoted when any one of them needs quotes; "preserve" leaves quoting untouched. Driven by format.quoteProps.
trailingComma
Enforce trailing commas in multiline literals, object literals, array literals, function parameter lists, function call arguments, and import/export specifier lists. Single-line literals are untouched. Driven by format.trailingComma.
sortImports
Opt-in. Reorder, group, and merge imports. Set format.sortImports to true for defaults, or to an object to customize.
The order array drives grouping. Each entry is a regular expression matched against a module specifier, or one of these placeholders:
<BUILTIN_MODULES>— Node built-ins (node:fs,assert, …).<THIRD_PARTY_MODULES>— bare-specifier externals not matched elsewhere.<TYPES>—import typedeclarations (optionally scoped, e.g.<TYPES>^[.]).""— emit one blank line at this position.
The default order is ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^[.]"], with no blank lines between groups (add "" entries to insert them). Within each group declarations sort by specifier — case-insensitively unless caseSensitive: true — and named specifiers always sort. Duplicate imports of the same module are merged; combineTypeAndValue: true additionally folds a type-only import into a value import (import { foo, type Bar } from "m"). Autofixable.
jsDoc
On by default. Normalize JSDoc tag names toward their canonical form.
Handles:
- Tag synonyms (
@return→@returns,@desc→@description, …).
Tag sorting, @param column alignment, continuation-space normalization, and description wrapping are on the roadmap, not yet implemented.
Autofixable. On by default; set format.jsDoc: false to disable, or pass an options object to customize.
printWidth
Column-aware reflow. Mirrors Prettier’s printWidth, tabWidth, useTabs, and endOfLine. The most distinctive rule in the format set, the one that actually reshapes your code.
Driven by format.printWidth, format.tabWidth, format.useTabs, format.endOfLine (defaults: 80, 2, false, "lf").
What it does
For each list-shaped expression in the file (object literals, array literals, function arguments, named imports, named exports, …), the rule considers two layouts:
- Flat: one line, no internal breaks.
- Broken: opening punctuator on the head line, one entry per indented line, closing punctuator on its own line. The trailing comma on the last entry honors
format.trailingComma:"all"keeps one on every list,"es5"keeps one on arrays / objects / named imports / exports but not on call ornewargument lists, and"none"drops it everywhere. The setting flows into the reflow automatically.
It measures the flat form. If flat fits within printWidth (counting the column where the node starts), the flat layout is kept. If it doesn’t fit, the broken layout is emitted.
// Before (printWidth: 20)
const user = { id: "8f5d2f3a", name: "Sam", email: "samchon.github@gmail.com" };
// After
const user = {
id: "8f5d2f3a",
name: "Sam",
email: "samchon.github@gmail.com",
};When the rule can’t reflow a node (a shape it doesn’t recognize, or a comment sits inside it), it abstains, no edits applied, original bytes pass through verbatim.
Trailing // line comments do not count against the budget. A line comment runs to the end of the source line by definition, so a node whose only overflow is its trailing comment stays flat, matching Prettier’s behavior. The un-movable punctuation suffix (;, ) {, ,) is still charged.
What it reflows
| Shape | Example before | Example after (printWidth: 20) |
|---|---|---|
| Object literal | const x = { aaa: 1, bbb: 2, ccc: 3 }; | multi-line with trailing comma |
| Array literal | const x = ["alpha", "beta", "gamma"]; | multi-line with trailing comma |
| Call expression | process(aaaaaa, bbbbbb, cccccc); | callee on head line, one arg per indented line |
new expression | new Foo(aaaaaa, bbbbbb, cccccc); | same as call, new keyword preserved |
| Type-argument call | foo<Alpha>(aaaaaa, bbbbbb, cccccc); | type arguments stay flat; value arguments break |
| Optional-call | foo?.(aaaaaa, bbbbbb, cccccc); | ?. token preserved between callee and ( |
| Named imports | import { alpha, bravo, charlie } from "x"; | multi-line specifier list |
| Named exports | export { alpha, bravo, charlie }; | multi-line specifier list |
import type { … } | preserves the type modifier | same |
A call or new whose last argument is a function, arrow function, object literal, or (non-numeric) array literal keeps that argument hugging the parentheses, Prettier’s last-argument hugging, instead of exploding the whole argument list. The callback body is re-indented to its new column:
register("compile", async (ctx) => {
await ctx.run();
});The mirror first-argument hugging keeps a leading block-bodied callback on the open-paren line when the trailing argument is short and simple (reduce((acc, x) => { … }, init), useEffect(() => { … }, [deps])). A few related call shapes reflow specially, matching Prettier:
- A call with two or more callback arguments explodes one per line even when it fits (
promise.then(() => a, () => b)), Prettier’s function-composition rule; decorators are exempt. - A test call (
it/test/describeand their focus/skip variants) keeps its callback hugged even when the description string runs pastprintWidth. - A concisely-printed numeric array packs as many elements per line as fit (Prettier’s fill), and an array whose elements are all same-kind multi-element arrays or objects breaks one element per line even when it fits.
A ternary (conditional expression) is reflowed too: Prettier’s indented staircase, with a nested ternary in the consequent position parenthesized when the chain stays on one line.
Width is measured in display columns (Prettier’s getStringWidth), not UTF-8 bytes: a wide East Asian or emoji code point counts as two columns, so a Hangul or CJK identifier is charged its true width.
What it does NOT touch (yet)
- JSX elements and fragments.
- Binary expressions.
- Destructuring patterns.
- Decorators (their call arguments still hug per the rules above).
- Multi-line string and template literals.
- Lists with comments interleaved between members (the rule detects this and abstains).
import * as ns from "x"namespace declarations.- Import-attribute clauses (
with {}/assert {}payloads).
For every uncovered shape the rule produces zero findings and zero edits.
Limitations
- Comments between members of a covered list make the rule abstain. Inline-comment handling is on the roadmap.
- No Prettier magic-trailing-comma hint. Aside from object-wrap preservation (an object written with a newline after
{stays expanded) and the same-kind array-of-arrays/objects force-break described above, width is the deciding factor. - Long chained call sites like
someLong.chained.call(a, b)only reflow the argument list; the callee chain is not split. Chain-aware printing is on the roadmap. - A callback body that contains a statement the printer does not yet cover, a multi-line
if,switch, or assignment. Makes the enclosing call abstain. The call is left byte-identical rather than half-reflowed. - The rule never inserts or removes blank lines between top-level statements.
Key interactions
Print-width reflow and trailing-comma handling share the trailingComma setting. The broken layout that the reflow emits honors it: the last entry gets a trailing comma only when trailingComma permits one for that list kind, and none when trailingComma is "none". There is no "off" value; set trailingComma: "none" to drop trailing commas everywhere, reflowed multi-line literals included.
ttsc format re-runs every enabled behavior until the file is stable, so key order in lint.config.ts doesn’t affect the final result.