@contractkit/prettier-plugin 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
5
5
  CLI tsup v8.5.1
6
6
  CLI Target: esnext
7
7
  ESM Build start
8
- ESM dist/index.js 26.17 KB
9
- ESM dist/index.js.map 65.66 KB
10
- ESM ⚡️ Build success in 231ms
8
+ ESM dist/index.js 27.08 KB
9
+ ESM dist/index.js.map 69.36 KB
10
+ ESM ⚡️ Build success in 174ms
11
11
  DTS Build start
12
- DTS ⚡️ Build success in 4442ms
13
- DTS dist/index.d.ts 1.13 KB
12
+ DTS ⚡️ Build success in 5639ms
13
+ DTS dist/index.d.ts 1.65 KB
@@ -3,24 +3,26 @@ $ vitest run --coverage
3
3
   RUN  v4.1.5 /home/runner/work/ContractKit/ContractKit/apps/prettier-plugin
4
4
  Coverage enabled with v8
5
5
 
6
- ✓ tests/print-ck.test.ts (81 tests) 923ms
7
- ✓ tests/round-trip.test.ts (34 tests) 2390ms
8
- ✓ formats test.ck to itself  690ms
9
- ✓ formatting is a fixed point for source #0  1192ms
6
+ ✓ tests/format-plugin.test.ts (3 tests) 136ms
7
+ ✓ tests/print-ck.test.ts (81 tests) 1049ms
8
+ ✓ tests/round-trip.test.ts (49 tests) 2210ms
9
+ ✓ formats test.ck to itself  918ms
10
+ ✓ formatting is a fixed point for source #0  515ms
10
11
 
11
-  Test Files  2 passed (2)
12
-  Tests  115 passed (115)
13
-  Start at  16:09:43
14
-  Duration  8.00s (transform 3.22s, setup 0ms, import 8.69s, tests 3.31s, environment 0ms)
12
+  Test Files  3 passed (3)
13
+  Tests  133 passed (133)
14
+  Start at  17:32:00
15
+  Duration  8.78s (transform 4.30s, setup 0ms, import 15.52s, tests 3.40s, environment 1ms)
15
16
 
16
17
   % Coverage report from v8
17
18
  -------------------|---------|----------|---------|---------|-------------------
18
19
  File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
19
20
  -------------------|---------|----------|---------|---------|-------------------
20
- All files | 76.13 | 71.15 | 85.36 | 82.04 |
21
+ All files | 79.5 | 74.08 | 93.47 | 86.44 |
21
22
  indent.ts | 100 | 100 | 100 | 100 |
22
- print-ck.ts | 89.09 | 87.56 | 100 | 97.36 | 91-92
23
- print-contract.ts | 45.71 | 38.23 | 50 | 46.66 | 20,52-85
24
- ...t-operation.ts | 78.41 | 70.42 | 95 | 86.75 | ...37,354,362-373
25
- print-type.ts | 64.13 | 63.46 | 63.63 | 66.66 | ...02-103,148-151
23
+ index.ts | 81.81 | 100 | 100 | 80 | 47-48
24
+ print-ck.ts | 90.59 | 88.26 | 100 | 100 | ...57-158,172,176
25
+ print-contract.ts | 74.28 | 60.29 | 100 | 80 | 70-76,83
26
+ ...t-operation.ts | 78.77 | 71.42 | 95 | 87.55 | ...63,380,388-399
27
+ print-type.ts | 69.56 | 64.42 | 81.81 | 72 | ...,45-61,148-151
26
28
  -------------------|---------|----------|---------|---------|-------------------
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @contractkit/prettier-plugin-contractkit
2
2
 
3
+ ## 0.14.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ab69718: Stop the formatter deleting comments in and around a `security { }` block, and keep the trailing newline at end of file.
8
+
9
+ The identical bug in `response { }` was fixed in 0.14.0; this position was not covered. `SecurityBody_fields` skipped standalone comments outright, so a `#` run inside a security block never reached the AST and `pnpm format` deleted it with no warning — exactly the prose that records _why_ a policy floor sits where it does. Three more positions failed the same way and now round-trip:
10
+ - a comment run above a body key in an operation body (the common case being a note above the `security:` key, or above `security: none`, explaining why a verb overrides the file's floor);
11
+ - a comment run above a verb that also carries its own inline `# ...` doc comment — the inline comment won and the run above it was discarded;
12
+ - a comment left after the last key in an operation body, before the closing brace.
13
+
14
+ `SecurityPolicyLine` and `SecuritySignatureLine` no longer take a trailing `comment?`. Whitespace skipping in an Ohm syntactic rule crosses newlines, so that optional comment swallowed a standalone comment written on the _next_ line and re-emitted it as the policy's inline description — turning a note to the next contract author into generated SDK documentation. `SecurityBody_fields` now collects every comment and decides which one is inline by comparing source lines, the same way `FieldList` already does.
15
+
16
+ The same newline-crossing optional appeared on `ModelBody_alias`, where it was worse: it claimed the doc comment of the _next_ declaration. `contract Status: enum(a, b)` followed by a documented contract either lost that doc comment outright, or silently re-filed it as `Status`'s own description — which then flowed into generated SDK docs describing the wrong type. Declaration-level comments are now items in `Root` (`DeclItem`) rather than a `comment*` prefix on each declaration, so their placement is decided where the previous declaration's end line is known: same line as the declaration above → its inline description; directly above the next one → its doc comment; otherwise a standalone block. A comment after the last declaration in a file used to be a parse error and is now kept as `CkRootNode.trailingComments`.
17
+
18
+ Two field positions were lost the same way. A comment on a nested object's opening brace (`rp: { # The relying party`) was offered to the first field _inside_ the object and dropped when that field had its own comment; it now belongs to the field owning the brace. A trailing comment on a field whose type wraps over several lines — `enum(\n a,\n b\n) # note` — was compared against the line the field _started_ on, so it was filed as a standalone comment for the next field; it now compares against the line the field ends on.
19
+
20
+ `SecurityFields` gains `leadingComments` / `trailingComments`, `OpOperationNode` gains `bodyLeadingComments`, `bodyTrailingComments`, and `leadingComments`, and `CkRootNode` gains `trailingComments`. All are optional and additive, so codegen plugins are unaffected.
21
+
22
+ ### `#` where it is not a comment
23
+
24
+ `nameText` ended a name at any `#`, so `name: Generate C# client` silently became `Generate C` and the rest vanished with no diagnostic. It now ends at whitespace-then-`#`, matching `optionsRawChar` — a bare `#` is data, and only ` #` opens a comment. That makes the two unquoted-text positions in the language consistent with each other. The TextMate `name-decl` pattern captured the whole rest of the line, colouring a trailing comment as part of the name; it now stops at the same boundary the parser does.
25
+
26
+ Note that a `name:` containing a bare `#` now parses to its full text, and the SDK method name is derived from `name:` — so a contract that was silently relying on the truncated value will see that method renamed. Only a name with a `#` in it is affected.
27
+
28
+ Separately, the prettier plugin trimmed the printed source and returned it without re-adding a terminator, so every formatted `.ck` file lost its trailing newline — fighting any editor or lint rule that wants one. The plugin now has end-to-end tests that go through prettier itself rather than calling `printCk` directly, which is the layer where that slipped through.
29
+
30
+ - Updated dependencies [ab69718]
31
+ - @contractkit/core@0.26.0
32
+
3
33
  ## 0.14.0
4
34
 
5
35
  ### Minor Changes
package/README.md CHANGED
@@ -55,6 +55,10 @@ Formatting a well-formed `.ck` file leaves it byte-identical. The formatter deli
55
55
  - **Comments inside a `response` block**, in all four positions: above a status code, above a `mime: Type` line, above a `headers:` block, and before either closing brace.
56
56
  - **An empty status block.** `304: {}` and `304:` mean different things to codegen — the first says the service returns that status with no body — so neither is normalized into the other.
57
57
  - **Comments in the `options` block**, including a header comment above the `options` keyword and a trailing comment on a `keys`/`services` entry.
58
+ - **Comments in and around a `security { }` block**: inside it above or below `policy:`, above a `security:` key on a verb, and above a `security: none` line. This is where the rationale for a policy floor is written, so losing it loses the reason the contract looks the way it does.
59
+ - **Comments above any operation body key**, and after the last one. A `#` block above a verb survives even when the verb carries its own inline `# ...` — they are separate, and only the inline one is the description.
60
+ - **Comments between declarations.** A comment trailing a one-line contract stays on that line; one directly above the next declaration stays its doc comment. A comment after the last declaration in the file is kept too.
61
+ - **Comments on a nested object's brace** (`rp: { # ... }`) and trailing a field whose type wraps over several lines.
58
62
  - **Unquoted options values.** `PetService: #modules/pet/pet.service.js` stays bare and a quoted value stays quoted. Both parse to the same string, so the parser records which form was written; a value that could not be read back bare is quoted regardless.
59
63
 
60
64
  This is covered by `tests/round-trip.test.ts`, which formats every `.ck` file under `contracts/` and asserts the output is unchanged, plus checks that formatting is a fixed point. Anything that makes the printer normalize rather than preserve will fail it.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import type { Plugin } from 'prettier';
2
2
  import type { CkRootNode } from '@contractkit/core';
3
+ /**
4
+ * Prettier plugin registering the `.ck` language, its parser, and its printer.
5
+ *
6
+ * The parser is `parseCk` and the printer is `printCk`, so formatting behaviour lives in
7
+ * `@contractkit/core` and `print-ck.ts` rather than here — this module only adapts them to
8
+ * prettier's interface. `tests/format-plugin.test.ts` covers that adapter layer, which a
9
+ * printer-level test cannot reach.
10
+ */
3
11
  declare const plugin: Plugin<CkRootNode>;
4
12
  export default plugin;
5
13
  export { printCk, DEFAULT_PRINT_WIDTH } from './print-ck.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAUpD,QAAA,MAAM,MAAM,EAAE,MAAM,CAAC,UAAU,CA8B9B,CAAC;AAEF,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAkBpD;;;;;;;GAOG;AACH,QAAA,MAAM,MAAM,EAAE,MAAM,CAAC,UAAU,CA8B9B,CAAC;AAEF,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -356,6 +356,7 @@ function printOperation(op) {
356
356
  const lines = [];
357
357
  const modPart = op.modifiers?.length ? `(${op.modifiers[0]})` : "";
358
358
  const inlineDescription = op.descriptionInline ?? true;
359
+ for (const c of op.leadingComments ?? []) lines.push(`${I1}# ${c}`);
359
360
  if (op.description && !inlineDescription) {
360
361
  for (const line of op.description.split("\n")) lines.push(`${I1}# ${line}`);
361
362
  }
@@ -363,12 +364,24 @@ function printOperation(op) {
363
364
  lines.push(`${I1}${op.method}${modPart}: {${commentSuffix}`);
364
365
  const order = op.keyOrder ?? [];
365
366
  const rest = CANONICAL_KEY_ORDER.filter((k) => !order.includes(k));
367
+ const orphaned = [];
366
368
  for (const key of [
367
369
  ...order,
368
370
  ...rest
369
371
  ]) {
370
- lines.push(...printOperationKey(op, key));
372
+ const keyLines = printOperationKey(op, key);
373
+ const comments = op.bodyLeadingComments?.[key] ?? [];
374
+ if (keyLines.length === 0) {
375
+ orphaned.push(...comments);
376
+ continue;
377
+ }
378
+ for (const c of comments) lines.push(`${I2}# ${c}`);
379
+ lines.push(...keyLines);
371
380
  }
381
+ for (const c of [
382
+ ...orphaned,
383
+ ...op.bodyTrailingComments ?? []
384
+ ]) lines.push(`${I2}# ${c}`);
372
385
  lines.push(`${I1}}`);
373
386
  return lines;
374
387
  }
@@ -470,13 +483,19 @@ function printSecurity(security, indent = I2, innerIndent = I3) {
470
483
  `${indent}security: none`
471
484
  ];
472
485
  const fields = security;
473
- if (fields.policy === void 0) return [];
486
+ const leading = fields.leadingComments ?? [];
487
+ const trailing = fields.trailingComments ?? [];
488
+ if (fields.policy === void 0 && leading.length === 0 && trailing.length === 0) return [];
474
489
  const lines = [
475
490
  `${indent}security: {`
476
491
  ];
477
- const comment = fields.policyDescription ? ` # ${fields.policyDescription}` : "";
478
- const value = fields.policy === false ? "none" : fields.policy;
479
- lines.push(`${innerIndent}policy: ${value}${comment}`);
492
+ for (const c of leading) lines.push(`${innerIndent}# ${c}`);
493
+ if (fields.policy !== void 0) {
494
+ const comment = fields.policyDescription ? ` # ${fields.policyDescription}` : "";
495
+ const value = fields.policy === false ? "none" : fields.policy;
496
+ lines.push(`${innerIndent}policy: ${value}${comment}`);
497
+ }
498
+ for (const c of trailing) lines.push(`${innerIndent}# ${c}`);
480
499
  lines.push(`${indent}}`);
481
500
  return lines;
482
501
  }
@@ -675,6 +694,10 @@ function printCk(ast, printWidth = DEFAULT_PRINT_WIDTH) {
675
694
  const modPart = route.modifiers?.length ? `(${route.modifiers[0]})` : "";
676
695
  parts.push(printDeclLeadIn(route.leadingComments, route.description) + `operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
677
696
  }
697
+ if (ast.trailingComments?.length) {
698
+ if (parts.length > 0) parts.push("");
699
+ for (const c of ast.trailingComments) parts.push(`# ${c}`);
700
+ }
678
701
  return parts.join("\n") + "\n";
679
702
  }
680
703
  __name(printCk, "printCk");
@@ -695,7 +718,10 @@ __name(printDeclLeadIn, "printDeclLeadIn");
695
718
  var { hardline, join } = builders;
696
719
  function toDoc(text) {
697
720
  const lines = text.trimEnd().split("\n");
698
- return join(hardline, lines);
721
+ return [
722
+ join(hardline, lines),
723
+ hardline
724
+ ];
699
725
  }
700
726
  __name(toDoc, "toDoc");
