@contractkit/prettier-plugin 0.14.0 → 0.14.2
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.
- package/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +15 -13
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +4 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -6
- package/dist/index.js.map +1 -1
- package/dist/print-ck.d.ts +5 -4
- package/dist/print-ck.d.ts.map +1 -1
- package/dist/print-operation.d.ts +5 -2
- package/dist/print-operation.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +17 -1
- package/src/print-ck.ts +12 -4
- package/src/print-operation.ts +33 -7
- package/tests/format-plugin.test.ts +42 -0
- package/tests/round-trip.test.ts +205 -0
|
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
|
|
|
5
5
|
[34mCLI[39m tsup v8.5.1
|
|
6
6
|
[34mCLI[39m Target: esnext
|
|
7
7
|
[34mESM[39m Build start
|
|
8
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
9
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
10
|
-
[32mESM[39m ⚡️ Build success in
|
|
8
|
+
[32mESM[39m [1mdist/index.js [22m[32m27.08 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m69.36 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 205ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
13
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 4778ms
|
|
13
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.65 KB[39m
|
|
@@ -3,24 +3,26 @@ $ vitest run --coverage
|
|
|
3
3
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.5 [39m[90m/home/runner/work/ContractKit/ContractKit/apps/prettier-plugin[39m
|
|
4
4
|
[2mCoverage enabled with [22m[33mv8[39m
|
|
5
5
|
|
|
6
|
-
[32m✓[39m tests/
|
|
7
|
-
[32m✓[39m tests/
|
|
8
|
-
|
|
9
|
-
[33m[2m✓[22m[39m
|
|
6
|
+
[32m✓[39m tests/format-plugin.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 124[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/print-ck.test.ts [2m([22m[2m81 tests[22m[2m)[22m[33m 600[2mms[22m[39m
|
|
8
|
+
[32m✓[39m tests/round-trip.test.ts [2m([22m[2m49 tests[22m[2m)[22m[33m 1974[2mms[22m[39m
|
|
9
|
+
[33m[2m✓[22m[39m formats test.ck to itself [33m 733[2mms[22m[39m
|
|
10
|
+
[33m[2m✓[22m[39m formatting is a fixed point for source #0 [33m 602[2mms[22m[39m
|
|
10
11
|
|
|
11
|
-
[2m Test Files [22m [1m[
|
|
12
|
-
[2m Tests [22m [1m[
|
|
13
|
-
[2m Start at [22m
|
|
14
|
-
[2m Duration [22m 8.
|
|
12
|
+
[2m Test Files [22m [1m[32m3 passed[39m[22m[90m (3)[39m
|
|
13
|
+
[2m Tests [22m [1m[32m133 passed[39m[22m[90m (133)[39m
|
|
14
|
+
[2m Start at [22m 13:44:24
|
|
15
|
+
[2m Duration [22m 8.12s[2m (transform 4.45s, setup 0ms, import 13.69s, tests 2.70s, environment 0ms)[22m
|
|
15
16
|
|
|
16
17
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
17
18
|
-------------------|---------|----------|---------|---------|-------------------
|
|
18
19
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
19
20
|
-------------------|---------|----------|---------|---------|-------------------
|
|
20
|
-
All files |
|
|
21
|
+
All files | 79.5 | 74.08 | 93.47 | 86.44 |
|
|
21
22
|
indent.ts | 100 | 100 | 100 | 100 |
|
|
22
|
-
|
|
23
|
-
print-
|
|
24
|
-
|
|
25
|
-
|
|
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,43 @@
|
|
|
1
1
|
# @contractkit/prettier-plugin-contractkit
|
|
2
2
|
|
|
3
|
+
## 0.14.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ca1c139: Declare the MIT license explicitly: every package now ships a `LICENSE` file in its published tarball and sets `"license": "MIT"` in its manifest, so license scanners and registries report the terms correctly.
|
|
8
|
+
- Updated dependencies [ca1c139]
|
|
9
|
+
- @contractkit/core@0.26.1
|
|
10
|
+
|
|
11
|
+
## 0.14.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- ab69718: Stop the formatter deleting comments in and around a `security { }` block, and keep the trailing newline at end of file.
|
|
16
|
+
|
|
17
|
+
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:
|
|
18
|
+
- 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);
|
|
19
|
+
- 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;
|
|
20
|
+
- a comment left after the last key in an operation body, before the closing brace.
|
|
21
|
+
|
|
22
|
+
`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.
|
|
23
|
+
|
|
24
|
+
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`.
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
`SecurityFields` gains `leadingComments` / `trailingComments`, `OpOperationNode` gains `bodyLeadingComments`, `bodyTrailingComments`, and `leadingComments`, and `CkRootNode` gains `trailingComments`. All are optional and additive, so codegen plugins are unaffected.
|
|
29
|
+
|
|
30
|
+
### `#` where it is not a comment
|
|
31
|
+
|
|
32
|
+
`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.
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [ab69718]
|
|
39
|
+
- @contractkit/core@0.26.0
|
|
40
|
+
|
|
3
41
|
## 0.14.0
|
|
4
42
|
|
|
5
43
|
### Minor Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marooned Software
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
478
|
-
|
|
479
|
-
|
|
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
|
|
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"]}
|
package/dist/print-ck.d.ts
CHANGED
|
@@ -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`
|
|
11
|
-
*
|
|
12
|
-
* (`inline`). Preserve those rather than
|
|
13
|
-
* rewrites the user's file.
|
|
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.
|
package/dist/print-ck.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
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;
|
|
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,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractkit/prettier-plugin",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "Prettier plugin for ContractKit DSL",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"author": {
|
|
6
7
|
"name": "Marooned Software",
|
|
7
8
|
"url": "https://github.com/MaroonedSoftware/contractkit"
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"prettier": "^3.0.0"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@contractkit/core": "0.
|
|
33
|
+
"@contractkit/core": "0.26.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"prettier": "^3.8.3",
|
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`
|
|
124
|
-
*
|
|
125
|
-
* (`inline`). Preserve those rather than
|
|
126
|
-
* rewrites the user's file.
|
|
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
|
|
package/src/print-operation.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
});
|
package/tests/round-trip.test.ts
CHANGED
|
@@ -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', () => {
|