701
727
  var plugin = {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/indent.ts","../src/print-type.ts","../src/print-contract.ts","../src/print-operation.ts","../src/print-ck.ts"],"sourcesContent":["import type { Plugin } from 'prettier';\nimport { builders } from 'prettier/doc';\nimport { parseCk, DiagnosticCollector } from '@contractkit/core';\nimport type { CkRootNode } from '@contractkit/core';\nimport { printCk } from './print-ck.js';\n\nconst { hardline, join } = builders;\n\nfunction toDoc(text: string) {\n const lines = text.trimEnd().split('\\n');\n return join(hardline, lines);\n}\n\nconst plugin: Plugin<CkRootNode> = {\n languages: [\n {\n name: 'ContractDSL',\n parsers: ['contract-ck'],\n extensions: ['.ck'],\n vscodeLanguageIds: ['contract-ck'],\n },\n ],\n\n parsers: {\n 'contract-ck': {\n parse(text, _options) {\n const diag = new DiagnosticCollector();\n return parseCk(text, '<stdin>', diag);\n },\n astFormat: 'contract-ck',\n locStart: () => 0,\n locEnd: _node => 0,\n },\n },\n\n printers: {\n 'contract-ck': {\n print(path, options) {\n const node = path.node as CkRootNode;\n return toDoc(printCk(node, options.printWidth));\n },\n },\n },\n};\n\nexport default plugin;\nexport { printCk, DEFAULT_PRINT_WIDTH } from './print-ck.js';\n","export const INDENT = ' ';\n","import type { ContractTypeNode, FieldNode, InlineObjectTypeNode } from '@contractkit/core';\nimport { INDENT } from './indent.js';\n\n// ─── Type expression printer ────────────────────────────────────────────────\n\n/** Render a `ContractTypeNode` back to its `.ck` source string. */\nexport function printType(type: ContractTypeNode): string {\n switch (type.kind) {\n case 'scalar': {\n const constraints: string[] = [];\n if (type.format !== undefined) {\n // Print unquoted when format contains only safe chars; quote otherwise\n const fmt = type.format;\n constraints.push(/^[a-zA-Z0-9\\-.:/]+$/.test(fmt) ? fmt : `\"${fmt}\"`);\n }\n if (type.min !== undefined) constraints.push(`min=${type.min}`);\n if (type.max !== undefined) constraints.push(`max=${type.max}`);\n if (type.len !== undefined) constraints.push(`len=${type.len}`);\n if (type.regex !== undefined) constraints.push(`regex=/${type.regex}/`);\n return constraints.length > 0 ? `${type.name}(${constraints.join(', ')})` : type.name;\n }\n case 'array': {\n const args: string[] = [printType(type.item)];\n if (type.min !== undefined) args.push(`min=${type.min}`);\n if (type.max !== undefined) args.push(`max=${type.max}`);\n return `array(${args.join(', ')})`;\n }\n case 'tuple':\n return `tuple(${type.items.map(printType).join(', ')})`;\n case 'record':\n return `record(${printType(type.key)}, ${printType(type.value)})`;\n case 'enum':\n return `enum(${type.values.map(formatEnumValue).join(', ')})`;\n case 'literal':\n return typeof type.value === 'string' ? `literal(\"${type.value}\")` : `literal(${type.value})`;\n case 'union':\n return type.members.map(printType).join(' | ');\n case 'discriminatedUnion':\n return `discriminated(by=${type.discriminator}, ${type.members.map(printType).join(' | ')})`;\n case 'intersection':\n return type.members.map(printType).join(' & ');\n case 'ref':\n return type.name;\n case 'inlineObject':\n return printInlineObjectCompact(type);\n case 'lazy':\n return `lazy(${printType(type.inner)})`;\n }\n}\n\n/** Compact single-line form — used when inline object appears nested inside another type. */\nfunction printInlineObjectCompact(obj: InlineObjectTypeNode): string {\n const prefix = obj.mode ? `mode(${obj.mode}) ` : '';\n if (obj.fields.length === 0) return `${prefix}{}`;\n const parts = obj.fields.map(f => {\n const opt = f.optional ? '?' : '';\n let t = printType(f.type);\n if (f.nullable) t += ' | null';\n return `${f.name}${opt}: ${t}`;\n });\n return `${prefix}{ ${parts.join(', ')} }`;\n}\n\n/** Multi-line enum form — one value per line, used when single-line would exceed print width. */\nexport function printEnumExpanded(values: string[], indent: string): string {\n const innerIndent = indent + INDENT;\n return `enum(\\n${values.map(v => `${innerIndent}${formatEnumValue(v)}`).join(',\\n')}\\n${indent})`;\n}\n\n// ─── Field printer ──────────────────────────────────────────────────────────\n\n/** Print a full field declaration, including visibility, default, and inline comment.\n * Modifier order is canonical: override → deprecated → readonly|writeonly → type. */\nexport function printField(field: FieldNode, indent: string, printWidth: number = 80): string {\n const opt = field.optional ? '?' : '';\n const ovr = field.override ? 'override ' : '';\n const dep = field.deprecated ? 'deprecated ' : '';\n const vis = field.visibility !== 'normal' ? `${field.visibility} ` : '';\n const mods = `${ovr}${dep}${vis}`;\n const def = field.default !== undefined ? ` = ${formatDefault(field.default)}` : '';\n const comment = field.description ? ` # ${field.description}` : '';\n const innerIndent = indent + INDENT;\n\n // Expand inline object types to multi-line — same rule as type aliases.\n // Only when there's no default and no nullable union (those can't split cleanly).\n if (!field.nullable && field.default === undefined) {\n const trailing = extractTrailingInlineObject(field.type);\n if (trailing) {\n const { prefix, inlineObj } = trailing;\n const modePart = inlineObj.mode ? `mode(${inlineObj.mode}) ` : '';\n const header = prefix\n ? `${indent}${field.name}${opt}: ${mods}${prefix} & ${modePart}{${comment}`\n : `${indent}${field.name}${opt}: ${mods}${modePart}{${comment}`;\n return [header, ...printInlineObjectExpanded(inlineObj, innerIndent, printWidth), `${indent}}`].join('\\n');\n }\n }\n\n let typeStr = printType(field.type);\n if (field.nullable) typeStr += ' | null';\n const fullLine = `${indent}${field.name}${opt}: ${mods}${typeStr}${def}${comment}`;\n if (field.type.kind === 'enum' && !field.nullable && field.default === undefined && fullLine.length > printWidth) {\n const enumStr = printEnumExpanded(field.type.values, indent);\n return `${indent}${field.name}${opt}: ${mods}${enumStr}${comment}`;\n }\n return fullLine;\n}\n\n/** Print inline-object fields expanded (used when an inline brace object trails a type alias).\n * Any `trailingComments` (comments after the last field, before `}`) are emitted as indented\n * `# text` lines after the fields, matching how model bodies round-trip trailing comments. */\nexport function printInlineObjectExpanded(obj: InlineObjectTypeNode, indent: string, printWidth: number = 80): string[] {\n const lines = obj.fields.map(f => printField(f, indent, printWidth));\n for (const comment of obj.trailingComments ?? []) {\n lines.push(`${indent}# ${comment}`);\n }\n return lines;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\n/** Format a single enum value: bare identifier stays bare; anything else gets double-quoted. */\nexport function formatEnumValue(v: string): string {\n if (/^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(v)) return v;\n return `\"${v}\"`;\n}\n\n/** Format a default value: quote strings that aren't valid bare identifiers. */\nexport function formatDefault(val: string | number | boolean): string {\n if (typeof val === 'number' || typeof val === 'boolean') return String(val);\n // If it looks like a bare identifier (enum value, unquoted token), keep it bare.\n if (/^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(val)) return val;\n return `\"${val}\"`;\n}\n\n/**\n * Detect whether the last member of a type is an inline brace object, and if so\n * return the prefix type string and the inline object for expanded printing.\n * Returns null if the type doesn't end with an inline object.\n */\nexport function extractTrailingInlineObject(type: ContractTypeNode): {\n prefix: string | null;\n inlineObj: InlineObjectTypeNode;\n} | null {\n if (type.kind === 'inlineObject') {\n return { prefix: null, inlineObj: type };\n }\n if (type.kind === 'intersection') {\n const last = type.members[type.members.length - 1];\n if (last?.kind === 'inlineObject') {\n const prefixStr = type.members.slice(0, -1).map(printType).join(' & ');\n return { prefix: prefixStr, inlineObj: last };\n }\n }\n return null;\n}\n","import type { ModelNode } from '@contractkit/core';\nimport { printField, printInlineObjectExpanded, extractTrailingInlineObject, printType, printEnumExpanded } from './print-type.js';\nimport { INDENT } from './indent.js';\n\n// ─── Model declaration ───────────────────────────────────────────────────────\n\n/**\n * Render a `contract` body from its `Name: ...` onward (the `contract ` keyword and any\n * doc comment written above the declaration are prepended by the caller). Handles both the\n * type-alias form (`Name: <type>`) and the regular field-block form, including modifiers,\n * base chain, and per-field printing. Any `model.trailingComments` (comments after the last\n * field, before `}`) are emitted as indented `# text` lines so they round-trip.\n *\n * `model.description` is emitted here only when `model.descriptionInline` says the author wrote\n * it on the header line; otherwise it belongs above the declaration and the caller emits it.\n */\nexport function printModelDecl(model: ModelNode, printWidth: number = 80): string {\n // Type alias form: Name : typeExpression\n if (model.type !== undefined) {\n return printTypeAlias(model, printWidth);\n }\n\n // Regular model with fields (possibly inherited)\n // A doc comment written above the declaration is re-emitted there by the caller; only an\n // inline one belongs on the header line.\n const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : '';\n const modifiers = [\n model.deprecated ? 'deprecated' : '',\n model.inputCase || model.outputCase\n ? `format(${[model.inputCase ? `input=${model.inputCase}` : '', model.outputCase ? `output=${model.outputCase}` : ''].filter(Boolean).join(', ')})`\n : '',\n model.mode ? `mode(${model.mode})` : '',\n ]\n .filter(Boolean)\n .join(' ');\n const modePrefix = modifiers ? `${modifiers} ` : '';\n const baseChain = model.bases && model.bases.length > 0 ? `${model.bases.join(' & ')} & ` : '';\n const header = `${modePrefix}${model.name}: ${baseChain}{${commentSuffix}`;\n\n const lines: string[] = [header];\n for (const field of model.fields) {\n lines.push(printField(field, INDENT, printWidth));\n }\n for (const comment of model.trailingComments ?? []) {\n lines.push(`${INDENT}# ${comment}`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction printTypeAlias(model: ModelNode, printWidth: number): string {\n const type = model.type!;\n // A doc comment written above the declaration is re-emitted there by the caller; only an\n // inline one belongs on the header line.\n const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : '';\n const modifiers = [\n model.deprecated ? 'deprecated' : '',\n model.inputCase || model.outputCase\n ? `format(${[model.inputCase ? `input=${model.inputCase}` : '', model.outputCase ? `output=${model.outputCase}` : ''].filter(Boolean).join(', ')})`\n : '',\n model.mode ? `mode(${model.mode})` : '',\n ]\n .filter(Boolean)\n .join(' ');\n const modePrefix = modifiers ? `${modifiers} ` : '';\n\n // If the type ends with an inline brace object, expand it as a pseudo-model block.\n const trailing = extractTrailingInlineObject(type);\n if (trailing) {\n const { prefix, inlineObj } = trailing;\n const modePart = inlineObj.mode ? `mode(${inlineObj.mode}) ` : '';\n const header = prefix\n ? `${modePrefix}${model.name}: ${prefix} & ${modePart}{${commentSuffix}`\n : `${modePrefix}${model.name}: ${modePart}{${commentSuffix}`;\n const lines: string[] = [header, ...printInlineObjectExpanded(inlineObj, INDENT, printWidth), '}'];\n return lines.join('\\n');\n }\n\n // Simple type alias — single line, unless it's a long enum.\n // Note: the contract prefix \"contract \" (9 chars) is prepended by the caller.\n const singleLine = `${modePrefix}${model.name}: ${printType(type)}${commentSuffix}`;\n if (type.kind === 'enum' && 'contract '.length + singleLine.length > printWidth) {\n return `${modePrefix}${model.name}: ${printEnumExpanded(type.values, '')}${commentSuffix}`;\n }\n return singleLine;\n}\n","import type {\n OpRouteNode,\n OpOperationNode,\n OpResponseNode,\n ParamSource,\n SecurityNode,\n SecurityFields,\n ContractTypeNode,\n ObjectMode,\n PluginValue,\n McpConfigNode,\n OpBodyKey,\n} from '@contractkit/core';\nimport { SECURITY_NONE } from '@contractkit/core';\nimport { printType, formatDefault } from './print-type.js';\nimport { INDENT } from './indent.js';\n\nconst I1 = INDENT;\nconst I2 = INDENT.repeat(2);\nconst I3 = INDENT.repeat(3);\nconst I4 = INDENT.repeat(4);\n\n// ─── Orphan comment helpers ──────────────────────────────────────────────────\n\ntype CommentEntry = { line: number; text: string };\n/** A run of consecutive-line orphan comments, keyed by the source line it starts on. */\nexport type CommentBlock = { startLine: number; lines: string[] };\n\n/** Group sorted orphan comment entries into consecutive-line blocks. */\nexport function groupComments(entries: CommentEntry[]): CommentBlock[] {\n const blocks: CommentBlock[] = [];\n let current: CommentBlock | null = null;\n for (const { line, text } of entries) {\n if (current && line === current.startLine + current.lines.length) {\n current.lines.push(text);\n } else {\n if (current) blocks.push(current);\n current = { startLine: line, lines: [text] };\n }\n }\n if (current) blocks.push(current);\n return blocks;\n}\n\n/**\n * Emit any comment blocks whose startLine is < beforeLine.\n * Lines are emitted verbatim — they already carry their original indentation.\n */\nexport function flushBlocks(out: string[], blocks: CommentBlock[], idx: { value: number }, beforeLine: number, _indent = '') {\n while (idx.value < blocks.length && blocks[idx.value]!.startLine < beforeLine) {\n for (const l of blocks[idx.value]!.lines) out.push(l);\n idx.value++;\n }\n}\n\n// ─── Route ───────────────────────────────────────────────────────────────────\n\n/**\n * Render an `operation` route body from its `path: {` onward (the `operation` keyword, any\n * modifier, and the route's leading comments are prepended by the caller). Emits the\n * params/security blocks and each HTTP operation, interleaving orphan comment `blocks` at their\n * original source positions — `idx` tracks how far through `blocks` we've consumed, and\n * `nextRouteStart` bounds the flush to comments before the following route. Any\n * `route.trailingComments` (comments after the last operation, before `}`) are emitted before\n * the closing brace so they round-trip.\n *\n * Blank lines between operations come from each operation's `blankLineBefore`, so the author's\n * spacing survives rather than being normalized to one rule or the other.\n */\nexport function printRoute(route: OpRouteNode, blocks: CommentBlock[], idx: { value: number }, nextRouteStart: number): string {\n const lines: string[] = [];\n lines.push(`${route.path}: {`);\n\n if (route.params !== undefined) {\n lines.push(...printParamsBlock(route.params, I1, route.paramsMode));\n }\n\n if (route.security !== undefined) {\n lines.push(...printSecurity(route.security, I1, I2));\n }\n\n for (const op of route.operations) {\n // Reproduce the author's spacing rather than imposing our own.\n if (op.blankLineBefore && lines.length > 1) lines.push('');\n // Flush comment blocks that appear before this operation (inside the route)\n flushBlocks(lines, blocks, idx, op.loc.line, I1);\n lines.push(...printOperation(op));\n }\n\n // Flush comment blocks between last operation and the next route\n flushBlocks(lines, blocks, idx, nextRouteStart, I1);\n\n // Trailing/orphan comments after the last operation, before the closing brace.\n for (const comment of route.trailingComments ?? []) {\n lines.push(`${I1}# ${comment}`);\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n\n// ─── Params block ────────────────────────────────────────────────────────────\n\nfunction printParamsBlock(source: ParamSource, indent: string, mode?: ObjectMode): string[] {\n const prefix = mode ? `mode(${mode}) ` : '';\n if (source.kind === 'ref') {\n return [`${indent}${prefix}params: ${source.name}`];\n }\n if (source.kind === 'params') {\n const lines: string[] = [`${indent}${prefix}params: {`];\n const inner = indent + INDENT;\n for (const p of source.nodes) {\n const opt = p.optional ? '?' : '';\n let t = printType(p.type);\n if (p.nullable) t += ' | null';\n const def = p.default !== undefined ? ` = ${formatDefault(p.default)}` : '';\n const comment = p.description ? ` # ${p.description}` : '';\n lines.push(`${inner}${p.name}${opt}: ${t}${def}${comment}`);\n }\n lines.push(`${indent}}`);\n return lines;\n }\n // ContractTypeNode\n return [`${indent}${prefix}params: ${printType(source.node)}`];\n}\n\n// ─── HTTP operation ──────────────────────────────────────────────────────────\n\n/** Order the body keys are emitted in when the node carries no source order (built programmatically). */\nconst CANONICAL_KEY_ORDER: OpBodyKey[] = ['name', 'service', 'sdk', 'mcp', 'signature', 'security', 'plugins', 'query', 'headers', 'request', 'responses'];\n\n/** Render a single operation-body key. Returns `[]` when the operation doesn't carry that key. */\nfunction printOperationKey(op: OpOperationNode, key: OpBodyKey): string[] {\n switch (key) {\n case 'name':\n return op.name ? [`${I2}name: ${op.name}`] : [];\n case 'service':\n return op.service ? [`${I2}service: ${op.service}`] : [];\n case 'sdk':\n return op.sdk ? [`${I2}sdk: ${op.sdk}`] : [];\n case 'mcp':\n if (op.mcp === true) return [`${I2}mcp: true`];\n if (op.mcp === false) return [`${I2}mcp: false`];\n return op.mcp ? printMcpBlock(op.mcp) : [];\n case 'signature': {\n if (!op.signature) return [];\n const comment = op.signatureDescription ? ` # ${op.signatureDescription}` : '';\n if (op.signaturePolicy) {\n return [\n `${I2}signature: {`,\n `${I3}options: ${formatSignatureValue(op.signature)}${comment}`,\n `${I3}policy: ${op.signaturePolicy}`,\n `${I2}}`,\n ];\n }\n return [`${I2}signature: ${formatSignatureValue(op.signature)}${comment}`];\n }\n case 'security':\n return op.security !== undefined ? printSecurity(op.security) : [];\n case 'plugins': {\n if (!op.plugins || Object.keys(op.plugins).length === 0) return [];\n const lines = [`${I2}plugins: {`];\n for (const [k, val] of Object.entries(op.plugins)) lines.push(...printPluginEntry(k, val, I3));\n lines.push(`${I2}}`);\n return lines;\n }\n case 'query':\n return op.query !== undefined ? printQueryOrHeaders('query', op.query, op.queryMode) : [];\n case 'headers':\n if (op.requestHeadersOptOut) return [`${I2}headers: none`];\n return op.headers !== undefined ? printQueryOrHeaders('headers', op.headers, op.headersMode) : [];\n case 'request': {\n if (!op.request) return [];\n const lines = [`${I2}request: {`];\n for (const body of op.request.bodies) lines.push(...printContentTypeLine(body.contentType, body.bodyType, I3));\n lines.push(`${I2}}`);\n return lines;\n }\n case 'responses':\n return op.responses.length > 0 ? printResponseBlock(op.responses, op.responsesTrailingComments) : [];\n }\n}\n\nfunction printOperation(op: OpOperationNode): string[] {\n const lines: string[] = [];\n const modPart = op.modifiers?.length ? `(${op.modifiers[0]})` : '';\n\n // A doc comment written above the method line goes back above it; only an inline one is\n // re-emitted as a trailing `#` on the header. Nodes built programmatically carry no placement,\n // and default to inline — the form most `.ck` sources use and one that round-trips as written.\n const inlineDescription = op.descriptionInline ?? true;\n if (op.description && !inlineDescription) {\n for (const line of op.description.split('\\n')) lines.push(`${I1}# ${line}`);\n }\n const commentSuffix = op.description && inlineDescription ? ` # ${op.description}` : '';\n lines.push(`${I1}${op.method}${modPart}: {${commentSuffix}`);\n\n // Emit in source order when the parser recorded it, so formatting never reorders a user's keys.\n // Any key the source order doesn't mention (e.g. added by a later AST pass) follows in canonical order.\n const order = op.keyOrder ?? [];\n const rest = CANONICAL_KEY_ORDER.filter(k => !order.includes(k));\n for (const key of [...order, ...rest]) {\n lines.push(...printOperationKey(op, key));\n }\n\n lines.push(`${I1}}`);\n return lines;\n}\n\n// ─── MCP block ───────────────────────────────────────────────────────────────\n\n/**\n * Reconstruct the `hint:` token list from the four annotation booleans, in canonical\n * order. Each set boolean contributes its positive or negative token; unset hints are omitted.\n */\nfunction mcpHintTokens(mcp: McpConfigNode): string[] {\n const tokens: string[] = [];\n if (mcp.readOnlyHint !== undefined) tokens.push(mcp.readOnlyHint ? 'readOnly' : 'nonReadOnly');\n if (mcp.idempotentHint !== undefined) tokens.push(mcp.idempotentHint ? 'idempotent' : 'nonIdempotent');\n if (mcp.destructiveHint !== undefined) tokens.push(mcp.destructiveHint ? 'destructive' : 'nonDestructive');\n if (mcp.openWorldHint !== undefined) tokens.push(mcp.openWorldHint ? 'openWorld' : 'closedWorld');\n return tokens;\n}\n\n/** Print an `mcp: { ... }` settings block. Fields are emitted in canonical order; `hint:` is omitted when no hints are set. */\nfunction printMcpBlock(mcp: McpConfigNode): string[] {\n const lines: string[] = [`${I2}mcp: {`];\n if (mcp.name !== undefined) lines.push(`${I3}name: \"${escapeString(mcp.name)}\"`);\n if (mcp.title !== undefined) lines.push(`${I3}title: \"${escapeString(mcp.title)}\"`);\n if (mcp.description !== undefined) lines.push(`${I3}description: \"${escapeString(mcp.description)}\"`);\n const tokens = mcpHintTokens(mcp);\n if (tokens.length > 0) lines.push(`${I3}hint: ${tokens.join(', ')}`);\n lines.push(`${I2}}`);\n return lines;\n}\n\n// ─── Plugins block ───────────────────────────────────────────────────────────\n\nconst IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;\n\nfunction escapeString(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\nfunction printPluginEntry(key: string, value: PluginValue, indent: string): string[] {\n const lines: string[] = [];\n const inline = printPluginInline(value);\n if (inline !== null) {\n lines.push(`${indent}${key}: ${inline}`);\n } else {\n const head = `${indent}${key}: `;\n const block = printPluginBlock(value, indent);\n lines.push(`${head}${block[0]!.trimStart()}`);\n for (let i = 1; i < block.length; i++) lines.push(block[i]!);\n }\n return lines;\n}\n\nfunction printPluginInline(value: PluginValue): string | null {\n if (typeof value === 'string') return `\"${escapeString(value)}\"`;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (value === null) return 'null';\n if (Array.isArray(value) && value.length === 0) return '[]';\n if (!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length === 0) return '{}';\n return null;\n}\n\nfunction printPluginBlock(value: PluginValue, indent: string): string[] {\n const inner = indent + INDENT;\n const lines: string[] = [];\n if (Array.isArray(value)) {\n lines.push(`${indent}[`);\n for (const item of value) {\n const inline = printPluginInline(item);\n if (inline !== null) {\n lines.push(`${inner}${inline}`);\n } else {\n const block = printPluginBlock(item, inner);\n for (const l of block) lines.push(l);\n }\n }\n lines.push(`${indent}]`);\n return lines;\n }\n if (typeof value === 'object' && value !== null) {\n lines.push(`${indent}{`);\n for (const [k, v] of Object.entries(value)) {\n const fieldKey = IDENT_RE.test(k) ? k : `\"${escapeString(k)}\"`;\n const inline = printPluginInline(v);\n if (inline !== null) {\n lines.push(`${inner}${fieldKey}: ${inline}`);\n } else {\n const block = printPluginBlock(v, inner);\n lines.push(`${inner}${fieldKey}: ${block[0]!.trimStart()}`);\n for (let i = 1; i < block.length; i++) lines.push(block[i]!);\n }\n }\n lines.push(`${indent}}`);\n return lines;\n }\n // Scalars are always inline; printPluginInline already handles them.\n return [`${indent}${printPluginInline(value)}`];\n}\n\n// ─── Security ────────────────────────────────────────────────────────────────\n\n/** Print a signature key: unquoted when it's a plain identifier, quoted otherwise. */\nfunction formatSignatureValue(value: string): string {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value) ? value : `\"${value}\"`;\n}\n\n/**\n * Print a `security:` declaration. Returns `[\"${indent}security: none\"]` for the public-endpoint\n * sentinel, a multi-line block when `policy` is set, or an empty array when there's nothing\n * meaningful to emit.\n *\n * @param indent indentation for the `security` keyword line\n * @param innerIndent indentation for field lines inside the block\n */\nexport function printSecurity(security: SecurityNode, indent = I2, innerIndent = I3): string[] {\n if (security === SECURITY_NONE) return [`${indent}security: none`];\n const fields = security as SecurityFields;\n if (fields.policy === undefined) return [];\n const lines = [`${indent}security: {`];\n const comment = fields.policyDescription ? ` # ${fields.policyDescription}` : '';\n const value = fields.policy === false ? 'none' : fields.policy;\n lines.push(`${innerIndent}policy: ${value}${comment}`);\n lines.push(`${indent}}`);\n return lines;\n}\n\n// ─── Query / headers ─────────────────────────────────────────────────────────\n\nfunction printQueryOrHeaders(keyword: 'query' | 'headers', source: ParamSource, mode?: ObjectMode): string[] {\n const prefix = mode ? `mode(${mode}) ` : '';\n if (source.kind === 'ref') {\n return [`${I2}${prefix}${keyword}: ${source.name}`];\n }\n if (source.kind === 'params') {\n if (source.nodes.length === 0) return [];\n const lines: string[] = [`${I2}${prefix}${keyword}: {`];\n for (const p of source.nodes) {\n const opt = p.optional ? '?' : '';\n let t = printType(p.type);\n if (p.nullable) t += ' | null';\n const def = p.default !== undefined ? ` = ${formatDefault(p.default)}` : '';\n const comment = p.description ? ` # ${p.description}` : '';\n lines.push(`${I3}${p.name}${opt}: ${t}${def}${comment}`);\n }\n lines.push(`${I2}}`);\n return lines;\n }\n // ContractTypeNode (e.g. intersection)\n return [`${I2}${prefix}${keyword}: ${printType(source.node)}`];\n}\n\n// ─── Content-type line ───────────────────────────────────────────────────────\n\n/** Print a `contentType: bodyType` line, expanding inline brace objects onto separate lines. */\nfunction printContentTypeLine(contentType: string, bodyType: ContractTypeNode, lineIndent: string): string[] {\n if (bodyType.kind === 'inlineObject') {\n const fieldIndent = lineIndent + INDENT;\n const lines: string[] = [`${lineIndent}${contentType}: {`];\n for (const f of bodyType.fields) {\n const opt = f.optional ? '?' : '';\n let t = printType(f.type);\n if (f.nullable) t += ' | null';\n const def = f.default !== undefined ? ` = ${formatDefault(f.default)}` : '';\n const comment = f.description ? ` # ${f.description}` : '';\n lines.push(`${fieldIndent}${f.name}${opt}: ${t}${def}${comment}`);\n }\n lines.push(`${lineIndent}}`);\n return lines;\n }\n return [`${lineIndent}${contentType}: ${printType(bodyType)}`];\n}\n\n// ─── Response block ──────────────────────────────────────────────────────────\n\nfunction printResponseBlock(responses: OpResponseNode[], trailingComments?: string[]): string[] {\n const lines: string[] = [`${I2}response: {`];\n\n for (const resp of responses) {\n for (const comment of resp.leadingComments ?? []) lines.push(`${I3}# ${comment}`);\n const bodies = resp.bodies;\n const hasHeaders = resp.headers && resp.headers.length > 0;\n const optOut = resp.headersOptOut;\n // `404(documented):` — the modifier changes what codegen does, so it has to survive.\n const code = resp.emit ? `${resp.statusCode}(${resp.emit})` : `${resp.statusCode}`;\n const inlinable = resp.inline && bodies.length > 0 && !hasHeaders && !optOut && bodies.every(b => b.bodyType.kind !== 'inlineObject');\n if (inlinable) {\n // Written on one line in the source, so keep it there: `200: { application/json: Pet }`.\n // Several mimes on that line stay on it too, space-separated as the grammar has them.\n const inner = bodies.map(b => `${b.contentType}: ${printType(b.bodyType)}`).join(' ');\n lines.push(`${I3}${code}: { ${inner} }`);\n } else if (bodies.length === 0 && !hasHeaders && !optOut && resp.hasBlock) {\n // An empty block means \"emitted, no body\" — collapsing it to `304:` would change\n // the generated router, so it is not a formatting detail.\n lines.push(`${I3}${code}: {}`);\n } else if (bodies.length > 0 || hasHeaders || optOut || (resp.trailingComments?.length ?? 0) > 0) {\n lines.push(`${I3}${code}: {`);\n for (const body of bodies) {\n for (const comment of body.leadingComments ?? []) lines.push(`${I4}# ${comment}`);\n lines.push(...printContentTypeLine(body.contentType, body.bodyType, I4));\n }\n for (const comment of resp.headersLeadingComments ?? []) lines.push(`${I4}# ${comment}`);\n if (optOut) {\n lines.push(`${I4}headers: none`);\n } else if (hasHeaders) {\n lines.push(`${I4}headers: {`);\n for (const h of resp.headers!) {\n const opt = h.optional ? '?' : '';\n const trail = h.description ? ` # ${h.description}` : '';\n lines.push(`${I4}${INDENT}${h.name}${opt}: ${printType(h.type)}${trail}`);\n }\n lines.push(`${I4}}`);\n }\n for (const comment of resp.trailingComments ?? []) lines.push(`${I4}# ${comment}`);\n lines.push(`${I3}}`);\n } else {\n lines.push(`${I3}${code}:`);\n }\n }\n\n for (const comment of trailingComments ?? []) lines.push(`${I3}# ${comment}`);\n lines.push(`${I2}}`);\n return lines;\n}\n","import type { CkRootNode, OpResponseHeaderNode, OptionsScopeComments } from '@contractkit/core';\nimport { printModelDecl } from './print-contract.js';\nimport { printRoute, printSecurity, type CommentBlock } from './print-operation.js';\nimport { printType } from './print-type.js';\nimport { INDENT } from './indent.js';\n\n/** Default line width used for wrapping inline-object and enum types when none is supplied. */\nexport const DEFAULT_PRINT_WIDTH = 80;\n\n// ─── Options block ──────────────────────────────────────────────────────────\n\n/**\n * Render an options value in the form the author wrote it.\n *\n * `wasUnquoted` comes from the parser, so an unquoted subpath import\n * (`PetService: #modules/pet/pet.service.js`) stays unquoted instead of being normalized. The\n * conditions still gate it: a value the grammar could not read back bare — one containing a\n * newline, a `}`, whitespace-then-`#`, or leading/trailing space — is quoted regardless, which\n * matters for values built programmatically rather than parsed.\n */\nfunction quoteOptionsValue(value: string, wasUnquoted = false): string {\n if (/^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(value)) return value;\n const roundTripsBare = value.length > 0 && value === value.trim() && !/[\\n\\r}]/.test(value) && !/[ \\t]#/.test(value) && !/^[\"']/.test(value);\n return wasUnquoted && roundTripsBare ? value : `\"${value}\"`;\n}\n\n/**\n * Emit the `key: value` lines of an options `keys`/`services` sub-block, interleaving any\n * retained comments: leading comments before the entry they precede, and trailing comments\n * after the last entry (before the closing `}`). Keeps comments inside these sub-blocks lossless.\n */\nfunction emitOptionsEntries(\n lines: string[],\n entries: Record<string, string>,\n comments: OptionsScopeComments | undefined,\n unquoted: string[] | undefined,\n): void {\n const I2 = INDENT + INDENT;\n const wasUnquoted = new Set(unquoted ?? []);\n for (const [key, value] of Object.entries(entries)) {\n for (const c of comments?.leading?.[key] ?? []) lines.push(`${I2}# ${c}`);\n const inline = comments?.inline?.[key];\n lines.push(`${I2}${key}: ${quoteOptionsValue(value, wasUnquoted.has(key))}${inline !== undefined ? ` # ${inline}` : ''}`);\n }\n for (const c of comments?.trailing ?? []) lines.push(`${I2}# ${c}`);\n}\n\nfunction printOptionsBlock(ast: CkRootNode): string | null {\n const hasMeta = Object.keys(ast.meta).length > 0;\n const hasServices = Object.keys(ast.services).length > 0;\n const hasSecurity = ast.security !== undefined;\n const hasRequestHeaders = (ast.requestHeaders?.length ?? 0) > 0;\n const hasResponseHeaders = (ast.responseHeaders?.length ?? 0) > 0;\n\n const hasBodyComments = ast.optionsComments?.body !== undefined;\n if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders && !hasBodyComments) return null;\n\n // A `#` run above the `options` keyword is the file's header comment.\n const lines: string[] = [...(ast.optionsComments?.leading ?? []).map(c => `# ${c}`), 'options {'];\n const body = ast.optionsComments?.body;\n /** Emit the comment run the author wrote directly above this sub-block. */\n const emitLeading = (scope: string) => {\n for (const c of body?.leading?.[scope] ?? []) lines.push(`${INDENT}# ${c}`);\n };\n\n if (hasMeta) {\n emitLeading('keys');\n lines.push(`${INDENT}keys: {`);\n emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys, ast.optionsUnquoted?.keys);\n lines.push(`${INDENT}}`);\n }\n\n if (hasServices) {\n emitLeading('services');\n lines.push(`${INDENT}services: {`);\n emitOptionsEntries(lines, ast.services, ast.optionsComments?.services, ast.optionsUnquoted?.services);\n lines.push(`${INDENT}}`);\n }\n\n if (hasRequestHeaders) {\n emitLeading('request');\n lines.push(...printOptionsHeaderScope('request', ast.requestHeaders!));\n }\n\n if (hasResponseHeaders) {\n emitLeading('response');\n lines.push(...printOptionsHeaderScope('response', ast.responseHeaders!));\n }\n\n if (hasSecurity) {\n emitLeading('security');\n lines.push(...printSecurity(ast.security!, INDENT, INDENT + INDENT));\n }\n\n for (const c of body?.trailing ?? []) lines.push(`${INDENT}# ${c}`);\n\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction printOptionsHeaderScope(keyword: 'request' | 'response', headers: OpResponseHeaderNode[]): string[] {\n const I2 = INDENT + INDENT;\n const I3 = INDENT + INDENT + INDENT;\n const lines = [`${INDENT}${keyword}: {`, `${I2}headers: {`];\n for (const h of headers) {\n const opt = h.optional ? '?' : '';\n const trail = h.description ? ` # ${h.description}` : '';\n lines.push(`${I3}${h.name}${opt}: ${printType(h.type)}${trail}`);\n }\n lines.push(`${I2}}`);\n lines.push(`${INDENT}}`);\n return lines;\n}\n\n// ─── CK file printer ───────────────────────────────────────────────────────\n\n/**\n * Render a parsed `.ck` AST back to source: options block first, then contracts, then\n * operations, separated by blank lines.\n *\n * Printing is the inverse of parsing — for well-formed source, `printCk(parseCk(text))` returns\n * `text` unchanged. That holds because the AST carries the author's layout alongside the\n * semantics: comment placement (`leadingComments`, `descriptionInline`), operation body key\n * order (`keyOrder`), blank lines (`blankLineBefore`), and single-line response blocks\n * (`inline`). Preserve those rather than canonicalizing them, or `pnpm format` silently\n * rewrites the user's file. See `tests/round-trip.test.ts`.\n *\n * `printWidth` is forwarded to per-model printing for line wrapping inside\n * inline-object types.\n */\nexport function printCk(ast: CkRootNode, printWidth: number = DEFAULT_PRINT_WIDTH): string {\n const parts: string[] = [];\n\n // Options block\n const options = printOptionsBlock(ast);\n if (options) parts.push(options);\n\n // Contracts (models)\n for (const model of ast.models) {\n if (parts.length > 0) parts.push('');\n parts.push(printDeclLeadIn(model.leadingComments, model.descriptionInline ? undefined : model.description) + `contract ${printModelDecl(model, printWidth)}`);\n }\n\n // Operations (routes)\n const emptyBlocks: CommentBlock[] = [];\n const emptyIdx = { value: 0 };\n for (const route of ast.routes) {\n if (parts.length > 0) parts.push('');\n const modPart = route.modifiers?.length ? `(${route.modifiers[0]})` : '';\n parts.push(printDeclLeadIn(route.leadingComments, route.description) + `operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);\n }\n\n return parts.join('\\n') + '\\n';\n}\n\n/**\n * Build the comment lines that precede a top-level declaration: any standalone block (a section\n * divider and the like, kept separated by the blank line the author wrote), then the declaration's\n * own doc comment on the lines immediately above it. Returns `''` when there is neither.\n */\nfunction printDeclLeadIn(leadingComments: string[] | undefined, description: string | undefined): string {\n const lines: string[] = [];\n if (leadingComments?.length) {\n for (const c of leadingComments) lines.push(`# ${c}`);\n lines.push('');\n }\n if (description) {\n for (const line of description.split('\\n')) lines.push(`# ${line}`);\n }\n return lines.length > 0 ? lines.join('\\n') + '\\n' : '';\n}\n"],"mappings":"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","names":["builders","parseCk","DiagnosticCollector","INDENT","printType","type","kind","constraints","format","undefined","fmt","push","test","min","max","len","regex","length","name","join","args","item","items","map","key","value","values","formatEnumValue","members","discriminator","printInlineObjectCompact","inner","obj","prefix","mode","fields","parts","f","opt","optional","t","nullable","printEnumExpanded","indent","innerIndent","INDENT","v","printField","field","printWidth","ovr","override","dep","deprecated","vis","visibility","mods","def","default","formatDefault","comment","description","trailing","extractTrailingInlineObject","inlineObj","modePart","header","printInlineObjectExpanded","typeStr","fullLine","enumStr","lines","trailingComments","val","String","last","prefixStr","slice","printModelDecl","model","printWidth","type","undefined","printTypeAlias","commentSuffix","description","descriptionInline","modifiers","deprecated","inputCase","outputCase","filter","Boolean","join","mode","modePrefix","baseChain","bases","length","header","name","lines","field","fields","push","printField","INDENT","comment","trailingComments","trailing","extractTrailingInlineObject","prefix","inlineObj","modePart","printInlineObjectExpanded","singleLine","printType","kind","printEnumExpanded","values","SECURITY_NONE","I1","INDENT","I2","repeat","I3","I4","flushBlocks","out","blocks","idx","beforeLine","_indent","value","length","startLine","l","lines","push","printRoute","route","nextRouteStart","path","params","undefined","printParamsBlock","I1","paramsMode","security","printSecurity","I2","op","operations","blankLineBefore","loc","line","printOperation","comment","trailingComments","join","source","indent","mode","prefix","kind","name","inner","INDENT","p","nodes","opt","optional","t","printType","type","nullable","def","default","formatDefault","description","node","CANONICAL_KEY_ORDER","printOperationKey","key","service","sdk","mcp","printMcpBlock","signature","signatureDescription","signaturePolicy","I3","formatSignatureValue","plugins","Object","keys","k","val","entries","printPluginEntry","query","printQueryOrHeaders","queryMode","requestHeadersOptOut","headers","headersMode","request","body","bodies","printContentTypeLine","contentType","bodyType","responses","printResponseBlock","responsesTrailingComments","modPart","modifiers","inlineDescription","descriptionInline","split","commentSuffix","method","order","keyOrder","rest","filter","includes","mcpHintTokens","tokens","readOnlyHint","idempotentHint","destructiveHint","openWorldHint","escapeString","title","IDENT_RE","s","replace","inline","printPluginInline","head","block","printPluginBlock","trimStart","i","String","Array","isArray","item","v","fieldKey","test","innerIndent","SECURITY_NONE","fields","policy","policyDescription","keyword","lineIndent","fieldIndent","f","resp","leadingComments","hasHeaders","optOut","headersOptOut","code","emit","statusCode","inlinable","every","b","map","hasBlock","I4","headersLeadingComments","h","trail","DEFAULT_PRINT_WIDTH","quoteOptionsValue","value","wasUnquoted","test","roundTripsBare","length","trim","emitOptionsEntries","lines","entries","comments","unquoted","I2","INDENT","Set","key","Object","c","leading","push","inline","has","undefined","trailing","printOptionsBlock","ast","hasMeta","keys","meta","hasServices","services","hasSecurity","security","hasRequestHeaders","requestHeaders","hasResponseHeaders","responseHeaders","hasBodyComments","optionsComments","body","map","emitLeading","scope","optionsUnquoted","printOptionsHeaderScope","printSecurity","join","keyword","headers","I3","h","opt","optional","trail","description","name","printType","type","printCk","printWidth","parts","options","model","models","printDeclLeadIn","leadingComments","descriptionInline","printModelDecl","emptyBlocks","emptyIdx","route","routes","modPart","modifiers","printRoute","Infinity","line","split","hardline","join","builders","toDoc","text","lines","trimEnd","split","plugin","languages","name","parsers","extensions","vscodeLanguageIds","parse","_options","diag","DiagnosticCollector","parseCk","astFormat","locStart","locEnd","_node","printers","print","path","options","node","printCk","printWidth"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/indent.ts","../src/print-type.ts","../src/print-contract.ts","../src/print-operation.ts","../src/print-ck.ts"],"sourcesContent":["import type { Plugin } from 'prettier';\nimport { builders } from 'prettier/doc';\nimport { parseCk, DiagnosticCollector } from '@contractkit/core';\nimport type { CkRootNode } from '@contractkit/core';\nimport { printCk } from './print-ck.js';\n\nconst { hardline, join } = builders;\n\n/**\n * Wrap printed source as a prettier doc.\n *\n * `printCk` already ends the file with a newline, but splitting on `\\n` turns that into a\n * trailing empty segment, so the text is trimmed first and the final newline re-added as an\n * explicit `hardline`. Dropping it would leave the file without a terminating newline, which\n * fights every editor and lint rule that wants one.\n */\nfunction toDoc(text: string) {\n const lines = text.trimEnd().split('\\n');\n return [join(hardline, lines), hardline];\n}\n\n/**\n * Prettier plugin registering the `.ck` language, its parser, and its printer.\n *\n * The parser is `parseCk` and the printer is `printCk`, so formatting behaviour lives in\n * `@contractkit/core` and `print-ck.ts` rather than here — this module only adapts them to\n * prettier's interface. `tests/format-plugin.test.ts` covers that adapter layer, which a\n * printer-level test cannot reach.\n */\nconst plugin: Plugin<CkRootNode> = {\n languages: [\n {\n name: 'ContractDSL',\n parsers: ['contract-ck'],\n extensions: ['.ck'],\n vscodeLanguageIds: ['contract-ck'],\n },\n ],\n\n parsers: {\n 'contract-ck': {\n parse(text, _options) {\n const diag = new DiagnosticCollector();\n return parseCk(text, '<stdin>', diag);\n },\n astFormat: 'contract-ck',\n locStart: () => 0,\n locEnd: _node => 0,\n },\n },\n\n printers: {\n 'contract-ck': {\n print(path, options) {\n const node = path.node as CkRootNode;\n return toDoc(printCk(node, options.printWidth));\n },\n },\n },\n};\n\nexport default plugin;\nexport { printCk, DEFAULT_PRINT_WIDTH } from './print-ck.js';\n","export const INDENT = ' ';\n","import type { ContractTypeNode, FieldNode, InlineObjectTypeNode } from '@contractkit/core';\nimport { INDENT } from './indent.js';\n\n// ─── Type expression printer ────────────────────────────────────────────────\n\n/** Render a `ContractTypeNode` back to its `.ck` source string. */\nexport function printType(type: ContractTypeNode): string {\n switch (type.kind) {\n case 'scalar': {\n const constraints: string[] = [];\n if (type.format !== undefined) {\n // Print unquoted when format contains only safe chars; quote otherwise\n const fmt = type.format;\n constraints.push(/^[a-zA-Z0-9\\-.:/]+$/.test(fmt) ? fmt : `\"${fmt}\"`);\n }\n if (type.min !== undefined) constraints.push(`min=${type.min}`);\n if (type.max !== undefined) constraints.push(`max=${type.max}`);\n if (type.len !== undefined) constraints.push(`len=${type.len}`);\n if (type.regex !== undefined) constraints.push(`regex=/${type.regex}/`);\n return constraints.length > 0 ? `${type.name}(${constraints.join(', ')})` : type.name;\n }\n case 'array': {\n const args: string[] = [printType(type.item)];\n if (type.min !== undefined) args.push(`min=${type.min}`);\n if (type.max !== undefined) args.push(`max=${type.max}`);\n return `array(${args.join(', ')})`;\n }\n case 'tuple':\n return `tuple(${type.items.map(printType).join(', ')})`;\n case 'record':\n return `record(${printType(type.key)}, ${printType(type.value)})`;\n case 'enum':\n return `enum(${type.values.map(formatEnumValue).join(', ')})`;\n case 'literal':\n return typeof type.value === 'string' ? `literal(\"${type.value}\")` : `literal(${type.value})`;\n case 'union':\n return type.members.map(printType).join(' | ');\n case 'discriminatedUnion':\n return `discriminated(by=${type.discriminator}, ${type.members.map(printType).join(' | ')})`;\n case 'intersection':\n return type.members.map(printType).join(' & ');\n case 'ref':\n return type.name;\n case 'inlineObject':\n return printInlineObjectCompact(type);\n case 'lazy':\n return `lazy(${printType(type.inner)})`;\n }\n}\n\n/** Compact single-line form — used when inline object appears nested inside another type. */\nfunction printInlineObjectCompact(obj: InlineObjectTypeNode): string {\n const prefix = obj.mode ? `mode(${obj.mode}) ` : '';\n if (obj.fields.length === 0) return `${prefix}{}`;\n const parts = obj.fields.map(f => {\n const opt = f.optional ? '?' : '';\n let t = printType(f.type);\n if (f.nullable) t += ' | null';\n return `${f.name}${opt}: ${t}`;\n });\n return `${prefix}{ ${parts.join(', ')} }`;\n}\n\n/** Multi-line enum form — one value per line, used when single-line would exceed print width. */\nexport function printEnumExpanded(values: string[], indent: string): string {\n const innerIndent = indent + INDENT;\n return `enum(\\n${values.map(v => `${innerIndent}${formatEnumValue(v)}`).join(',\\n')}\\n${indent})`;\n}\n\n// ─── Field printer ──────────────────────────────────────────────────────────\n\n/** Print a full field declaration, including visibility, default, and inline comment.\n * Modifier order is canonical: override → deprecated → readonly|writeonly → type. */\nexport function printField(field: FieldNode, indent: string, printWidth: number = 80): string {\n const opt = field.optional ? '?' : '';\n const ovr = field.override ? 'override ' : '';\n const dep = field.deprecated ? 'deprecated ' : '';\n const vis = field.visibility !== 'normal' ? `${field.visibility} ` : '';\n const mods = `${ovr}${dep}${vis}`;\n const def = field.default !== undefined ? ` = ${formatDefault(field.default)}` : '';\n const comment = field.description ? ` # ${field.description}` : '';\n const innerIndent = indent + INDENT;\n\n // Expand inline object types to multi-line — same rule as type aliases.\n // Only when there's no default and no nullable union (those can't split cleanly).\n if (!field.nullable && field.default === undefined) {\n const trailing = extractTrailingInlineObject(field.type);\n if (trailing) {\n const { prefix, inlineObj } = trailing;\n const modePart = inlineObj.mode ? `mode(${inlineObj.mode}) ` : '';\n const header = prefix\n ? `${indent}${field.name}${opt}: ${mods}${prefix} & ${modePart}{${comment}`\n : `${indent}${field.name}${opt}: ${mods}${modePart}{${comment}`;\n return [header, ...printInlineObjectExpanded(inlineObj, innerIndent, printWidth), `${indent}}`].join('\\n');\n }\n }\n\n let typeStr = printType(field.type);\n if (field.nullable) typeStr += ' | null';\n const fullLine = `${indent}${field.name}${opt}: ${mods}${typeStr}${def}${comment}`;\n if (field.type.kind === 'enum' && !field.nullable && field.default === undefined && fullLine.length > printWidth) {\n const enumStr = printEnumExpanded(field.type.values, indent);\n return `${indent}${field.name}${opt}: ${mods}${enumStr}${comment}`;\n }\n return fullLine;\n}\n\n/** Print inline-object fields expanded (used when an inline brace object trails a type alias).\n * Any `trailingComments` (comments after the last field, before `}`) are emitted as indented\n * `# text` lines after the fields, matching how model bodies round-trip trailing comments. */\nexport function printInlineObjectExpanded(obj: InlineObjectTypeNode, indent: string, printWidth: number = 80): string[] {\n const lines = obj.fields.map(f => printField(f, indent, printWidth));\n for (const comment of obj.trailingComments ?? []) {\n lines.push(`${indent}# ${comment}`);\n }\n return lines;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\n/** Format a single enum value: bare identifier stays bare; anything else gets double-quoted. */\nexport function formatEnumValue(v: string): string {\n if (/^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(v)) return v;\n return `\"${v}\"`;\n}\n\n/** Format a default value: quote strings that aren't valid bare identifiers. */\nexport function formatDefault(val: string | number | boolean): string {\n if (typeof val === 'number' || typeof val === 'boolean') return String(val);\n // If it looks like a bare identifier (enum value, unquoted token), keep it bare.\n if (/^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(val)) return val;\n return `\"${val}\"`;\n}\n\n/**\n * Detect whether the last member of a type is an inline brace object, and if so\n * return the prefix type string and the inline object for expanded printing.\n * Returns null if the type doesn't end with an inline object.\n */\nexport function extractTrailingInlineObject(type: ContractTypeNode): {\n prefix: string | null;\n inlineObj: InlineObjectTypeNode;\n} | null {\n if (type.kind === 'inlineObject') {\n return { prefix: null, inlineObj: type };\n }\n if (type.kind === 'intersection') {\n const last = type.members[type.members.length - 1];\n if (last?.kind === 'inlineObject') {\n const prefixStr = type.members.slice(0, -1).map(printType).join(' & ');\n return { prefix: prefixStr, inlineObj: last };\n }\n }\n return null;\n}\n","import type { ModelNode } from '@contractkit/core';\nimport { printField, printInlineObjectExpanded, extractTrailingInlineObject, printType, printEnumExpanded } from './print-type.js';\nimport { INDENT } from './indent.js';\n\n// ─── Model declaration ───────────────────────────────────────────────────────\n\n/**\n * Render a `contract` body from its `Name: ...` onward (the `contract ` keyword and any\n * doc comment written above the declaration are prepended by the caller). Handles both the\n * type-alias form (`Name: <type>`) and the regular field-block form, including modifiers,\n * base chain, and per-field printing. Any `model.trailingComments` (comments after the last\n * field, before `}`) are emitted as indented `# text` lines so they round-trip.\n *\n * `model.description` is emitted here only when `model.descriptionInline` says the author wrote\n * it on the header line; otherwise it belongs above the declaration and the caller emits it.\n */\nexport function printModelDecl(model: ModelNode, printWidth: number = 80): string {\n // Type alias form: Name : typeExpression\n if (model.type !== undefined) {\n return printTypeAlias(model, printWidth);\n }\n\n // Regular model with fields (possibly inherited)\n // A doc comment written above the declaration is re-emitted there by the caller; only an\n // inline one belongs on the header line.\n const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : '';\n const modifiers = [\n model.deprecated ? 'deprecated' : '',\n model.inputCase || model.outputCase\n ? `format(${[model.inputCase ? `input=${model.inputCase}` : '', model.outputCase ? `output=${model.outputCase}` : ''].filter(Boolean).join(', ')})`\n : '',\n model.mode ? `mode(${model.mode})` : '',\n ]\n .filter(Boolean)\n .join(' ');\n const modePrefix = modifiers ? `${modifiers} ` : '';\n const baseChain = model.bases && model.bases.length > 0 ? `${model.bases.join(' & ')} & ` : '';\n const header = `${modePrefix}${model.name}: ${baseChain}{${commentSuffix}`;\n\n const lines: string[] = [header];\n for (const field of model.fields) {\n lines.push(printField(field, INDENT, printWidth));\n }\n for (const comment of model.trailingComments ?? []) {\n lines.push(`${INDENT}# ${comment}`);\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction printTypeAlias(model: ModelNode, printWidth: number): string {\n const type = model.type!;\n // A doc comment written above the declaration is re-emitted there by the caller; only an\n // inline one belongs on the header line.\n const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : '';\n const modifiers = [\n model.deprecated ? 'deprecated' : '',\n model.inputCase || model.outputCase\n ? `format(${[model.inputCase ? `input=${model.inputCase}` : '', model.outputCase ? `output=${model.outputCase}` : ''].filter(Boolean).join(', ')})`\n : '',\n model.mode ? `mode(${model.mode})` : '',\n ]\n .filter(Boolean)\n .join(' ');\n const modePrefix = modifiers ? `${modifiers} ` : '';\n\n // If the type ends with an inline brace object, expand it as a pseudo-model block.\n const trailing = extractTrailingInlineObject(type);\n if (trailing) {\n const { prefix, inlineObj } = trailing;\n const modePart = inlineObj.mode ? `mode(${inlineObj.mode}) ` : '';\n const header = prefix\n ? `${modePrefix}${model.name}: ${prefix} & ${modePart}{${commentSuffix}`\n : `${modePrefix}${model.name}: ${modePart}{${commentSuffix}`;\n const lines: string[] = [header, ...printInlineObjectExpanded(inlineObj, INDENT, printWidth), '}'];\n return lines.join('\\n');\n }\n\n // Simple type alias — single line, unless it's a long enum.\n // Note: the contract prefix \"contract \" (9 chars) is prepended by the caller.\n const singleLine = `${modePrefix}${model.name}: ${printType(type)}${commentSuffix}`;\n if (type.kind === 'enum' && 'contract '.length + singleLine.length > printWidth) {\n return `${modePrefix}${model.name}: ${printEnumExpanded(type.values, '')}${commentSuffix}`;\n }\n return singleLine;\n}\n","import type {\n OpRouteNode,\n OpOperationNode,\n OpResponseNode,\n ParamSource,\n SecurityNode,\n SecurityFields,\n ContractTypeNode,\n ObjectMode,\n PluginValue,\n McpConfigNode,\n OpBodyKey,\n} from '@contractkit/core';\nimport { SECURITY_NONE } from '@contractkit/core';\nimport { printType, formatDefault } from './print-type.js';\nimport { INDENT } from './indent.js';\n\nconst I1 = INDENT;\nconst I2 = INDENT.repeat(2);\nconst I3 = INDENT.repeat(3);\nconst I4 = INDENT.repeat(4);\n\n// ─── Orphan comment helpers ──────────────────────────────────────────────────\n\ntype CommentEntry = { line: number; text: string };\n/** A run of consecutive-line orphan comments, keyed by the source line it starts on. */\nexport type CommentBlock = { startLine: number; lines: string[] };\n\n/** Group sorted orphan comment entries into consecutive-line blocks. */\nexport function groupComments(entries: CommentEntry[]): CommentBlock[] {\n const blocks: CommentBlock[] = [];\n let current: CommentBlock | null = null;\n for (const { line, text } of entries) {\n if (current && line === current.startLine + current.lines.length) {\n current.lines.push(text);\n } else {\n if (current) blocks.push(current);\n current = { startLine: line, lines: [text] };\n }\n }\n if (current) blocks.push(current);\n return blocks;\n}\n\n/**\n * Emit any comment blocks whose startLine is < beforeLine.\n * Lines are emitted verbatim — they already carry their original indentation.\n */\nexport function flushBlocks(out: string[], blocks: CommentBlock[], idx: { value: number }, beforeLine: number, _indent = '') {\n while (idx.value < blocks.length && blocks[idx.value]!.startLine < beforeLine) {\n for (const l of blocks[idx.value]!.lines) out.push(l);\n idx.value++;\n }\n}\n\n// ─── Route ───────────────────────────────────────────────────────────────────\n\n/**\n * Render an `operation` route body from its `path: {` onward (the `operation` keyword, any\n * modifier, and the route's leading comments are prepended by the caller). Emits the\n * params/security blocks and each HTTP operation, interleaving orphan comment `blocks` at their\n * original source positions — `idx` tracks how far through `blocks` we've consumed, and\n * `nextRouteStart` bounds the flush to comments before the following route. Any\n * `route.trailingComments` (comments after the last operation, before `}`) are emitted before\n * the closing brace so they round-trip.\n *\n * Blank lines between operations come from each operation's `blankLineBefore`, so the author's\n * spacing survives rather than being normalized to one rule or the other.\n */\nexport function printRoute(route: OpRouteNode, blocks: CommentBlock[], idx: { value: number }, nextRouteStart: number): string {\n const lines: string[] = [];\n lines.push(`${route.path}: {`);\n\n if (route.params !== undefined) {\n lines.push(...printParamsBlock(route.params, I1, route.paramsMode));\n }\n\n if (route.security !== undefined) {\n lines.push(...printSecurity(route.security, I1, I2));\n }\n\n for (const op of route.operations) {\n // Reproduce the author's spacing rather than imposing our own.\n if (op.blankLineBefore && lines.length > 1) lines.push('');\n // Flush comment blocks that appear before this operation (inside the route)\n flushBlocks(lines, blocks, idx, op.loc.line, I1);\n lines.push(...printOperation(op));\n }\n\n // Flush comment blocks between last operation and the next route\n flushBlocks(lines, blocks, idx, nextRouteStart, I1);\n\n // Trailing/orphan comments after the last operation, before the closing brace.\n for (const comment of route.trailingComments ?? []) {\n lines.push(`${I1}# ${comment}`);\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n\n// ─── Params block ────────────────────────────────────────────────────────────\n\nfunction printParamsBlock(source: ParamSource, indent: string, mode?: ObjectMode): string[] {\n const prefix = mode ? `mode(${mode}) ` : '';\n if (source.kind === 'ref') {\n return [`${indent}${prefix}params: ${source.name}`];\n }\n if (source.kind === 'params') {\n const lines: string[] = [`${indent}${prefix}params: {`];\n const inner = indent + INDENT;\n for (const p of source.nodes) {\n const opt = p.optional ? '?' : '';\n let t = printType(p.type);\n if (p.nullable) t += ' | null';\n const def = p.default !== undefined ? ` = ${formatDefault(p.default)}` : '';\n const comment = p.description ? ` # ${p.description}` : '';\n lines.push(`${inner}${p.name}${opt}: ${t}${def}${comment}`);\n }\n lines.push(`${indent}}`);\n return lines;\n }\n // ContractTypeNode\n return [`${indent}${prefix}params: ${printType(source.node)}`];\n}\n\n// ─── HTTP operation ──────────────────────────────────────────────────────────\n\n/** Order the body keys are emitted in when the node carries no source order (built programmatically). */\nconst CANONICAL_KEY_ORDER: OpBodyKey[] = ['name', 'service', 'sdk', 'mcp', 'signature', 'security', 'plugins', 'query', 'headers', 'request', 'responses'];\n\n/** Render a single operation-body key. Returns `[]` when the operation doesn't carry that key. */\nfunction printOperationKey(op: OpOperationNode, key: OpBodyKey): string[] {\n switch (key) {\n case 'name':\n return op.name ? [`${I2}name: ${op.name}`] : [];\n case 'service':\n return op.service ? [`${I2}service: ${op.service}`] : [];\n case 'sdk':\n return op.sdk ? [`${I2}sdk: ${op.sdk}`] : [];\n case 'mcp':\n if (op.mcp === true) return [`${I2}mcp: true`];\n if (op.mcp === false) return [`${I2}mcp: false`];\n return op.mcp ? printMcpBlock(op.mcp) : [];\n case 'signature': {\n if (!op.signature) return [];\n const comment = op.signatureDescription ? ` # ${op.signatureDescription}` : '';\n if (op.signaturePolicy) {\n return [\n `${I2}signature: {`,\n `${I3}options: ${formatSignatureValue(op.signature)}${comment}`,\n `${I3}policy: ${op.signaturePolicy}`,\n `${I2}}`,\n ];\n }\n return [`${I2}signature: ${formatSignatureValue(op.signature)}${comment}`];\n }\n case 'security':\n return op.security !== undefined ? printSecurity(op.security) : [];\n case 'plugins': {\n if (!op.plugins || Object.keys(op.plugins).length === 0) return [];\n const lines = [`${I2}plugins: {`];\n for (const [k, val] of Object.entries(op.plugins)) lines.push(...printPluginEntry(k, val, I3));\n lines.push(`${I2}}`);\n return lines;\n }\n case 'query':\n return op.query !== undefined ? printQueryOrHeaders('query', op.query, op.queryMode) : [];\n case 'headers':\n if (op.requestHeadersOptOut) return [`${I2}headers: none`];\n return op.headers !== undefined ? printQueryOrHeaders('headers', op.headers, op.headersMode) : [];\n case 'request': {\n if (!op.request) return [];\n const lines = [`${I2}request: {`];\n for (const body of op.request.bodies) lines.push(...printContentTypeLine(body.contentType, body.bodyType, I3));\n lines.push(`${I2}}`);\n return lines;\n }\n case 'responses':\n return op.responses.length > 0 ? printResponseBlock(op.responses, op.responsesTrailingComments) : [];\n }\n}\n\nfunction printOperation(op: OpOperationNode): string[] {\n const lines: string[] = [];\n const modPart = op.modifiers?.length ? `(${op.modifiers[0]})` : '';\n\n // A doc comment written above the method line goes back above it; only an inline one is\n // re-emitted as a trailing `#` on the header. Nodes built programmatically carry no placement,\n // and default to inline — the form most `.ck` sources use and one that round-trips as written.\n const inlineDescription = op.descriptionInline ?? true;\n // Standalone prose above the verb, kept apart from the doc comment when the verb carries an\n // inline one. Emitted first so it stays above the line it was written above.\n for (const c of op.leadingComments ?? []) lines.push(`${I1}# ${c}`);\n if (op.description && !inlineDescription) {\n for (const line of op.description.split('\\n')) lines.push(`${I1}# ${line}`);\n }\n const commentSuffix = op.description && inlineDescription ? ` # ${op.description}` : '';\n lines.push(`${I1}${op.method}${modPart}: {${commentSuffix}`);\n\n // Emit in source order when the parser recorded it, so formatting never reorders a user's keys.\n // Any key the source order doesn't mention (e.g. added by a later AST pass) follows in canonical order.\n const order = op.keyOrder ?? [];\n const rest = CANONICAL_KEY_ORDER.filter(k => !order.includes(k));\n // A comment run whose key turned out to print nothing has nowhere to sit; rather than drop it,\n // it falls through to the trailing run at the end of the body.\n const orphaned: string[] = [];\n for (const key of [...order, ...rest]) {\n const keyLines = printOperationKey(op, key);\n const comments = op.bodyLeadingComments?.[key] ?? [];\n if (keyLines.length === 0) {\n orphaned.push(...comments);\n continue;\n }\n for (const c of comments) lines.push(`${I2}# ${c}`);\n lines.push(...keyLines);\n }\n\n for (const c of [...orphaned, ...(op.bodyTrailingComments ?? [])]) lines.push(`${I2}# ${c}`);\n\n lines.push(`${I1}}`);\n return lines;\n}\n\n// ─── MCP block ───────────────────────────────────────────────────────────────\n\n/**\n * Reconstruct the `hint:` token list from the four annotation booleans, in canonical\n * order. Each set boolean contributes its positive or negative token; unset hints are omitted.\n */\nfunction mcpHintTokens(mcp: McpConfigNode): string[] {\n const tokens: string[] = [];\n if (mcp.readOnlyHint !== undefined) tokens.push(mcp.readOnlyHint ? 'readOnly' : 'nonReadOnly');\n if (mcp.idempotentHint !== undefined) tokens.push(mcp.idempotentHint ? 'idempotent' : 'nonIdempotent');\n if (mcp.destructiveHint !== undefined) tokens.push(mcp.destructiveHint ? 'destructive' : 'nonDestructive');\n if (mcp.openWorldHint !== undefined) tokens.push(mcp.openWorldHint ? 'openWorld' : 'closedWorld');\n return tokens;\n}\n\n/** Print an `mcp: { ... }` settings block. Fields are emitted in canonical order; `hint:` is omitted when no hints are set. */\nfunction printMcpBlock(mcp: McpConfigNode): string[] {\n const lines: string[] = [`${I2}mcp: {`];\n if (mcp.name !== undefined) lines.push(`${I3}name: \"${escapeString(mcp.name)}\"`);\n if (mcp.title !== undefined) lines.push(`${I3}title: \"${escapeString(mcp.title)}\"`);\n if (mcp.description !== undefined) lines.push(`${I3}description: \"${escapeString(mcp.description)}\"`);\n const tokens = mcpHintTokens(mcp);\n if (tokens.length > 0) lines.push(`${I3}hint: ${tokens.join(', ')}`);\n lines.push(`${I2}}`);\n return lines;\n}\n\n// ─── Plugins block ───────────────────────────────────────────────────────────\n\nconst IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;\n\nfunction escapeString(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\nfunction printPluginEntry(key: string, value: PluginValue, indent: string): string[] {\n const lines: string[] = [];\n const inline = printPluginInline(value);\n if (inline !== null) {\n lines.push(`${indent}${key}: ${inline}`);\n } else {\n const head = `${indent}${key}: `;\n const block = printPluginBlock(value, indent);\n lines.push(`${head}${block[0]!.trimStart()}`);\n for (let i = 1; i < block.length; i++) lines.push(block[i]!);\n }\n return lines;\n}\n\nfunction printPluginInline(value: PluginValue): string | null {\n if (typeof value === 'string') return `\"${escapeString(value)}\"`;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (value === null) return 'null';\n if (Array.isArray(value) && value.length === 0) return '[]';\n if (!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length === 0) return '{}';\n return null;\n}\n\nfunction printPluginBlock(value: PluginValue, indent: string): string[] {\n const inner = indent + INDENT;\n const lines: string[] = [];\n if (Array.isArray(value)) {\n lines.push(`${indent}[`);\n for (const item of value) {\n const inline = printPluginInline(item);\n if (inline !== null) {\n lines.push(`${inner}${inline}`);\n } else {\n const block = printPluginBlock(item, inner);\n for (const l of block) lines.push(l);\n }\n }\n lines.push(`${indent}]`);\n return lines;\n }\n if (typeof value === 'object' && value !== null) {\n lines.push(`${indent}{`);\n for (const [k, v] of Object.entries(value)) {\n const fieldKey = IDENT_RE.test(k) ? k : `\"${escapeString(k)}\"`;\n const inline = printPluginInline(v);\n if (inline !== null) {\n lines.push(`${inner}${fieldKey}: ${inline}`);\n } else {\n const block = printPluginBlock(v, inner);\n lines.push(`${inner}${fieldKey}: ${block[0]!.trimStart()}`);\n for (let i = 1; i < block.length; i++) lines.push(block[i]!);\n }\n }\n lines.push(`${indent}}`);\n return lines;\n }\n // Scalars are always inline; printPluginInline already handles them.\n return [`${indent}${printPluginInline(value)}`];\n}\n\n// ─── Security ────────────────────────────────────────────────────────────────\n\n/** Print a signature key: unquoted when it's a plain identifier, quoted otherwise. */\nfunction formatSignatureValue(value: string): string {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value) ? value : `\"${value}\"`;\n}\n\n/**\n * Print a `security:` declaration. Returns `[\"${indent}security: none\"]` for the public-endpoint\n * sentinel, a multi-line block when `policy` is set or the block carries comments, or an empty\n * array when there is nothing meaningful to emit.\n *\n * A block holding only comments still prints: the rationale for a policy floor is the reason\n * authors write in here, and collapsing the braces away would delete it.\n *\n * @param indent indentation for the `security` keyword line\n * @param innerIndent indentation for field lines inside the block\n */\nexport function printSecurity(security: SecurityNode, indent = I2, innerIndent = I3): string[] {\n if (security === SECURITY_NONE) return [`${indent}security: none`];\n const fields = security as SecurityFields;\n const leading = fields.leadingComments ?? [];\n const trailing = fields.trailingComments ?? [];\n // A block with no policy still has to be emitted when it carries comments, or the author's\n // rationale disappears along with the empty braces.\n if (fields.policy === undefined && leading.length === 0 && trailing.length === 0) return [];\n const lines = [`${indent}security: {`];\n for (const c of leading) lines.push(`${innerIndent}# ${c}`);\n if (fields.policy !== undefined) {\n const comment = fields.policyDescription ? ` # ${fields.policyDescription}` : '';\n const value = fields.policy === false ? 'none' : fields.policy;\n lines.push(`${innerIndent}policy: ${value}${comment}`);\n }\n for (const c of trailing) lines.push(`${innerIndent}# ${c}`);\n lines.push(`${indent}}`);\n return lines;\n}\n\n// ─── Query / headers ─────────────────────────────────────────────────────────\n\nfunction printQueryOrHeaders(keyword: 'query' | 'headers', source: ParamSource, mode?: ObjectMode): string[] {\n const prefix = mode ? `mode(${mode}) ` : '';\n if (source.kind === 'ref') {\n return [`${I2}${prefix}${keyword}: ${source.name}`];\n }\n if (source.kind === 'params') {\n if (source.nodes.length === 0) return [];\n const lines: string[] = [`${I2}${prefix}${keyword}: {`];\n for (const p of source.nodes) {\n const opt = p.optional ? '?' : '';\n let t = printType(p.type);\n if (p.nullable) t += ' | null';\n const def = p.default !== undefined ? ` = ${formatDefault(p.default)}` : '';\n const comment = p.description ? ` # ${p.description}` : '';\n lines.push(`${I3}${p.name}${opt}: ${t}${def}${comment}`);\n }\n lines.push(`${I2}}`);\n return lines;\n }\n // ContractTypeNode (e.g. intersection)\n return [`${I2}${prefix}${keyword}: ${printType(source.node)}`];\n}\n\n// ─── Content-type line ───────────────────────────────────────────────────────\n\n/** Print a `contentType: bodyType` line, expanding inline brace objects onto separate lines. */\nfunction printContentTypeLine(contentType: string, bodyType: ContractTypeNode, lineIndent: string): string[] {\n if (bodyType.kind === 'inlineObject') {\n const fieldIndent = lineIndent + INDENT;\n const lines: string[] = [`${lineIndent}${contentType}: {`];\n for (const f of bodyType.fields) {\n const opt = f.optional ? '?' : '';\n let t = printType(f.type);\n if (f.nullable) t += ' | null';\n const def = f.default !== undefined ? ` = ${formatDefault(f.default)}` : '';\n const comment = f.description ? ` # ${f.description}` : '';\n lines.push(`${fieldIndent}${f.name}${opt}: ${t}${def}${comment}`);\n }\n lines.push(`${lineIndent}}`);\n return lines;\n }\n return [`${lineIndent}${contentType}: ${printType(bodyType)}`];\n}\n\n// ─── Response block ──────────────────────────────────────────────────────────\n\nfunction printResponseBlock(responses: OpResponseNode[], trailingComments?: string[]): string[] {\n const lines: string[] = [`${I2}response: {`];\n\n for (const resp of responses) {\n for (const comment of resp.leadingComments ?? []) lines.push(`${I3}# ${comment}`);\n const bodies = resp.bodies;\n const hasHeaders = resp.headers && resp.headers.length > 0;\n const optOut = resp.headersOptOut;\n // `404(documented):` — the modifier changes what codegen does, so it has to survive.\n const code = resp.emit ? `${resp.statusCode}(${resp.emit})` : `${resp.statusCode}`;\n const inlinable = resp.inline && bodies.length > 0 && !hasHeaders && !optOut && bodies.every(b => b.bodyType.kind !== 'inlineObject');\n if (inlinable) {\n // Written on one line in the source, so keep it there: `200: { application/json: Pet }`.\n // Several mimes on that line stay on it too, space-separated as the grammar has them.\n const inner = bodies.map(b => `${b.contentType}: ${printType(b.bodyType)}`).join(' ');\n lines.push(`${I3}${code}: { ${inner} }`);\n } else if (bodies.length === 0 && !hasHeaders && !optOut && resp.hasBlock) {\n // An empty block means \"emitted, no body\" — collapsing it to `304:` would change\n // the generated router, so it is not a formatting detail.\n lines.push(`${I3}${code}: {}`);\n } else if (bodies.length > 0 || hasHeaders || optOut || (resp.trailingComments?.length ?? 0) > 0) {\n lines.push(`${I3}${code}: {`);\n for (const body of bodies) {\n for (const comment of body.leadingComments ?? []) lines.push(`${I4}# ${comment}`);\n lines.push(...printContentTypeLine(body.contentType, body.bodyType, I4));\n }\n for (const comment of resp.headersLeadingComments ?? []) lines.push(`${I4}# ${comment}`);\n if (optOut) {\n lines.push(`${I4}headers: none`);\n } else if (hasHeaders) {\n lines.push(`${I4}headers: {`);\n for (const h of resp.headers!) {\n const opt = h.optional ? '?' : '';\n const trail = h.description ? ` # ${h.description}` : '';\n lines.push(`${I4}${INDENT}${h.name}${opt}: ${printType(h.type)}${trail}`);\n }\n lines.push(`${I4}}`);\n }\n for (const comment of resp.trailingComments ?? []) lines.push(`${I4}# ${comment}`);\n lines.push(`${I3}}`);\n } else {\n lines.push(`${I3}${code}:`);\n }\n }\n\n for (const comment of trailingComments ?? []) lines.push(`${I3}# ${comment}`);\n lines.push(`${I2}}`);\n return lines;\n}\n","import type { CkRootNode, OpResponseHeaderNode, OptionsScopeComments } from '@contractkit/core';\nimport { printModelDecl } from './print-contract.js';\nimport { printRoute, printSecurity, type CommentBlock } from './print-operation.js';\nimport { printType } from './print-type.js';\nimport { INDENT } from './indent.js';\n\n/** Default line width used for wrapping inline-object and enum types when none is supplied. */\nexport const DEFAULT_PRINT_WIDTH = 80;\n\n// ─── Options block ──────────────────────────────────────────────────────────\n\n/**\n * Render an options value in the form the author wrote it.\n *\n * `wasUnquoted` comes from the parser, so an unquoted subpath import\n * (`PetService: #modules/pet/pet.service.js`) stays unquoted instead of being normalized. The\n * conditions still gate it: a value the grammar could not read back bare — one containing a\n * newline, a `}`, whitespace-then-`#`, or leading/trailing space — is quoted regardless, which\n * matters for values built programmatically rather than parsed.\n */\nfunction quoteOptionsValue(value: string, wasUnquoted = false): string {\n if (/^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(value)) return value;\n const roundTripsBare = value.length > 0 && value === value.trim() && !/[\\n\\r}]/.test(value) && !/[ \\t]#/.test(value) && !/^[\"']/.test(value);\n return wasUnquoted && roundTripsBare ? value : `\"${value}\"`;\n}\n\n/**\n * Emit the `key: value` lines of an options `keys`/`services` sub-block, interleaving any\n * retained comments: leading comments before the entry they precede, and trailing comments\n * after the last entry (before the closing `}`). Keeps comments inside these sub-blocks lossless.\n */\nfunction emitOptionsEntries(\n lines: string[],\n entries: Record<string, string>,\n comments: OptionsScopeComments | undefined,\n unquoted: string[] | undefined,\n): void {\n const I2 = INDENT + INDENT;\n const wasUnquoted = new Set(unquoted ?? []);\n for (const [key, value] of Object.entries(entries)) {\n for (const c of comments?.leading?.[key] ?? []) lines.push(`${I2}# ${c}`);\n const inline = comments?.inline?.[key];\n lines.push(`${I2}${key}: ${quoteOptionsValue(value, wasUnquoted.has(key))}${inline !== undefined ? ` # ${inline}` : ''}`);\n }\n for (const c of comments?.trailing ?? []) lines.push(`${I2}# ${c}`);\n}\n\nfunction printOptionsBlock(ast: CkRootNode): string | null {\n const hasMeta = Object.keys(ast.meta).length > 0;\n const hasServices = Object.keys(ast.services).length > 0;\n const hasSecurity = ast.security !== undefined;\n const hasRequestHeaders = (ast.requestHeaders?.length ?? 0) > 0;\n const hasResponseHeaders = (ast.responseHeaders?.length ?? 0) > 0;\n\n const hasBodyComments = ast.optionsComments?.body !== undefined;\n if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders && !hasBodyComments) return null;\n\n // A `#` run above the `options` keyword is the file's header comment.\n const lines: string[] = [...(ast.optionsComments?.leading ?? []).map(c => `# ${c}`), 'options {'];\n const body = ast.optionsComments?.body;\n /** Emit the comment run the author wrote directly above this sub-block. */\n const emitLeading = (scope: string) => {\n for (const c of body?.leading?.[scope] ?? []) lines.push(`${INDENT}# ${c}`);\n };\n\n if (hasMeta) {\n emitLeading('keys');\n lines.push(`${INDENT}keys: {`);\n emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys, ast.optionsUnquoted?.keys);\n lines.push(`${INDENT}}`);\n }\n\n if (hasServices) {\n emitLeading('services');\n lines.push(`${INDENT}services: {`);\n emitOptionsEntries(lines, ast.services, ast.optionsComments?.services, ast.optionsUnquoted?.services);\n lines.push(`${INDENT}}`);\n }\n\n if (hasRequestHeaders) {\n emitLeading('request');\n lines.push(...printOptionsHeaderScope('request', ast.requestHeaders!));\n }\n\n if (hasResponseHeaders) {\n emitLeading('response');\n lines.push(...printOptionsHeaderScope('response', ast.responseHeaders!));\n }\n\n if (hasSecurity) {\n emitLeading('security');\n lines.push(...printSecurity(ast.security!, INDENT, INDENT + INDENT));\n }\n\n for (const c of body?.trailing ?? []) lines.push(`${INDENT}# ${c}`);\n\n lines.push('}');\n return lines.join('\\n');\n}\n\nfunction printOptionsHeaderScope(keyword: 'request' | 'response', headers: OpResponseHeaderNode[]): string[] {\n const I2 = INDENT + INDENT;\n const I3 = INDENT + INDENT + INDENT;\n const lines = [`${INDENT}${keyword}: {`, `${I2}headers: {`];\n for (const h of headers) {\n const opt = h.optional ? '?' : '';\n const trail = h.description ? ` # ${h.description}` : '';\n lines.push(`${I3}${h.name}${opt}: ${printType(h.type)}${trail}`);\n }\n lines.push(`${I2}}`);\n lines.push(`${INDENT}}`);\n return lines;\n}\n\n// ─── CK file printer ───────────────────────────────────────────────────────\n\n/**\n * Render a parsed `.ck` AST back to source: options block first, then contracts, then\n * operations, separated by blank lines.\n *\n * Printing is the inverse of parsing — for well-formed source, `printCk(parseCk(text))` returns\n * `text` unchanged. That holds because the AST carries the author's layout alongside the\n * semantics: comment placement (`leadingComments`, `descriptionInline`, `bodyLeadingComments`,\n * the various `trailingComments`), operation body key order (`keyOrder`), blank lines\n * (`blankLineBefore`), and single-line response blocks (`inline`). Preserve those rather than\n * canonicalizing them, or `pnpm format` silently rewrites the user's file. A comment the AST\n * cannot represent is not merely unformatted — it is deleted. See `tests/round-trip.test.ts`.\n *\n * `printWidth` is forwarded to per-model printing for line wrapping inside\n * inline-object types.\n */\nexport function printCk(ast: CkRootNode, printWidth: number = DEFAULT_PRINT_WIDTH): string {\n const parts: string[] = [];\n\n // Options block\n const options = printOptionsBlock(ast);\n if (options) parts.push(options);\n\n // Contracts (models)\n for (const model of ast.models) {\n if (parts.length > 0) parts.push('');\n parts.push(printDeclLeadIn(model.leadingComments, model.descriptionInline ? undefined : model.description) + `contract ${printModelDecl(model, printWidth)}`);\n }\n\n // Operations (routes)\n const emptyBlocks: CommentBlock[] = [];\n const emptyIdx = { value: 0 };\n for (const route of ast.routes) {\n if (parts.length > 0) parts.push('');\n const modPart = route.modifiers?.length ? `(${route.modifiers[0]})` : '';\n parts.push(printDeclLeadIn(route.leadingComments, route.description) + `operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);\n }\n\n // A comment run after the last declaration. Separated by a blank line, matching how every\n // other top-level item is spaced.\n if (ast.trailingComments?.length) {\n if (parts.length > 0) parts.push('');\n for (const c of ast.trailingComments) parts.push(`# ${c}`);\n }\n\n return parts.join('\\n') + '\\n';\n}\n\n/**\n * Build the comment lines that precede a top-level declaration: any standalone block (a section\n * divider and the like, kept separated by the blank line the author wrote), then the declaration's\n * own doc comment on the lines immediately above it. Returns `''` when there is neither.\n */\nfunction printDeclLeadIn(leadingComments: string[] | undefined, description: string | undefined): string {\n const lines: string[] = [];\n if (leadingComments?.length) {\n for (const c of leadingComments) lines.push(`# ${c}`);\n lines.push('');\n }\n if (description) {\n for (const line of description.split('\\n')) lines.push(`# ${line}`);\n }\n return lines.length > 0 ? lines.join('\\n') + '\\n' : '';\n}\n"],"mappings":"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","names":["builders","parseCk","DiagnosticCollector","INDENT","printType","type","kind","constraints","format","undefined","fmt","push","test","min","max","len","regex","length","name","join","args","item","items","map","key","value","values","formatEnumValue","members","discriminator","printInlineObjectCompact","inner","obj","prefix","mode","fields","parts","f","opt","optional","t","nullable","printEnumExpanded","indent","innerIndent","INDENT","v","printField","field","printWidth","ovr","override","dep","deprecated","vis","visibility","mods","def","default","formatDefault","comment","description","trailing","extractTrailingInlineObject","inlineObj","modePart","header","printInlineObjectExpanded","typeStr","fullLine","enumStr","lines","trailingComments","val","String","last","prefixStr","slice","printModelDecl","model","printWidth","type","undefined","printTypeAlias","commentSuffix","description","descriptionInline","modifiers","deprecated","inputCase","outputCase","filter","Boolean","join","mode","modePrefix","baseChain","bases","length","header","name","lines","field","fields","push","printField","INDENT","comment","trailingComments","trailing","extractTrailingInlineObject","prefix","inlineObj","modePart","printInlineObjectExpanded","singleLine","printType","kind","printEnumExpanded","values","SECURITY_NONE","I1","INDENT","I2","repeat","I3","I4","flushBlocks","out","blocks","idx","beforeLine","_indent","value","length","startLine","l","lines","push","printRoute","route","nextRouteStart","path","params","undefined","printParamsBlock","I1","paramsMode","security","printSecurity","I2","op","operations","blankLineBefore","loc","line","printOperation","comment","trailingComments","join","source","indent","mode","prefix","kind","name","inner","INDENT","p","nodes","opt","optional","t","printType","type","nullable","def","default","formatDefault","description","node","CANONICAL_KEY_ORDER","printOperationKey","key","service","sdk","mcp","printMcpBlock","signature","signatureDescription","signaturePolicy","I3","formatSignatureValue","plugins","Object","keys","k","val","entries","printPluginEntry","query","printQueryOrHeaders","queryMode","requestHeadersOptOut","headers","headersMode","request","body","bodies","printContentTypeLine","contentType","bodyType","responses","printResponseBlock","responsesTrailingComments","modPart","modifiers","inlineDescription","descriptionInline","c","leadingComments","split","commentSuffix","method","order","keyOrder","rest","filter","includes","orphaned","keyLines","comments","bodyLeadingComments","bodyTrailingComments","mcpHintTokens","tokens","readOnlyHint","idempotentHint","destructiveHint","openWorldHint","escapeString","title","IDENT_RE","s","replace","inline","printPluginInline","head","block","printPluginBlock","trimStart","i","String","Array","isArray","item","v","fieldKey","test","innerIndent","SECURITY_NONE","fields","leading","trailing","policy","policyDescription","keyword","lineIndent","fieldIndent","f","resp","hasHeaders","optOut","headersOptOut","code","emit","statusCode","inlinable","every","b","map","hasBlock","I4","headersLeadingComments","h","trail","DEFAULT_PRINT_WIDTH","quoteOptionsValue","value","wasUnquoted","test","roundTripsBare","length","trim","emitOptionsEntries","lines","entries","comments","unquoted","I2","INDENT","Set","key","Object","c","leading","push","inline","has","undefined","trailing","printOptionsBlock","ast","hasMeta","keys","meta","hasServices","services","hasSecurity","security","hasRequestHeaders","requestHeaders","hasResponseHeaders","responseHeaders","hasBodyComments","optionsComments","body","map","emitLeading","scope","optionsUnquoted","printOptionsHeaderScope","printSecurity","join","keyword","headers","I3","h","opt","optional","trail","description","name","printType","type","printCk","printWidth","parts","options","model","models","printDeclLeadIn","leadingComments","descriptionInline","printModelDecl","emptyBlocks","emptyIdx","route","routes","modPart","modifiers","printRoute","Infinity","trailingComments","line","split","hardline","join","builders","toDoc","text","lines","trimEnd","split","plugin","languages","name","parsers","extensions","vscodeLanguageIds","parse","_options","diag","DiagnosticCollector","parseCk","astFormat","locStart","locEnd","_node","printers","print","path","options","node","printCk","printWidth"]}
@@ -7,10 +7,11 @@ export declare const DEFAULT_PRINT_WIDTH = 80;
7
7
  *
8
8
  * Printing is the inverse of parsing — for well-formed source, `printCk(parseCk(text))` returns
9
9
  * `text` unchanged. That holds because the AST carries the author's layout alongside the
10
- * semantics: comment placement (`leadingComments`, `descriptionInline`), operation body key
11
- * order (`keyOrder`), blank lines (`blankLineBefore`), and single-line response blocks
12
- * (`inline`). Preserve those rather than canonicalizing them, or `pnpm format` silently
13
- * rewrites the user's file. See `tests/round-trip.test.ts`.
10
+ * semantics: comment placement (`leadingComments`, `descriptionInline`, `bodyLeadingComments`,
11
+ * the various `trailingComments`), operation body key order (`keyOrder`), blank lines
12
+ * (`blankLineBefore`), and single-line response blocks (`inline`). Preserve those rather than
13
+ * canonicalizing them, or `pnpm format` silently rewrites the user's file. A comment the AST
14
+ * cannot represent is not merely unformatted — it is deleted. See `tests/round-trip.test.ts`.
14
15
  *
15
16
  * `printWidth` is forwarded to per-model printing for line wrapping inside
16
17
  * inline-object types.
@@ -1 +1 @@
1
- {"version":3,"file":"print-ck.d.ts","sourceRoot":"","sources":["../src/print-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA8C,MAAM,mBAAmB,CAAC;AAMhG,+FAA+F;AAC/F,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA6GtC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,GAAE,MAA4B,GAAG,MAAM,CAuBzF"}
1
+ {"version":3,"file":"print-ck.d.ts","sourceRoot":"","sources":["../src/print-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA8C,MAAM,mBAAmB,CAAC;AAMhG,+FAA+F;AAC/F,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA6GtC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,GAAE,MAA4B,GAAG,MAAM,CA8BzF"}
@@ -34,8 +34,11 @@ export declare function printRoute(route: OpRouteNode, blocks: CommentBlock[], i
34
34
  }, nextRouteStart: number): string;
35
35
  /**
36
36
  * Print a `security:` declaration. Returns `["${indent}security: none"]` for the public-endpoint
37
- * sentinel, a multi-line block when `policy` is set, or an empty array when there's nothing
38
- * meaningful to emit.
37
+ * sentinel, a multi-line block when `policy` is set or the block carries comments, or an empty
38
+ * array when there is nothing meaningful to emit.
39
+ *
40
+ * A block holding only comments still prints: the rationale for a policy floor is the reason
41
+ * authors write in here, and collapsing the braces away would delete it.
39
42
  *
40
43
  * @param indent indentation for the `security` keyword line
41
44
  * @param innerIndent indentation for field lines inside the block
@@ -1 +1 @@
1
- {"version":3,"file":"print-operation.d.ts","sourceRoot":"","sources":["../src/print-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,WAAW,EAIX,YAAY,EAOf,MAAM,mBAAmB,CAAC;AAY3B,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACnD,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAElE,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAarE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,SAAK,QAK1H;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CA8B7H;AAoND;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAK,EAAE,WAAW,SAAK,GAAG,MAAM,EAAE,CAU7F"}
1
+ {"version":3,"file":"print-operation.d.ts","sourceRoot":"","sources":["../src/print-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,WAAW,EAIX,YAAY,EAOf,MAAM,mBAAmB,CAAC;AAY3B,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACnD,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAElE,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAarE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,SAAK,QAK1H;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CA8B7H;AAmOD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAK,EAAE,WAAW,SAAK,GAAG,MAAM,EAAE,CAkB7F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractkit/prettier-plugin",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Prettier plugin for ContractKit DSL",
5
5
  "author": {
6
6
  "name": "Marooned Software",
@@ -29,12 +29,12 @@
29
29
  "prettier": "^3.0.0"
30
30
  },
31
31
  "dependencies": {
32
- "@contractkit/core": "0.25.0"
32
+ "@contractkit/core": "0.26.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "prettier": "^3.8.3",
36
- "@repo/config-eslint": "0.3.1",
37
- "@repo/config-typescript": "0.1.0"
36
+ "@repo/config-typescript": "0.1.0",
37
+ "@repo/config-eslint": "0.3.1"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration",
package/src/index.ts CHANGED
@@ -6,11 +6,27 @@ import { printCk } from './print-ck.js';
6
6
 
7
7
  const { hardline, join } = builders;
8
8
 
9
+ /**
10
+ * Wrap printed source as a prettier doc.
11
+ *
12
+ * `printCk` already ends the file with a newline, but splitting on `\n` turns that into a
13
+ * trailing empty segment, so the text is trimmed first and the final newline re-added as an
14
+ * explicit `hardline`. Dropping it would leave the file without a terminating newline, which
15
+ * fights every editor and lint rule that wants one.
16
+ */
9
17
  function toDoc(text: string) {
10
18
  const lines = text.trimEnd().split('\n');
11
- return join(hardline, lines);
19
+ return [join(hardline, lines), hardline];
12
20
  }
13
21
 
22
+ /**
23
+ * Prettier plugin registering the `.ck` language, its parser, and its printer.
24
+ *
25
+ * The parser is `parseCk` and the printer is `printCk`, so formatting behaviour lives in
26
+ * `@contractkit/core` and `print-ck.ts` rather than here — this module only adapts them to
27
+ * prettier's interface. `tests/format-plugin.test.ts` covers that adapter layer, which a
28
+ * printer-level test cannot reach.
29
+ */
14
30
  const plugin: Plugin<CkRootNode> = {
15
31
  languages: [
16
32
  {
package/src/print-ck.ts CHANGED
@@ -120,10 +120,11 @@ function printOptionsHeaderScope(keyword: 'request' | 'response', headers: OpRes
120
120
  *
121
121
  * Printing is the inverse of parsing — for well-formed source, `printCk(parseCk(text))` returns
122
122
  * `text` unchanged. That holds because the AST carries the author's layout alongside the
123
- * semantics: comment placement (`leadingComments`, `descriptionInline`), operation body key
124
- * order (`keyOrder`), blank lines (`blankLineBefore`), and single-line response blocks
125
- * (`inline`). Preserve those rather than canonicalizing them, or `pnpm format` silently
126
- * rewrites the user's file. See `tests/round-trip.test.ts`.
123
+ * semantics: comment placement (`leadingComments`, `descriptionInline`, `bodyLeadingComments`,
124
+ * the various `trailingComments`), operation body key order (`keyOrder`), blank lines
125
+ * (`blankLineBefore`), and single-line response blocks (`inline`). Preserve those rather than
126
+ * canonicalizing them, or `pnpm format` silently rewrites the user's file. A comment the AST
127
+ * cannot represent is not merely unformatted — it is deleted. See `tests/round-trip.test.ts`.
127
128
  *
128
129
  * `printWidth` is forwarded to per-model printing for line wrapping inside
129
130
  * inline-object types.
@@ -150,6 +151,13 @@ export function printCk(ast: CkRootNode, printWidth: number = DEFAULT_PRINT_WIDT
150
151
  parts.push(printDeclLeadIn(route.leadingComments, route.description) + `operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
151
152
  }
152
153
 
154
+ // A comment run after the last declaration. Separated by a blank line, matching how every
155
+ // other top-level item is spaced.
156
+ if (ast.trailingComments?.length) {
157
+ if (parts.length > 0) parts.push('');
158
+ for (const c of ast.trailingComments) parts.push(`# ${c}`);
159
+ }
160
+
153
161
  return parts.join('\n') + '\n';
154
162
  }
155
163
 
@@ -189,6 +189,9 @@ function printOperation(op: OpOperationNode): string[] {
189
189
  // re-emitted as a trailing `#` on the header. Nodes built programmatically carry no placement,
190
190
  // and default to inline — the form most `.ck` sources use and one that round-trips as written.
191
191
  const inlineDescription = op.descriptionInline ?? true;
192
+ // Standalone prose above the verb, kept apart from the doc comment when the verb carries an
193
+ // inline one. Emitted first so it stays above the line it was written above.
194
+ for (const c of op.leadingComments ?? []) lines.push(`${I1}# ${c}`);
192
195
  if (op.description && !inlineDescription) {
193
196
  for (const line of op.description.split('\n')) lines.push(`${I1}# ${line}`);
194
197
  }
@@ -199,10 +202,22 @@ function printOperation(op: OpOperationNode): string[] {
199
202
  // Any key the source order doesn't mention (e.g. added by a later AST pass) follows in canonical order.
200
203
  const order = op.keyOrder ?? [];
201
204
  const rest = CANONICAL_KEY_ORDER.filter(k => !order.includes(k));
205
+ // A comment run whose key turned out to print nothing has nowhere to sit; rather than drop it,
206
+ // it falls through to the trailing run at the end of the body.
207
+ const orphaned: string[] = [];
202
208
  for (const key of [...order, ...rest]) {
203
- lines.push(...printOperationKey(op, key));
209
+ const keyLines = printOperationKey(op, key);
210
+ const comments = op.bodyLeadingComments?.[key] ?? [];
211
+ if (keyLines.length === 0) {
212
+ orphaned.push(...comments);
213
+ continue;
214
+ }
215
+ for (const c of comments) lines.push(`${I2}# ${c}`);
216
+ lines.push(...keyLines);
204
217
  }
205
218
 
219
+ for (const c of [...orphaned, ...(op.bodyTrailingComments ?? [])]) lines.push(`${I2}# ${c}`);
220
+
206
221
  lines.push(`${I1}}`);
207
222
  return lines;
208
223
  }
@@ -311,8 +326,11 @@ function formatSignatureValue(value: string): string {
311
326
 
312
327
  /**
313
328
  * Print a `security:` declaration. Returns `["${indent}security: none"]` for the public-endpoint
314
- * sentinel, a multi-line block when `policy` is set, or an empty array when there's nothing
315
- * meaningful to emit.
329
+ * sentinel, a multi-line block when `policy` is set or the block carries comments, or an empty
330
+ * array when there is nothing meaningful to emit.
331
+ *
332
+ * A block holding only comments still prints: the rationale for a policy floor is the reason
333
+ * authors write in here, and collapsing the braces away would delete it.
316
334
  *
317
335
  * @param indent indentation for the `security` keyword line
318
336
  * @param innerIndent indentation for field lines inside the block
@@ -320,11 +338,19 @@ function formatSignatureValue(value: string): string {
320
338
  export function printSecurity(security: SecurityNode, indent = I2, innerIndent = I3): string[] {
321
339
  if (security === SECURITY_NONE) return [`${indent}security: none`];
322
340
  const fields = security as SecurityFields;
323
- if (fields.policy === undefined) return [];
341
+ const leading = fields.leadingComments ?? [];
342
+ const trailing = fields.trailingComments ?? [];
343
+ // A block with no policy still has to be emitted when it carries comments, or the author's
344
+ // rationale disappears along with the empty braces.
345
+ if (fields.policy === undefined && leading.length === 0 && trailing.length === 0) return [];
324
346
  const lines = [`${indent}security: {`];
325
- const comment = fields.policyDescription ? ` # ${fields.policyDescription}` : '';
326
- const value = fields.policy === false ? 'none' : fields.policy;
327
- lines.push(`${innerIndent}policy: ${value}${comment}`);
347
+ for (const c of leading) lines.push(`${innerIndent}# ${c}`);
348
+ if (fields.policy !== undefined) {
349
+ const comment = fields.policyDescription ? ` # ${fields.policyDescription}` : '';
350
+ const value = fields.policy === false ? 'none' : fields.policy;
351
+ lines.push(`${innerIndent}policy: ${value}${comment}`);
352
+ }
353
+ for (const c of trailing) lines.push(`${innerIndent}# ${c}`);
328
354
  lines.push(`${indent}}`);
329
355
  return lines;
330
356
  }
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as prettier from 'prettier';
3
+ import plugin from '../src/index.js';
4
+
5
+ /**
6
+ * End-to-end checks that go through prettier itself rather than calling `printCk` directly.
7
+ *
8
+ * `printCk` terminates the file with a newline, so a printer-level test cannot see whether the
9
+ * plugin's doc wrapper preserves it — the plugin used to `trimEnd()` the printed source and
10
+ * return it without re-adding the terminator, so every formatted `.ck` file lost its trailing
11
+ * newline. These tests exercise the layer where that happened.
12
+ */
13
+
14
+ function format(source: string): Promise<string> {
15
+ return prettier.format(source, { parser: 'contract-ck', plugins: [plugin] });
16
+ }
17
+
18
+ describe('prettier plugin — end to end', () => {
19
+ it('ends the formatted file with exactly one newline', async () => {
20
+ const source = `contract Pet: {
21
+ name: string
22
+ }
23
+ `;
24
+ const out = await format(source);
25
+ expect(out).toBe(source);
26
+ expect(out.endsWith('\n')).toBe(true);
27
+ expect(out.endsWith('\n\n')).toBe(false);
28
+ });
29
+
30
+ it('adds the trailing newline when the source lacks one', async () => {
31
+ const out = await format('contract Pet: {\n name: string\n}');
32
+ expect(out).toBe('contract Pet: {\n name: string\n}\n');
33
+ });
34
+
35
+ it('does not accumulate blank lines when run twice', async () => {
36
+ const source = `contract Pet: {
37
+ name: string
38
+ }
39
+ `;
40
+ expect(await format(await format(source))).toBe(source);
41
+ });
42
+ });
@@ -471,6 +471,211 @@ describe('round-trip — comments in the response block', () => {
471
471
  });
472
472
  });
473
473
 
474
+ // ─── Security blocks ─────────────────────────────────────────────────────────
475
+
476
+ /**
477
+ * A `security { }` block is where authors record *why* a policy floor sits where it does, so the
478
+ * prose in and around it is load-bearing. Every position here used to be deleted outright on
479
+ * format: the parser skipped standalone comments inside the block, dropped comment runs above a
480
+ * body key, and let a comment run above a verb lose to the verb's own inline doc comment.
481
+ */
482
+ describe('round-trip — security block comments', () => {
483
+ it('keeps a comment above the policy line in the options block', () => {
484
+ const source = `options {
485
+ security: {
486
+ # The floor for every operation in this file.
487
+ # Reads override it downward at their own verb.
488
+ policy: platform.manage
489
+ }
490
+ }
491
+ `;
492
+ expect(format(source)).toBe(source);
493
+ });
494
+
495
+ it('keeps a comment after the policy line, before the closing brace', () => {
496
+ const source = `options {
497
+ security: {
498
+ policy: platform.manage
499
+ # Object-scoped permissions cannot be named here yet.
500
+ }
501
+ }
502
+ `;
503
+ expect(format(source)).toBe(source);
504
+ });
505
+
506
+ it('distinguishes an inline policy comment from one on the next line', () => {
507
+ const source = `options {
508
+ security: {
509
+ # Why this floor
510
+ policy: platform.manage # the operator gate
511
+ }
512
+ }
513
+ `;
514
+ expect(format(source)).toBe(source);
515
+ });
516
+
517
+ it('keeps a comment above a security block inside an operation body', () => {
518
+ const source = `operation /plugins: {
519
+ get: {
520
+ name: List plugins
521
+ # A read, so it drops to the view floor.
522
+ security: {
523
+ policy: platform.view
524
+ }
525
+ response: {
526
+ 200:
527
+ }
528
+ }
529
+ }
530
+ `;
531
+ expect(format(source)).toBe(source);
532
+ });
533
+
534
+ it('keeps a comment above a security: none line', () => {
535
+ const source = `operation /plugins/callback: {
536
+ get: {
537
+ name: OAuth callback
538
+ # The provider redirects a browser here, so it cannot carry a session.
539
+ security: none
540
+ response: {
541
+ 200:
542
+ }
543
+ }
544
+ }
545
+ `;
546
+ expect(format(source)).toBe(source);
547
+ });
548
+
549
+ it('keeps a comment run above a verb that also has an inline doc comment', () => {
550
+ const source = `operation /plugins/{id}/logs: {
551
+ params: {
552
+ id: uuid
553
+ }
554
+ # Log output is whatever the plugin chose to write, including upstream error
555
+ # bodies. Treat it as operator-only until the log store can promise otherwise.
556
+ get: { # Returns the plugin's buffered log lines
557
+ name: Get plugin logs
558
+ response: {
559
+ 200:
560
+ }
561
+ }
562
+ }
563
+ `;
564
+ expect(format(source)).toBe(source);
565
+ });
566
+
567
+ it('keeps a comment left after the last key in an operation body', () => {
568
+ const source = `operation /plugins: {
569
+ get: {
570
+ name: List plugins
571
+ response: {
572
+ 200:
573
+ }
574
+ # TODO: gate this behind the object-scoped permission once it exists
575
+ }
576
+ }
577
+ `;
578
+ expect(format(source)).toBe(source);
579
+ });
580
+ });
581
+
582
+ // ─── Declaration-boundary comments ───────────────────────────────────────────
583
+
584
+ /**
585
+ * A comment between two top-level declarations is ambiguous: it can be an inline comment trailing
586
+ * the one above, a doc comment for the one below, or a standalone block. A declaration could not
587
+ * see what preceded it, so `ModelBody`'s optional trailing comment claimed the *next* line's
588
+ * comment — deleting a doc comment, or silently re-filing it as the previous contract's
589
+ * description. `Root` places these now, by comparing source lines.
590
+ */
591
+ describe('round-trip — comments between declarations', () => {
592
+ it('keeps a doc comment on a contract that follows a one-line alias contract', () => {
593
+ const source = `contract ConfigFieldType: enum(string, url, secret)
594
+
595
+ # One choice of a \`select\` config field
596
+ contract ConfigFieldOption: {
597
+ value: string
598
+ }
599
+ `;
600
+ expect(format(source)).toBe(source);
601
+ });
602
+
603
+ it('keeps an inline comment on an alias contract where it was written', () => {
604
+ const source = `contract PluginStatus: enum(discovered, active) # Lifecycle state
605
+
606
+ contract ConfigFieldOption: {
607
+ value: string
608
+ }
609
+ `;
610
+ expect(format(source)).toBe(source);
611
+ });
612
+
613
+ it('tells an inline comment apart from the doc comment below it', () => {
614
+ const source = `contract PluginStatus: enum(discovered, active) # Lifecycle state
615
+
616
+ # One choice of a \`select\` config field
617
+ contract ConfigFieldOption: {
618
+ value: string
619
+ }
620
+ `;
621
+ expect(format(source)).toBe(source);
622
+ });
623
+
624
+ it('keeps an inline comment on the last declaration in the file', () => {
625
+ const source = `contract PluginStatus: enum(discovered, active) # Lifecycle state
626
+ `;
627
+ expect(format(source)).toBe(source);
628
+ });
629
+
630
+ it('keeps a comment after the last declaration', () => {
631
+ const source = `contract PluginStatus: enum(discovered, active)
632
+
633
+ # TODO: add the archived state once the store supports it
634
+ `;
635
+ expect(format(source)).toBe(source);
636
+ });
637
+ });
638
+
639
+ // ─── Field comments ──────────────────────────────────────────────────────────
640
+
641
+ describe('round-trip — field comments', () => {
642
+ it('keeps a comment on a nested inline object opening brace', () => {
643
+ const source = `contract FidoFactorAttestation: {
644
+ rp: { # The relying party
645
+ name: string # The relying party name
646
+ }
647
+ }
648
+ `;
649
+ expect(format(source)).toBe(source);
650
+ });
651
+
652
+ it('keeps a nested brace comment that repeats the contract header wording', () => {
653
+ // The header comment is reclaimed from the field list by matching text; when a nested
654
+ // field carried the same wording, the reclaim deleted the wrong one.
655
+ const source = `contract FidoFactorAttestation: { # The FIDO factor attestation information
656
+ rp: { # The FIDO factor attestation information
657
+ name: string # The relying party name
658
+ }
659
+ }
660
+ `;
661
+ expect(format(source)).toBe(source);
662
+ });
663
+
664
+ it('keeps a trailing comment on a field whose enum type wraps', () => {
665
+ const source = `contract LineupEntry: {
666
+ segmentState?: enum(
667
+ planned,
668
+ rendering,
669
+ ready,
670
+ failed,
671
+ gone
672
+ ) # How far along the segment is
673
+ }
674
+ `;
675
+ expect(format(source)).toBe(source);
676
+ });
677
+ });
678
+
474
679
  // ─── Idempotence ─────────────────────────────────────────────────────────────
475
680
 
476
681
  describe('round-trip — idempotence', () => {