@contractkit/prettier-plugin 0.13.0 → 0.14.0
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 +4 -4
- package/.turbo/turbo-test$colon$ci.log +10 -10
- package/CHANGELOG.md +32 -0
- package/README.md +5 -1
- package/dist/index.js +31 -16
- package/dist/index.js.map +1 -1
- package/dist/print-ck.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/print-ck.ts +23 -12
- package/src/print-operation.ts +24 -10
- package/tests/print-ck.test.ts +5 -3
- package/tests/round-trip.test.ts +221 -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[32m26.17 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m65.66 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 231ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 4442ms
|
|
13
13
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.13 KB[39m
|
|
@@ -3,24 +3,24 @@ $ 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/print-ck.test.ts [2m([22m[2m81 tests[22m[2m)[22m[33m
|
|
7
|
-
[32m✓[39m tests/round-trip.test.ts [2m([22m[
|
|
8
|
-
[33m[2m✓[22m[39m formats test.ck to itself [33m
|
|
9
|
-
[33m[2m✓[22m[39m formatting is a fixed point for source #0 [33m
|
|
6
|
+
[32m✓[39m tests/print-ck.test.ts [2m([22m[2m81 tests[22m[2m)[22m[33m 923[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/round-trip.test.ts [2m([22m[2m34 tests[22m[2m)[22m[33m 2390[2mms[22m[39m
|
|
8
|
+
[33m[2m✓[22m[39m formats test.ck to itself [33m 690[2mms[22m[39m
|
|
9
|
+
[33m[2m✓[22m[39m formatting is a fixed point for source #0 [33m 1192[2mms[22m[39m
|
|
10
10
|
|
|
11
11
|
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
12
|
-
[2m Tests [22m [1m[
|
|
13
|
-
[2m Start at [22m
|
|
14
|
-
[2m Duration [22m
|
|
12
|
+
[2m Tests [22m [1m[32m115 passed[39m[22m[90m (115)[39m
|
|
13
|
+
[2m Start at [22m 16:09:43
|
|
14
|
+
[2m Duration [22m 8.00s[2m (transform 3.22s, setup 0ms, import 8.69s, tests 3.31s, environment 0ms)[22m
|
|
15
15
|
|
|
16
16
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
17
17
|
-------------------|---------|----------|---------|---------|-------------------
|
|
18
18
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
19
19
|
-------------------|---------|----------|---------|---------|-------------------
|
|
20
|
-
All files |
|
|
20
|
+
All files | 76.13 | 71.15 | 85.36 | 82.04 |
|
|
21
21
|
indent.ts | 100 | 100 | 100 | 100 |
|
|
22
|
-
print-ck.ts |
|
|
22
|
+
print-ck.ts | 89.09 | 87.56 | 100 | 97.36 | 91-92
|
|
23
23
|
print-contract.ts | 45.71 | 38.23 | 50 | 46.66 | 20,52-85
|
|
24
|
-
...t-operation.ts | 78.
|
|
24
|
+
...t-operation.ts | 78.41 | 70.42 | 95 | 86.75 | ...37,354,362-373
|
|
25
25
|
print-type.ts | 64.13 | 63.46 | 63.63 | 66.66 | ...02-103,148-151
|
|
26
26
|
-------------------|---------|----------|---------|---------|-------------------
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @contractkit/prettier-plugin-contractkit
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 85d7566: Let an operation emit more than one status, and a status serve more than one content type.
|
|
8
|
+
|
|
9
|
+
A status code could previously declare only one mime — the parser warned `Duplicate response body` and dropped the rest — and the generated router pinned both `ctx.status` and `ctx.type` to whichever response happened to be listed first with a body. An endpoint serving several formats had to declare one lying mime and let browsers sniff, and a service had no way to say which status it produced.
|
|
10
|
+
|
|
11
|
+
A status now holds every declared `mime: Type` line (`OpResponseNode.bodies`). When there is more than one, the service picks at runtime and the router sets `ctx.type` from the returned `contentType`. When an operation produces more than one status, the service returns a union discriminated on `status` and the handler switches on it, so each status writes only its own headers, mime and body. Both SDKs mirror the router: the TypeScript and Python clients return a matching union, report which mime came back, and pass the non-2xx statuses they expect to the shared fetch so a declared `304` no longer surfaces as an error. `SdkError` now takes a body type parameter, and each operation exports a `…ErrorBody` alias for the statuses that stay on the throw path.
|
|
12
|
+
|
|
13
|
+
Which statuses the service produces is derived from the declaration: **a status is emitted if it has a block, or is 2xx.** An empty block (`304: {}`) says the service returns that status carrying nothing; a bare `304:` says it is documented and something else produces it. `404(documented): { … }` is the one modifier, forcing a block-carrying status back out.
|
|
14
|
+
|
|
15
|
+
Two long-standing bugs in the same area go with it. The formatter deleted any comment written inside a `response` block — above a status code, above a mime line, above a `headers:` block, or before a closing brace — so `pnpm format` silently threw away the notes explaining why a contract looks the way it does; all four positions now round-trip. And the generated router declared a `_ZodBinary` helper for a binary _response_ body, which is a plain `Buffer` annotation with no schema behind it, leaving an unused const that tripped `noUnusedLocals` downstream; helpers are now chosen from the code that was actually generated, the same way imports already were.
|
|
16
|
+
|
|
17
|
+
**If you are already on a pre-release version, two things change under you.** A contract that declares a body on an error status — `404: { application/json: Problem }` alongside a `200` — now returns it from the service instead of throwing, and the SDK return type becomes a union; add `(documented)` to that status to keep the previous behaviour. Contracts whose error statuses are bodyless (`400:`, `404:`) are unaffected. Anything reading the AST directly should move from `OpResponseNode.contentType`/`bodyType` to `bodies`, which replaces them.
|
|
18
|
+
|
|
19
|
+
- 90d19ee: Allow a comment above `options`, and a trailing comment on an options entry.
|
|
20
|
+
|
|
21
|
+
A `#` comment above a `contract` or `operation` has always been fine, but one above the `options` keyword was a parse error — a file header is a natural thing to write, and writing it broke the build. `OptionsBlock` now owns the leading `comment*`. It lives there rather than on `Root` deliberately: on `Root` its greedy match would swallow the doc comment above a `contract` in a file that has no options block at all.
|
|
22
|
+
|
|
23
|
+
A trailing comment on a `keys`/`services` entry was worse than unsupported: it was swallowed into the value, and because an unquoted value ends at the first `}`, a comment containing one — `# interpolated as {{area}}` — closed the block early and silently mis-parsed the rest of the file. The value now ends at whitespace-then-`#`, and the comment is retained so the formatter round-trips it. A `#` with no space before it still belongs to the value, so the unquoted subpath form (`PetService: #modules/pet/pet.service.js`) is unaffected.
|
|
24
|
+
|
|
25
|
+
The TextMate grammar had the matching gap: its unquoted-value pattern accepted identifiers only, so an unquoted subpath fell through to the comment pattern and was coloured as though the parser ignored it.
|
|
26
|
+
|
|
27
|
+
An unquoted value also stays unquoted. The AST now records which entries were authored bare, so the formatter reproduces that choice instead of normalizing every value to the quoted form. The flag is formatting-only — both forms parse to the same string — and a value that could not be read back bare is still quoted, which covers values built programmatically rather than parsed.
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [85d7566]
|
|
32
|
+
- Updated dependencies [90d19ee]
|
|
33
|
+
- @contractkit/core@0.25.0
|
|
34
|
+
|
|
3
35
|
## 0.13.0
|
|
4
36
|
|
|
5
37
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -51,7 +51,11 @@ Formatting a well-formed `.ck` file leaves it byte-identical. The formatter deli
|
|
|
51
51
|
- **Comment placement.** A `#` block separated from the declaration below it by a blank line stays a standalone divider; one directly above becomes that declaration's doc comment and is re-emitted above it, not folded onto the header line. A comment written inline (`contract Pet: { # ...`) stays inline.
|
|
52
52
|
- **Operation body key order.** `sdk` before `service` stays that way; the printer never sorts a user's keys into a canonical order.
|
|
53
53
|
- **Blank lines** between operations inside a route.
|
|
54
|
-
- **Single-line response blocks.** `200: { application/json: Pet }` is not expanded, and an expanded block is not collapsed.
|
|
54
|
+
- **Single-line response blocks.** `200: { application/json: Pet }` is not expanded, and an expanded block is not collapsed. A status declaring several mimes on one line stays on one line too.
|
|
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
|
+
- **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
|
+
- **Comments in the `options` block**, including a header comment above the `options` keyword and a trailing comment on a `keys`/`services` entry.
|
|
58
|
+
- **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.
|
|
55
59
|
|
|
56
60
|
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.
|
|
57
61
|
|
package/dist/index.js
CHANGED
|
@@ -348,7 +348,7 @@ function printOperationKey(op, key) {
|
|
|
348
348
|
return lines;
|
|
349
349
|
}
|
|
350
350
|
case "responses":
|
|
351
|
-
return op.responses.length > 0 ? printResponseBlock(op.responses) : [];
|
|
351
|
+
return op.responses.length > 0 ? printResponseBlock(op.responses, op.responsesTrailingComments) : [];
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
__name(printOperationKey, "printOperationKey");
|
|
@@ -531,21 +531,29 @@ function printContentTypeLine(contentType, bodyType, lineIndent) {
|
|
|
531
531
|
];
|
|
532
532
|
}
|
|
533
533
|
__name(printContentTypeLine, "printContentTypeLine");
|
|
534
|
-
function printResponseBlock(responses) {
|
|
534
|
+
function printResponseBlock(responses, trailingComments) {
|
|
535
535
|
const lines = [
|
|
536
536
|
`${I2}response: {`
|
|
537
537
|
];
|
|
538
538
|
for (const resp of responses) {
|
|
539
|
-
const
|
|
539
|
+
for (const comment of resp.leadingComments ?? []) lines.push(`${I3}# ${comment}`);
|
|
540
|
+
const bodies = resp.bodies;
|
|
540
541
|
const hasHeaders = resp.headers && resp.headers.length > 0;
|
|
541
542
|
const optOut = resp.headersOptOut;
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
543
|
+
const code = resp.emit ? `${resp.statusCode}(${resp.emit})` : `${resp.statusCode}`;
|
|
544
|
+
const inlinable = resp.inline && bodies.length > 0 && !hasHeaders && !optOut && bodies.every((b) => b.bodyType.kind !== "inlineObject");
|
|
545
|
+
if (inlinable) {
|
|
546
|
+
const inner = bodies.map((b) => `${b.contentType}: ${printType(b.bodyType)}`).join(" ");
|
|
547
|
+
lines.push(`${I3}${code}: { ${inner} }`);
|
|
548
|
+
} else if (bodies.length === 0 && !hasHeaders && !optOut && resp.hasBlock) {
|
|
549
|
+
lines.push(`${I3}${code}: {}`);
|
|
550
|
+
} else if (bodies.length > 0 || hasHeaders || optOut || (resp.trailingComments?.length ?? 0) > 0) {
|
|
551
|
+
lines.push(`${I3}${code}: {`);
|
|
552
|
+
for (const body of bodies) {
|
|
553
|
+
for (const comment of body.leadingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
554
|
+
lines.push(...printContentTypeLine(body.contentType, body.bodyType, I4));
|
|
548
555
|
}
|
|
556
|
+
for (const comment of resp.headersLeadingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
549
557
|
if (optOut) {
|
|
550
558
|
lines.push(`${I4}headers: none`);
|
|
551
559
|
} else if (hasHeaders) {
|
|
@@ -557,11 +565,13 @@ function printResponseBlock(responses) {
|
|
|
557
565
|
}
|
|
558
566
|
lines.push(`${I4}}`);
|
|
559
567
|
}
|
|
568
|
+
for (const comment of resp.trailingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
560
569
|
lines.push(`${I3}}`);
|
|
561
570
|
} else {
|
|
562
|
-
lines.push(`${I3}${
|
|
571
|
+
lines.push(`${I3}${code}:`);
|
|
563
572
|
}
|
|
564
573
|
}
|
|
574
|
+
for (const comment of trailingComments ?? []) lines.push(`${I3}# ${comment}`);
|
|
565
575
|
lines.push(`${I2}}`);
|
|
566
576
|
return lines;
|
|
567
577
|
}
|
|
@@ -569,15 +579,19 @@ __name(printResponseBlock, "printResponseBlock");
|
|
|
569
579
|
|
|
570
580
|
// src/print-ck.ts
|
|
571
581
|
var DEFAULT_PRINT_WIDTH = 80;
|
|
572
|
-
function quoteOptionsValue(value) {
|
|
573
|
-
|
|
582
|
+
function quoteOptionsValue(value, wasUnquoted = false) {
|
|
583
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value)) return value;
|
|
584
|
+
const roundTripsBare = value.length > 0 && value === value.trim() && !/[\n\r}]/.test(value) && !/[ \t]#/.test(value) && !/^["']/.test(value);
|
|
585
|
+
return wasUnquoted && roundTripsBare ? value : `"${value}"`;
|
|
574
586
|
}
|
|
575
587
|
__name(quoteOptionsValue, "quoteOptionsValue");
|
|
576
|
-
function emitOptionsEntries(lines, entries, comments) {
|
|
588
|
+
function emitOptionsEntries(lines, entries, comments, unquoted) {
|
|
577
589
|
const I22 = INDENT + INDENT;
|
|
590
|
+
const wasUnquoted = new Set(unquoted ?? []);
|
|
578
591
|
for (const [key, value] of Object.entries(entries)) {
|
|
579
592
|
for (const c of comments?.leading?.[key] ?? []) lines.push(`${I22}# ${c}`);
|
|
580
|
-
|
|
593
|
+
const inline = comments?.inline?.[key];
|
|
594
|
+
lines.push(`${I22}${key}: ${quoteOptionsValue(value, wasUnquoted.has(key))}${inline !== void 0 ? ` # ${inline}` : ""}`);
|
|
581
595
|
}
|
|
582
596
|
for (const c of comments?.trailing ?? []) lines.push(`${I22}# ${c}`);
|
|
583
597
|
}
|
|
@@ -591,6 +605,7 @@ function printOptionsBlock(ast) {
|
|
|
591
605
|
const hasBodyComments = ast.optionsComments?.body !== void 0;
|
|
592
606
|
if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders && !hasBodyComments) return null;
|
|
593
607
|
const lines = [
|
|
608
|
+
...(ast.optionsComments?.leading ?? []).map((c) => `# ${c}`),
|
|
594
609
|
"options {"
|
|
595
610
|
];
|
|
596
611
|
const body = ast.optionsComments?.body;
|
|
@@ -600,13 +615,13 @@ function printOptionsBlock(ast) {
|
|
|
600
615
|
if (hasMeta) {
|
|
601
616
|
emitLeading("keys");
|
|
602
617
|
lines.push(`${INDENT}keys: {`);
|
|
603
|
-
emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys);
|
|
618
|
+
emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys, ast.optionsUnquoted?.keys);
|
|
604
619
|
lines.push(`${INDENT}}`);
|
|
605
620
|
}
|
|
606
621
|
if (hasServices) {
|
|
607
622
|
emitLeading("services");
|
|
608
623
|
lines.push(`${INDENT}services: {`);
|
|
609
|
-
emitOptionsEntries(lines, ast.services, ast.optionsComments?.services);
|
|
624
|
+
emitOptionsEntries(lines, ast.services, ast.optionsComments?.services, ast.optionsUnquoted?.services);
|
|
610
625
|
lines.push(`${INDENT}}`);
|
|
611
626
|
}
|
|
612
627
|
if (hasRequestHeaders) {
|
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) : [];\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[]): string[] {\n const lines: string[] = [`${I2}response: {`];\n\n for (const resp of responses) {\n const hasBody = resp.contentType && resp.bodyType;\n const hasHeaders = resp.headers && resp.headers.length > 0;\n const optOut = resp.headersOptOut;\n if (resp.inline && hasBody && !hasHeaders && !optOut && resp.bodyType!.kind !== 'inlineObject') {\n // Written on one line in the source, so keep it there: `200: { application/json: Pet }`.\n lines.push(`${I3}${resp.statusCode}: { ${resp.contentType}: ${printType(resp.bodyType!)} }`);\n } else if (hasBody || hasHeaders || optOut) {\n lines.push(`${I3}${resp.statusCode}: {`);\n if (hasBody) {\n lines.push(...printContentTypeLine(resp.contentType!, resp.bodyType!, I4));\n }\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 lines.push(`${I3}}`);\n } else {\n lines.push(`${I3}${resp.statusCode}:`);\n }\n }\n\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 * Quote an options-block value if it isn't a plain identifier.\n *\n * Plain identifiers (starts with letter/underscore/dollar, rest are\n * alphanumeric/underscore/dollar/hyphen/dot) are left bare. Everything\n * else — paths with slashes, values starting with `#`, values with spaces,\n * etc. — is double-quoted so the round-trip parse is unambiguous.\n */\nfunction quoteOptionsValue(value: string): string {\n return /^[a-zA-Z_$][a-zA-Z0-9_$\\-.]*$/.test(value) ? 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(lines: string[], entries: Record<string, string>, comments: OptionsScopeComments | undefined): void {\n const I2 = INDENT + INDENT;\n for (const [key, value] of Object.entries(entries)) {\n for (const c of comments?.leading?.[key] ?? []) lines.push(`${I2}# ${c}`);\n lines.push(`${I2}${key}: ${quoteOptionsValue(value)}`);\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 const lines: string[] = ['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);\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);\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":";;;;AACA,SAASA,gBAAgB;AACzB,SAASC,SAASC,2BAA2B;;;ACFtC,IAAMC,SAAS;;;ACMf,SAASC,UAAUC,MAAsB;AAC5C,UAAQA,KAAKC,MAAI;IACb,KAAK,UAAU;AACX,YAAMC,cAAwB,CAAA;AAC9B,UAAIF,KAAKG,WAAWC,QAAW;AAE3B,cAAMC,MAAML,KAAKG;AACjBD,oBAAYI,KAAK,sBAAsBC,KAAKF,GAAAA,IAAOA,MAAM,IAAIA,GAAAA,GAAM;MACvE;AACA,UAAIL,KAAKQ,QAAQJ,OAAWF,aAAYI,KAAK,OAAON,KAAKQ,GAAG,EAAE;AAC9D,UAAIR,KAAKS,QAAQL,OAAWF,aAAYI,KAAK,OAAON,KAAKS,GAAG,EAAE;AAC9D,UAAIT,KAAKU,QAAQN,OAAWF,aAAYI,KAAK,OAAON,KAAKU,GAAG,EAAE;AAC9D,UAAIV,KAAKW,UAAUP,OAAWF,aAAYI,KAAK,UAAUN,KAAKW,KAAK,GAAG;AACtE,aAAOT,YAAYU,SAAS,IAAI,GAAGZ,KAAKa,IAAI,IAAIX,YAAYY,KAAK,IAAA,CAAA,MAAWd,KAAKa;IACrF;IACA,KAAK,SAAS;AACV,YAAME,OAAiB;QAAChB,UAAUC,KAAKgB,IAAI;;AAC3C,UAAIhB,KAAKQ,QAAQJ,OAAWW,MAAKT,KAAK,OAAON,KAAKQ,GAAG,EAAE;AACvD,UAAIR,KAAKS,QAAQL,OAAWW,MAAKT,KAAK,OAAON,KAAKS,GAAG,EAAE;AACvD,aAAO,SAASM,KAAKD,KAAK,IAAA,CAAA;IAC9B;IACA,KAAK;AACD,aAAO,SAASd,KAAKiB,MAAMC,IAAInB,SAAAA,EAAWe,KAAK,IAAA,CAAA;IACnD,KAAK;AACD,aAAO,UAAUf,UAAUC,KAAKmB,GAAG,CAAA,KAAMpB,UAAUC,KAAKoB,KAAK,CAAA;IACjE,KAAK;AACD,aAAO,QAAQpB,KAAKqB,OAAOH,IAAII,eAAAA,EAAiBR,KAAK,IAAA,CAAA;IACzD,KAAK;AACD,aAAO,OAAOd,KAAKoB,UAAU,WAAW,YAAYpB,KAAKoB,KAAK,OAAO,WAAWpB,KAAKoB,KAAK;IAC9F,KAAK;AACD,aAAOpB,KAAKuB,QAAQL,IAAInB,SAAAA,EAAWe,KAAK,KAAA;IAC5C,KAAK;AACD,aAAO,oBAAoBd,KAAKwB,aAAa,KAAKxB,KAAKuB,QAAQL,IAAInB,SAAAA,EAAWe,KAAK,KAAA,CAAA;IACvF,KAAK;AACD,aAAOd,KAAKuB,QAAQL,IAAInB,SAAAA,EAAWe,KAAK,KAAA;IAC5C,KAAK;AACD,aAAOd,KAAKa;IAChB,KAAK;AACD,aAAOY,yBAAyBzB,IAAAA;IACpC,KAAK;AACD,aAAO,QAAQD,UAAUC,KAAK0B,KAAK,CAAA;EAC3C;AACJ;AA1CgB3B;AA6ChB,SAAS0B,yBAAyBE,KAAyB;AACvD,QAAMC,SAASD,IAAIE,OAAO,QAAQF,IAAIE,IAAI,OAAO;AACjD,MAAIF,IAAIG,OAAOlB,WAAW,EAAG,QAAO,GAAGgB,MAAAA;AACvC,QAAMG,QAAQJ,IAAIG,OAAOZ,IAAIc,CAAAA,MAAAA;AACzB,UAAMC,MAAMD,EAAEE,WAAW,MAAM;AAC/B,QAAIC,IAAIpC,UAAUiC,EAAEhC,IAAI;AACxB,QAAIgC,EAAEI,SAAUD,MAAK;AACrB,WAAO,GAAGH,EAAEnB,IAAI,GAAGoB,GAAAA,KAAQE,CAAAA;EAC/B,CAAA;AACA,SAAO,GAAGP,MAAAA,KAAWG,MAAMjB,KAAK,IAAA,CAAA;AACpC;AAVSW;AAaF,SAASY,kBAAkBhB,QAAkBiB,QAAc;AAC9D,QAAMC,cAAcD,SAASE;AAC7B,SAAO;EAAUnB,OAAOH,IAAIuB,CAAAA,MAAK,GAAGF,WAAAA,GAAcjB,gBAAgBmB,CAAAA,CAAAA,EAAI,EAAE3B,KAAK,KAAA,CAAA;EAAWwB,MAAAA;AAC5F;AAHgBD;AAST,SAASK,WAAWC,OAAkBL,QAAgBM,aAAqB,IAAE;AAChF,QAAMX,MAAMU,MAAMT,WAAW,MAAM;AACnC,QAAMW,MAAMF,MAAMG,WAAW,cAAc;AAC3C,QAAMC,MAAMJ,MAAMK,aAAa,gBAAgB;AAC/C,QAAMC,MAAMN,MAAMO,eAAe,WAAW,GAAGP,MAAMO,UAAU,MAAM;AACrE,QAAMC,OAAO,GAAGN,GAAAA,GAAME,GAAAA,GAAME,GAAAA;AAC5B,QAAMG,MAAMT,MAAMU,YAAYjD,SAAY,MAAMkD,cAAcX,MAAMU,OAAO,CAAA,KAAM;AACjF,QAAME,UAAUZ,MAAMa,cAAc,MAAMb,MAAMa,WAAW,KAAK;AAChE,QAAMjB,cAAcD,SAASE;AAI7B,MAAI,CAACG,MAAMP,YAAYO,MAAMU,YAAYjD,QAAW;AAChD,UAAMqD,WAAWC,4BAA4Bf,MAAM3C,IAAI;AACvD,QAAIyD,UAAU;AACV,YAAM,EAAE7B,QAAQ+B,UAAS,IAAKF;AAC9B,YAAMG,WAAWD,UAAU9B,OAAO,QAAQ8B,UAAU9B,IAAI,OAAO;AAC/D,YAAMgC,SAASjC,SACT,GAAGU,MAAAA,GAASK,MAAM9B,IAAI,GAAGoB,GAAAA,KAAQkB,IAAAA,GAAOvB,MAAAA,MAAYgC,QAAAA,IAAYL,OAAAA,KAChE,GAAGjB,MAAAA,GAASK,MAAM9B,IAAI,GAAGoB,GAAAA,KAAQkB,IAAAA,GAAOS,QAAAA,IAAYL,OAAAA;AAC1D,aAAO;QAACM;WAAWC,0BAA0BH,WAAWpB,aAAaK,UAAAA;QAAa,GAAGN,MAAAA;QAAWxB,KAAK,IAAA;IACzG;EACJ;AAEA,MAAIiD,UAAUhE,UAAU4C,MAAM3C,IAAI;AAClC,MAAI2C,MAAMP,SAAU2B,YAAW;AAC/B,QAAMC,WAAW,GAAG1B,MAAAA,GAASK,MAAM9B,IAAI,GAAGoB,GAAAA,KAAQkB,IAAAA,GAAOY,OAAAA,GAAUX,GAAAA,GAAMG,OAAAA;AACzE,MAAIZ,MAAM3C,KAAKC,SAAS,UAAU,CAAC0C,MAAMP,YAAYO,MAAMU,YAAYjD,UAAa4D,SAASpD,SAASgC,YAAY;AAC9G,UAAMqB,UAAU5B,kBAAkBM,MAAM3C,KAAKqB,QAAQiB,MAAAA;AACrD,WAAO,GAAGA,MAAAA,GAASK,MAAM9B,IAAI,GAAGoB,GAAAA,KAAQkB,IAAAA,GAAOc,OAAAA,GAAUV,OAAAA;EAC7D;AACA,SAAOS;AACX;AAhCgBtB;AAqCT,SAASoB,0BAA0BnC,KAA2BW,QAAgBM,aAAqB,IAAE;AACxG,QAAMsB,QAAQvC,IAAIG,OAAOZ,IAAIc,CAAAA,MAAKU,WAAWV,GAAGM,QAAQM,UAAAA,CAAAA;AACxD,aAAWW,WAAW5B,IAAIwC,oBAAoB,CAAA,GAAI;AAC9CD,UAAM5D,KAAK,GAAGgC,MAAAA,KAAWiB,OAAAA,EAAS;EACtC;AACA,SAAOW;AACX;AANgBJ;AAWT,SAASxC,gBAAgBmB,GAAS;AACrC,MAAI,gCAAgClC,KAAKkC,CAAAA,EAAI,QAAOA;AACpD,SAAO,IAAIA,CAAAA;AACf;AAHgBnB;AAMT,SAASgC,cAAcc,KAA8B;AACxD,MAAI,OAAOA,QAAQ,YAAY,OAAOA,QAAQ,UAAW,QAAOC,OAAOD,GAAAA;AAEvE,MAAI,gCAAgC7D,KAAK6D,GAAAA,EAAM,QAAOA;AACtD,SAAO,IAAIA,GAAAA;AACf;AALgBd;AAYT,SAASI,4BAA4B1D,MAAsB;AAI9D,MAAIA,KAAKC,SAAS,gBAAgB;AAC9B,WAAO;MAAE2B,QAAQ;MAAM+B,WAAW3D;IAAK;EAC3C;AACA,MAAIA,KAAKC,SAAS,gBAAgB;AAC9B,UAAMqE,OAAOtE,KAAKuB,QAAQvB,KAAKuB,QAAQX,SAAS,CAAA;AAChD,QAAI0D,MAAMrE,SAAS,gBAAgB;AAC/B,YAAMsE,YAAYvE,KAAKuB,QAAQiD,MAAM,GAAG,EAAC,EAAGtD,IAAInB,SAAAA,EAAWe,KAAK,KAAA;AAChE,aAAO;QAAEc,QAAQ2C;QAAWZ,WAAWW;MAAK;IAChD;EACJ;AACA,SAAO;AACX;AAfgBZ;;;AC3HT,SAASe,eAAeC,OAAkBC,aAAqB,IAAE;AAEpE,MAAID,MAAME,SAASC,QAAW;AAC1B,WAAOC,eAAeJ,OAAOC,UAAAA;EACjC;AAKA,QAAMI,gBAAgBL,MAAMM,eAAeN,MAAMO,oBAAoB,MAAMP,MAAMM,WAAW,KAAK;AACjG,QAAME,YAAY;IACdR,MAAMS,aAAa,eAAe;IAClCT,MAAMU,aAAaV,MAAMW,aACnB,UAAU;MAACX,MAAMU,YAAY,SAASV,MAAMU,SAAS,KAAK;MAAIV,MAAMW,aAAa,UAAUX,MAAMW,UAAU,KAAK;MAAIC,OAAOC,OAAAA,EAASC,KAAK,IAAA,CAAA,MACzI;IACNd,MAAMe,OAAO,QAAQf,MAAMe,IAAI,MAAM;IAEpCH,OAAOC,OAAAA,EACPC,KAAK,GAAA;AACV,QAAME,aAAaR,YAAY,GAAGA,SAAAA,MAAe;AACjD,QAAMS,YAAYjB,MAAMkB,SAASlB,MAAMkB,MAAMC,SAAS,IAAI,GAAGnB,MAAMkB,MAAMJ,KAAK,KAAA,CAAA,QAAc;AAC5F,QAAMM,SAAS,GAAGJ,UAAAA,GAAahB,MAAMqB,IAAI,KAAKJ,SAAAA,IAAaZ,aAAAA;AAE3D,QAAMiB,QAAkB;IAACF;;AACzB,aAAWG,SAASvB,MAAMwB,QAAQ;AAC9BF,UAAMG,KAAKC,WAAWH,OAAOI,QAAQ1B,UAAAA,CAAAA;EACzC;AACA,aAAW2B,WAAW5B,MAAM6B,oBAAoB,CAAA,GAAI;AAChDP,UAAMG,KAAK,GAAGE,MAAAA,KAAWC,OAAAA,EAAS;EACtC;AACAN,QAAMG,KAAK,GAAA;AACX,SAAOH,MAAMR,KAAK,IAAA;AACtB;AAhCgBf;AAkChB,SAASK,eAAeJ,OAAkBC,YAAkB;AACxD,QAAMC,OAAOF,MAAME;AAGnB,QAAMG,gBAAgBL,MAAMM,eAAeN,MAAMO,oBAAoB,MAAMP,MAAMM,WAAW,KAAK;AACjG,QAAME,YAAY;IACdR,MAAMS,aAAa,eAAe;IAClCT,MAAMU,aAAaV,MAAMW,aACnB,UAAU;MAACX,MAAMU,YAAY,SAASV,MAAMU,SAAS,KAAK;MAAIV,MAAMW,aAAa,UAAUX,MAAMW,UAAU,KAAK;MAAIC,OAAOC,OAAAA,EAASC,KAAK,IAAA,CAAA,MACzI;IACNd,MAAMe,OAAO,QAAQf,MAAMe,IAAI,MAAM;IAEpCH,OAAOC,OAAAA,EACPC,KAAK,GAAA;AACV,QAAME,aAAaR,YAAY,GAAGA,SAAAA,MAAe;AAGjD,QAAMsB,WAAWC,4BAA4B7B,IAAAA;AAC7C,MAAI4B,UAAU;AACV,UAAM,EAAEE,QAAQC,UAAS,IAAKH;AAC9B,UAAMI,WAAWD,UAAUlB,OAAO,QAAQkB,UAAUlB,IAAI,OAAO;AAC/D,UAAMK,SAASY,SACT,GAAGhB,UAAAA,GAAahB,MAAMqB,IAAI,KAAKW,MAAAA,MAAYE,QAAAA,IAAY7B,aAAAA,KACvD,GAAGW,UAAAA,GAAahB,MAAMqB,IAAI,KAAKa,QAAAA,IAAY7B,aAAAA;AACjD,UAAMiB,QAAkB;MAACF;SAAWe,0BAA0BF,WAAWN,QAAQ1B,UAAAA;MAAa;;AAC9F,WAAOqB,MAAMR,KAAK,IAAA;EACtB;AAIA,QAAMsB,aAAa,GAAGpB,UAAAA,GAAahB,MAAMqB,IAAI,KAAKgB,UAAUnC,IAAAA,CAAAA,GAAQG,aAAAA;AACpE,MAAIH,KAAKoC,SAAS,UAAU,YAAYnB,SAASiB,WAAWjB,SAASlB,YAAY;AAC7E,WAAO,GAAGe,UAAAA,GAAahB,MAAMqB,IAAI,KAAKkB,kBAAkBrC,KAAKsC,QAAQ,EAAA,CAAA,GAAMnC,aAAAA;EAC/E;AACA,SAAO+B;AACX;AAnCShC;;;ACrCT,SAASqC,qBAAqB;AAI9B,IAAMC,KAAKC;AACX,IAAMC,KAAKD,OAAOE,OAAO,CAAA;AACzB,IAAMC,KAAKH,OAAOE,OAAO,CAAA;AACzB,IAAME,KAAKJ,OAAOE,OAAO,CAAA;AA4BlB,SAASG,YAAYC,KAAeC,QAAwBC,KAAwBC,YAAoBC,UAAU,IAAE;AACvH,SAAOF,IAAIG,QAAQJ,OAAOK,UAAUL,OAAOC,IAAIG,KAAK,EAAGE,YAAYJ,YAAY;AAC3E,eAAWK,KAAKP,OAAOC,IAAIG,KAAK,EAAGI,MAAOT,KAAIU,KAAKF,CAAAA;AACnDN,QAAIG;EACR;AACJ;AALgBN;AAqBT,SAASY,WAAWC,OAAoBX,QAAwBC,KAAwBW,gBAAsB;AACjH,QAAMJ,QAAkB,CAAA;AACxBA,QAAMC,KAAK,GAAGE,MAAME,IAAI,KAAK;AAE7B,MAAIF,MAAMG,WAAWC,QAAW;AAC5BP,UAAMC,KAAI,GAAIO,iBAAiBL,MAAMG,QAAQG,IAAIN,MAAMO,UAAU,CAAA;EACrE;AAEA,MAAIP,MAAMQ,aAAaJ,QAAW;AAC9BP,UAAMC,KAAI,GAAIW,cAAcT,MAAMQ,UAAUF,IAAII,EAAAA,CAAAA;EACpD;AAEA,aAAWC,MAAMX,MAAMY,YAAY;AAE/B,QAAID,GAAGE,mBAAmBhB,MAAMH,SAAS,EAAGG,OAAMC,KAAK,EAAA;AAEvDX,gBAAYU,OAAOR,QAAQC,KAAKqB,GAAGG,IAAIC,MAAMT,EAAAA;AAC7CT,UAAMC,KAAI,GAAIkB,eAAeL,EAAAA,CAAAA;EACjC;AAGAxB,cAAYU,OAAOR,QAAQC,KAAKW,gBAAgBK,EAAAA;AAGhD,aAAWW,WAAWjB,MAAMkB,oBAAoB,CAAA,GAAI;AAChDrB,UAAMC,KAAK,GAAGQ,EAAAA,KAAOW,OAAAA,EAAS;EAClC;AAEApB,QAAMC,KAAK,GAAA;AACX,SAAOD,MAAMsB,KAAK,IAAA;AACtB;AA9BgBpB;AAkChB,SAASM,iBAAiBe,QAAqBC,QAAgBC,MAAiB;AAC5E,QAAMC,SAASD,OAAO,QAAQA,IAAAA,OAAW;AACzC,MAAIF,OAAOI,SAAS,OAAO;AACvB,WAAO;MAAC,GAAGH,MAAAA,GAASE,MAAAA,WAAiBH,OAAOK,IAAI;;EACpD;AACA,MAAIL,OAAOI,SAAS,UAAU;AAC1B,UAAM3B,QAAkB;MAAC,GAAGwB,MAAAA,GAASE,MAAAA;;AACrC,UAAMG,QAAQL,SAASM;AACvB,eAAWC,KAAKR,OAAOS,OAAO;AAC1B,YAAMC,MAAMF,EAAEG,WAAW,MAAM;AAC/B,UAAIC,IAAIC,UAAUL,EAAEM,IAAI;AACxB,UAAIN,EAAEO,SAAUH,MAAK;AACrB,YAAMI,MAAMR,EAAES,YAAYjC,SAAY,MAAMkC,cAAcV,EAAES,OAAO,CAAA,KAAM;AACzE,YAAMpB,UAAUW,EAAEW,cAAc,MAAMX,EAAEW,WAAW,KAAK;AACxD1C,YAAMC,KAAK,GAAG4B,KAAAA,GAAQE,EAAEH,IAAI,GAAGK,GAAAA,KAAQE,CAAAA,GAAII,GAAAA,GAAMnB,OAAAA,EAAS;IAC9D;AACApB,UAAMC,KAAK,GAAGuB,MAAAA,GAAS;AACvB,WAAOxB;EACX;AAEA,SAAO;IAAC,GAAGwB,MAAAA,GAASE,MAAAA,WAAiBU,UAAUb,OAAOoB,IAAI,CAAA;;AAC9D;AArBSnC;AA0BT,IAAMoC,sBAAmC;EAAC;EAAQ;EAAW;EAAO;EAAO;EAAa;EAAY;EAAW;EAAS;EAAW;EAAW;;AAG9I,SAASC,kBAAkB/B,IAAqBgC,KAAc;AAC1D,UAAQA,KAAAA;IACJ,KAAK;AACD,aAAOhC,GAAGc,OAAO;QAAC,GAAGf,EAAAA,SAAWC,GAAGc,IAAI;UAAM,CAAA;IACjD,KAAK;AACD,aAAOd,GAAGiC,UAAU;QAAC,GAAGlC,EAAAA,YAAcC,GAAGiC,OAAO;UAAM,CAAA;IAC1D,KAAK;AACD,aAAOjC,GAAGkC,MAAM;QAAC,GAAGnC,EAAAA,QAAUC,GAAGkC,GAAG;UAAM,CAAA;IAC9C,KAAK;AACD,UAAIlC,GAAGmC,QAAQ,KAAM,QAAO;QAAC,GAAGpC,EAAAA;;AAChC,UAAIC,GAAGmC,QAAQ,MAAO,QAAO;QAAC,GAAGpC,EAAAA;;AACjC,aAAOC,GAAGmC,MAAMC,cAAcpC,GAAGmC,GAAG,IAAI,CAAA;IAC5C,KAAK,aAAa;AACd,UAAI,CAACnC,GAAGqC,UAAW,QAAO,CAAA;AAC1B,YAAM/B,UAAUN,GAAGsC,uBAAuB,MAAMtC,GAAGsC,oBAAoB,KAAK;AAC5E,UAAItC,GAAGuC,iBAAiB;AACpB,eAAO;UACH,GAAGxC,EAAAA;UACH,GAAGyC,EAAAA,YAAcC,qBAAqBzC,GAAGqC,SAAS,CAAA,GAAI/B,OAAAA;UACtD,GAAGkC,EAAAA,WAAaxC,GAAGuC,eAAe;UAClC,GAAGxC,EAAAA;;MAEX;AACA,aAAO;QAAC,GAAGA,EAAAA,cAAgB0C,qBAAqBzC,GAAGqC,SAAS,CAAA,GAAI/B,OAAAA;;IACpE;IACA,KAAK;AACD,aAAON,GAAGH,aAAaJ,SAAYK,cAAcE,GAAGH,QAAQ,IAAI,CAAA;IACpE,KAAK,WAAW;AACZ,UAAI,CAACG,GAAG0C,WAAWC,OAAOC,KAAK5C,GAAG0C,OAAO,EAAE3D,WAAW,EAAG,QAAO,CAAA;AAChE,YAAMG,QAAQ;QAAC,GAAGa,EAAAA;;AAClB,iBAAW,CAAC8C,GAAGC,GAAAA,KAAQH,OAAOI,QAAQ/C,GAAG0C,OAAO,EAAGxD,OAAMC,KAAI,GAAI6D,iBAAiBH,GAAGC,KAAKN,EAAAA,CAAAA;AAC1FtD,YAAMC,KAAK,GAAGY,EAAAA,GAAK;AACnB,aAAOb;IACX;IACA,KAAK;AACD,aAAOc,GAAGiD,UAAUxD,SAAYyD,oBAAoB,SAASlD,GAAGiD,OAAOjD,GAAGmD,SAAS,IAAI,CAAA;IAC3F,KAAK;AACD,UAAInD,GAAGoD,qBAAsB,QAAO;QAAC,GAAGrD,EAAAA;;AACxC,aAAOC,GAAGqD,YAAY5D,SAAYyD,oBAAoB,WAAWlD,GAAGqD,SAASrD,GAAGsD,WAAW,IAAI,CAAA;IACnG,KAAK,WAAW;AACZ,UAAI,CAACtD,GAAGuD,QAAS,QAAO,CAAA;AACxB,YAAMrE,QAAQ;QAAC,GAAGa,EAAAA;;AAClB,iBAAWyD,QAAQxD,GAAGuD,QAAQE,OAAQvE,OAAMC,KAAI,GAAIuE,qBAAqBF,KAAKG,aAAaH,KAAKI,UAAUpB,EAAAA,CAAAA;AAC1GtD,YAAMC,KAAK,GAAGY,EAAAA,GAAK;AACnB,aAAOb;IACX;IACA,KAAK;AACD,aAAOc,GAAG6D,UAAU9E,SAAS,IAAI+E,mBAAmB9D,GAAG6D,SAAS,IAAI,CAAA;EAC5E;AACJ;AAjDS9B;AAmDT,SAAS1B,eAAeL,IAAmB;AACvC,QAAMd,QAAkB,CAAA;AACxB,QAAM6E,UAAU/D,GAAGgE,WAAWjF,SAAS,IAAIiB,GAAGgE,UAAU,CAAA,CAAE,MAAM;AAKhE,QAAMC,oBAAoBjE,GAAGkE,qBAAqB;AAClD,MAAIlE,GAAG4B,eAAe,CAACqC,mBAAmB;AACtC,eAAW7D,QAAQJ,GAAG4B,YAAYuC,MAAM,IAAA,EAAOjF,OAAMC,KAAK,GAAGQ,EAAAA,KAAOS,IAAAA,EAAM;EAC9E;AACA,QAAMgE,gBAAgBpE,GAAG4B,eAAeqC,oBAAoB,MAAMjE,GAAG4B,WAAW,KAAK;AACrF1C,QAAMC,KAAK,GAAGQ,EAAAA,GAAKK,GAAGqE,MAAM,GAAGN,OAAAA,MAAaK,aAAAA,EAAe;AAI3D,QAAME,QAAQtE,GAAGuE,YAAY,CAAA;AAC7B,QAAMC,OAAO1C,oBAAoB2C,OAAO5B,CAAAA,MAAK,CAACyB,MAAMI,SAAS7B,CAAAA,CAAAA;AAC7D,aAAWb,OAAO;OAAIsC;OAAUE;KAAO;AACnCtF,UAAMC,KAAI,GAAI4C,kBAAkB/B,IAAIgC,GAAAA,CAAAA;EACxC;AAEA9C,QAAMC,KAAK,GAAGQ,EAAAA,GAAK;AACnB,SAAOT;AACX;AAxBSmB;AAgCT,SAASsE,cAAcxC,KAAkB;AACrC,QAAMyC,SAAmB,CAAA;AACzB,MAAIzC,IAAI0C,iBAAiBpF,OAAWmF,QAAOzF,KAAKgD,IAAI0C,eAAe,aAAa,aAAA;AAChF,MAAI1C,IAAI2C,mBAAmBrF,OAAWmF,QAAOzF,KAAKgD,IAAI2C,iBAAiB,eAAe,eAAA;AACtF,MAAI3C,IAAI4C,oBAAoBtF,OAAWmF,QAAOzF,KAAKgD,IAAI4C,kBAAkB,gBAAgB,gBAAA;AACzF,MAAI5C,IAAI6C,kBAAkBvF,OAAWmF,QAAOzF,KAAKgD,IAAI6C,gBAAgB,cAAc,aAAA;AACnF,SAAOJ;AACX;AAPSD;AAUT,SAASvC,cAAcD,KAAkB;AACrC,QAAMjD,QAAkB;IAAC,GAAGa,EAAAA;;AAC5B,MAAIoC,IAAIrB,SAASrB,OAAWP,OAAMC,KAAK,GAAGqD,EAAAA,UAAYyC,aAAa9C,IAAIrB,IAAI,CAAA,GAAI;AAC/E,MAAIqB,IAAI+C,UAAUzF,OAAWP,OAAMC,KAAK,GAAGqD,EAAAA,WAAayC,aAAa9C,IAAI+C,KAAK,CAAA,GAAI;AAClF,MAAI/C,IAAIP,gBAAgBnC,OAAWP,OAAMC,KAAK,GAAGqD,EAAAA,iBAAmByC,aAAa9C,IAAIP,WAAW,CAAA,GAAI;AACpG,QAAMgD,SAASD,cAAcxC,GAAAA;AAC7B,MAAIyC,OAAO7F,SAAS,EAAGG,OAAMC,KAAK,GAAGqD,EAAAA,SAAWoC,OAAOpE,KAAK,IAAA,CAAA,EAAO;AACnEtB,QAAMC,KAAK,GAAGY,EAAAA,GAAK;AACnB,SAAOb;AACX;AATSkD;AAaT,IAAM+C,WAAW;AAEjB,SAASF,aAAaG,GAAS;AAC3B,SAAOA,EAAEC,QAAQ,OAAO,MAAA,EAAQA,QAAQ,MAAM,KAAA;AAClD;AAFSJ;AAIT,SAASjC,iBAAiBhB,KAAalD,OAAoB4B,QAAc;AACrE,QAAMxB,QAAkB,CAAA;AACxB,QAAMoG,SAASC,kBAAkBzG,KAAAA;AACjC,MAAIwG,WAAW,MAAM;AACjBpG,UAAMC,KAAK,GAAGuB,MAAAA,GAASsB,GAAAA,KAAQsD,MAAAA,EAAQ;EAC3C,OAAO;AACH,UAAME,OAAO,GAAG9E,MAAAA,GAASsB,GAAAA;AACzB,UAAMyD,QAAQC,iBAAiB5G,OAAO4B,MAAAA;AACtCxB,UAAMC,KAAK,GAAGqG,IAAAA,GAAOC,MAAM,CAAA,EAAIE,UAAS,CAAA,EAAI;AAC5C,aAASC,IAAI,GAAGA,IAAIH,MAAM1G,QAAQ6G,IAAK1G,OAAMC,KAAKsG,MAAMG,CAAAA,CAAE;EAC9D;AACA,SAAO1G;AACX;AAZS8D;AAcT,SAASuC,kBAAkBzG,OAAkB;AACzC,MAAI,OAAOA,UAAU,SAAU,QAAO,IAAImG,aAAanG,KAAAA,CAAAA;AACvD,MAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAAW,QAAO+G,OAAO/G,KAAAA;AAC3E,MAAIA,UAAU,KAAM,QAAO;AAC3B,MAAIgH,MAAMC,QAAQjH,KAAAA,KAAUA,MAAMC,WAAW,EAAG,QAAO;AACvD,MAAI,CAAC+G,MAAMC,QAAQjH,KAAAA,KAAU,OAAOA,UAAU,YAAY6D,OAAOC,KAAK9D,KAAAA,EAAOC,WAAW,EAAG,QAAO;AAClG,SAAO;AACX;AAPSwG;AAST,SAASG,iBAAiB5G,OAAoB4B,QAAc;AACxD,QAAMK,QAAQL,SAASM;AACvB,QAAM9B,QAAkB,CAAA;AACxB,MAAI4G,MAAMC,QAAQjH,KAAAA,GAAQ;AACtBI,UAAMC,KAAK,GAAGuB,MAAAA,GAAS;AACvB,eAAWsF,QAAQlH,OAAO;AACtB,YAAMwG,SAASC,kBAAkBS,IAAAA;AACjC,UAAIV,WAAW,MAAM;AACjBpG,cAAMC,KAAK,GAAG4B,KAAAA,GAAQuE,MAAAA,EAAQ;MAClC,OAAO;AACH,cAAMG,QAAQC,iBAAiBM,MAAMjF,KAAAA;AACrC,mBAAW9B,KAAKwG,MAAOvG,OAAMC,KAAKF,CAAAA;MACtC;IACJ;AACAC,UAAMC,KAAK,GAAGuB,MAAAA,GAAS;AACvB,WAAOxB;EACX;AACA,MAAI,OAAOJ,UAAU,YAAYA,UAAU,MAAM;AAC7CI,UAAMC,KAAK,GAAGuB,MAAAA,GAAS;AACvB,eAAW,CAACmC,GAAGoD,CAAAA,KAAMtD,OAAOI,QAAQjE,KAAAA,GAAQ;AACxC,YAAMoH,WAAWf,SAASgB,KAAKtD,CAAAA,IAAKA,IAAI,IAAIoC,aAAapC,CAAAA,CAAAA;AACzD,YAAMyC,SAASC,kBAAkBU,CAAAA;AACjC,UAAIX,WAAW,MAAM;AACjBpG,cAAMC,KAAK,GAAG4B,KAAAA,GAAQmF,QAAAA,KAAaZ,MAAAA,EAAQ;MAC/C,OAAO;AACH,cAAMG,QAAQC,iBAAiBO,GAAGlF,KAAAA;AAClC7B,cAAMC,KAAK,GAAG4B,KAAAA,GAAQmF,QAAAA,KAAaT,MAAM,CAAA,EAAIE,UAAS,CAAA,EAAI;AAC1D,iBAASC,IAAI,GAAGA,IAAIH,MAAM1G,QAAQ6G,IAAK1G,OAAMC,KAAKsG,MAAMG,CAAAA,CAAE;MAC9D;IACJ;AACA1G,UAAMC,KAAK,GAAGuB,MAAAA,GAAS;AACvB,WAAOxB;EACX;AAEA,SAAO;IAAC,GAAGwB,MAAAA,GAAS6E,kBAAkBzG,KAAAA,CAAAA;;AAC1C;AAnCS4G;AAwCT,SAASjD,qBAAqB3D,OAAa;AACvC,SAAO,6BAA6BqH,KAAKrH,KAAAA,IAASA,QAAQ,IAAIA,KAAAA;AAClE;AAFS2D;AAYF,SAAS3C,cAAcD,UAAwBa,SAASX,IAAIqG,cAAc5D,IAAE;AAC/E,MAAI3C,aAAawG,cAAe,QAAO;IAAC,GAAG3F,MAAAA;;AAC3C,QAAM4F,SAASzG;AACf,MAAIyG,OAAOC,WAAW9G,OAAW,QAAO,CAAA;AACxC,QAAMP,QAAQ;IAAC,GAAGwB,MAAAA;;AAClB,QAAMJ,UAAUgG,OAAOE,oBAAoB,MAAMF,OAAOE,iBAAiB,KAAK;AAC9E,QAAM1H,QAAQwH,OAAOC,WAAW,QAAQ,SAASD,OAAOC;AACxDrH,QAAMC,KAAK,GAAGiH,WAAAA,WAAsBtH,KAAAA,GAAQwB,OAAAA,EAAS;AACrDpB,QAAMC,KAAK,GAAGuB,MAAAA,GAAS;AACvB,SAAOxB;AACX;AAVgBY;AAchB,SAASoD,oBAAoBuD,SAA8BhG,QAAqBE,MAAiB;AAC7F,QAAMC,SAASD,OAAO,QAAQA,IAAAA,OAAW;AACzC,MAAIF,OAAOI,SAAS,OAAO;AACvB,WAAO;MAAC,GAAGd,EAAAA,GAAKa,MAAAA,GAAS6F,OAAAA,KAAYhG,OAAOK,IAAI;;EACpD;AACA,MAAIL,OAAOI,SAAS,UAAU;AAC1B,QAAIJ,OAAOS,MAAMnC,WAAW,EAAG,QAAO,CAAA;AACtC,UAAMG,QAAkB;MAAC,GAAGa,EAAAA,GAAKa,MAAAA,GAAS6F,OAAAA;;AAC1C,eAAWxF,KAAKR,OAAOS,OAAO;AAC1B,YAAMC,MAAMF,EAAEG,WAAW,MAAM;AAC/B,UAAIC,IAAIC,UAAUL,EAAEM,IAAI;AACxB,UAAIN,EAAEO,SAAUH,MAAK;AACrB,YAAMI,MAAMR,EAAES,YAAYjC,SAAY,MAAMkC,cAAcV,EAAES,OAAO,CAAA,KAAM;AACzE,YAAMpB,UAAUW,EAAEW,cAAc,MAAMX,EAAEW,WAAW,KAAK;AACxD1C,YAAMC,KAAK,GAAGqD,EAAAA,GAAKvB,EAAEH,IAAI,GAAGK,GAAAA,KAAQE,CAAAA,GAAII,GAAAA,GAAMnB,OAAAA,EAAS;IAC3D;AACApB,UAAMC,KAAK,GAAGY,EAAAA,GAAK;AACnB,WAAOb;EACX;AAEA,SAAO;IAAC,GAAGa,EAAAA,GAAKa,MAAAA,GAAS6F,OAAAA,KAAYnF,UAAUb,OAAOoB,IAAI,CAAA;;AAC9D;AArBSqB;AA0BT,SAASQ,qBAAqBC,aAAqBC,UAA4B8C,YAAkB;AAC7F,MAAI9C,SAAS/C,SAAS,gBAAgB;AAClC,UAAM8F,cAAcD,aAAa1F;AACjC,UAAM9B,QAAkB;MAAC,GAAGwH,UAAAA,GAAa/C,WAAAA;;AACzC,eAAWiD,KAAKhD,SAAS0C,QAAQ;AAC7B,YAAMnF,MAAMyF,EAAExF,WAAW,MAAM;AAC/B,UAAIC,IAAIC,UAAUsF,EAAErF,IAAI;AACxB,UAAIqF,EAAEpF,SAAUH,MAAK;AACrB,YAAMI,MAAMmF,EAAElF,YAAYjC,SAAY,MAAMkC,cAAciF,EAAElF,OAAO,CAAA,KAAM;AACzE,YAAMpB,UAAUsG,EAAEhF,cAAc,MAAMgF,EAAEhF,WAAW,KAAK;AACxD1C,YAAMC,KAAK,GAAGwH,WAAAA,GAAcC,EAAE9F,IAAI,GAAGK,GAAAA,KAAQE,CAAAA,GAAII,GAAAA,GAAMnB,OAAAA,EAAS;IACpE;AACApB,UAAMC,KAAK,GAAGuH,UAAAA,GAAa;AAC3B,WAAOxH;EACX;AACA,SAAO;IAAC,GAAGwH,UAAAA,GAAa/C,WAAAA,KAAgBrC,UAAUsC,QAAAA,CAAAA;;AACtD;AAhBSF;AAoBT,SAASI,mBAAmBD,WAA2B;AACnD,QAAM3E,QAAkB;IAAC,GAAGa,EAAAA;;AAE5B,aAAW8G,QAAQhD,WAAW;AAC1B,UAAMiD,UAAUD,KAAKlD,eAAekD,KAAKjD;AACzC,UAAMmD,aAAaF,KAAKxD,WAAWwD,KAAKxD,QAAQtE,SAAS;AACzD,UAAMiI,SAASH,KAAKI;AACpB,QAAIJ,KAAKvB,UAAUwB,WAAW,CAACC,cAAc,CAACC,UAAUH,KAAKjD,SAAU/C,SAAS,gBAAgB;AAE5F3B,YAAMC,KAAK,GAAGqD,EAAAA,GAAKqE,KAAKK,UAAU,OAAOL,KAAKlD,WAAW,KAAKrC,UAAUuF,KAAKjD,QAAQ,CAAA,IAAM;IAC/F,WAAWkD,WAAWC,cAAcC,QAAQ;AACxC9H,YAAMC,KAAK,GAAGqD,EAAAA,GAAKqE,KAAKK,UAAU,KAAK;AACvC,UAAIJ,SAAS;AACT5H,cAAMC,KAAI,GAAIuE,qBAAqBmD,KAAKlD,aAAckD,KAAKjD,UAAWuD,EAAAA,CAAAA;MAC1E;AACA,UAAIH,QAAQ;AACR9H,cAAMC,KAAK,GAAGgI,EAAAA,eAAiB;MACnC,WAAWJ,YAAY;AACnB7H,cAAMC,KAAK,GAAGgI,EAAAA,YAAc;AAC5B,mBAAWC,KAAKP,KAAKxD,SAAU;AAC3B,gBAAMlC,MAAMiG,EAAEhG,WAAW,MAAM;AAC/B,gBAAMiG,QAAQD,EAAExF,cAAc,MAAMwF,EAAExF,WAAW,KAAK;AACtD1C,gBAAMC,KAAK,GAAGgI,EAAAA,GAAKnG,MAAAA,GAASoG,EAAEtG,IAAI,GAAGK,GAAAA,KAAQG,UAAU8F,EAAE7F,IAAI,CAAA,GAAI8F,KAAAA,EAAO;QAC5E;AACAnI,cAAMC,KAAK,GAAGgI,EAAAA,GAAK;MACvB;AACAjI,YAAMC,KAAK,GAAGqD,EAAAA,GAAK;IACvB,OAAO;AACHtD,YAAMC,KAAK,GAAGqD,EAAAA,GAAKqE,KAAKK,UAAU,GAAG;IACzC;EACJ;AAEAhI,QAAMC,KAAK,GAAGY,EAAAA,GAAK;AACnB,SAAOb;AACX;AAlCS4E;;;ACpXF,IAAMwD,sBAAsB;AAYnC,SAASC,kBAAkBC,OAAa;AACpC,SAAO,gCAAgCC,KAAKD,KAAAA,IAASA,QAAQ,IAAIA,KAAAA;AACrE;AAFSD;AAST,SAASG,mBAAmBC,OAAiBC,SAAiCC,UAA0C;AACpH,QAAMC,MAAKC,SAASA;AACpB,aAAW,CAACC,KAAKR,KAAAA,KAAUS,OAAOL,QAAQA,OAAAA,GAAU;AAChD,eAAWM,KAAKL,UAAUM,UAAUH,GAAAA,KAAQ,CAAA,EAAIL,OAAMS,KAAK,GAAGN,GAAAA,KAAOI,CAAAA,EAAG;AACxEP,UAAMS,KAAK,GAAGN,GAAAA,GAAKE,GAAAA,KAAQT,kBAAkBC,KAAAA,CAAAA,EAAQ;EACzD;AACA,aAAWU,KAAKL,UAAUQ,YAAY,CAAA,EAAIV,OAAMS,KAAK,GAAGN,GAAAA,KAAOI,CAAAA,EAAG;AACtE;AAPSR;AAST,SAASY,kBAAkBC,KAAe;AACtC,QAAMC,UAAUP,OAAOQ,KAAKF,IAAIG,IAAI,EAAEC,SAAS;AAC/C,QAAMC,cAAcX,OAAOQ,KAAKF,IAAIM,QAAQ,EAAEF,SAAS;AACvD,QAAMG,cAAcP,IAAIQ,aAAaC;AACrC,QAAMC,qBAAqBV,IAAIW,gBAAgBP,UAAU,KAAK;AAC9D,QAAMQ,sBAAsBZ,IAAIa,iBAAiBT,UAAU,KAAK;AAEhE,QAAMU,kBAAkBd,IAAIe,iBAAiBC,SAASP;AACtD,MAAI,CAACR,WAAW,CAACI,eAAe,CAACE,eAAe,CAACG,qBAAqB,CAACE,sBAAsB,CAACE,gBAAiB,QAAO;AAEtH,QAAM1B,QAAkB;IAAC;;AACzB,QAAM4B,OAAOhB,IAAIe,iBAAiBC;AAElC,QAAMC,cAAc,wBAACC,UAAAA;AACjB,eAAWvB,KAAKqB,MAAMpB,UAAUsB,KAAAA,KAAU,CAAA,EAAI9B,OAAMS,KAAK,GAAGL,MAAAA,KAAWG,CAAAA,EAAG;EAC9E,GAFoB;AAIpB,MAAIM,SAAS;AACTgB,gBAAY,MAAA;AACZ7B,UAAMS,KAAK,GAAGL,MAAAA,SAAe;AAC7BL,uBAAmBC,OAAOY,IAAIG,MAAMH,IAAIe,iBAAiBb,IAAAA;AACzDd,UAAMS,KAAK,GAAGL,MAAAA,GAAS;EAC3B;AAEA,MAAIa,aAAa;AACbY,gBAAY,UAAA;AACZ7B,UAAMS,KAAK,GAAGL,MAAAA,aAAmB;AACjCL,uBAAmBC,OAAOY,IAAIM,UAAUN,IAAIe,iBAAiBT,QAAAA;AAC7DlB,UAAMS,KAAK,GAAGL,MAAAA,GAAS;EAC3B;AAEA,MAAIkB,mBAAmB;AACnBO,gBAAY,SAAA;AACZ7B,UAAMS,KAAI,GAAIsB,wBAAwB,WAAWnB,IAAIW,cAAc,CAAA;EACvE;AAEA,MAAIC,oBAAoB;AACpBK,gBAAY,UAAA;AACZ7B,UAAMS,KAAI,GAAIsB,wBAAwB,YAAYnB,IAAIa,eAAe,CAAA;EACzE;AAEA,MAAIN,aAAa;AACbU,gBAAY,UAAA;AACZ7B,UAAMS,KAAI,GAAIuB,cAAcpB,IAAIQ,UAAWhB,QAAQA,SAASA,MAAAA,CAAAA;EAChE;AAEA,aAAWG,KAAKqB,MAAMlB,YAAY,CAAA,EAAIV,OAAMS,KAAK,GAAGL,MAAAA,KAAWG,CAAAA,EAAG;AAElEP,QAAMS,KAAK,GAAA;AACX,SAAOT,MAAMiC,KAAK,IAAA;AACtB;AAlDStB;AAoDT,SAASoB,wBAAwBG,SAAiCC,SAA+B;AAC7F,QAAMhC,MAAKC,SAASA;AACpB,QAAMgC,MAAKhC,SAASA,SAASA;AAC7B,QAAMJ,QAAQ;IAAC,GAAGI,MAAAA,GAAS8B,OAAAA;IAAc,GAAG/B,GAAAA;;AAC5C,aAAWkC,KAAKF,SAAS;AACrB,UAAMG,MAAMD,EAAEE,WAAW,MAAM;AAC/B,UAAMC,QAAQH,EAAEI,cAAc,MAAMJ,EAAEI,WAAW,KAAK;AACtDzC,UAAMS,KAAK,GAAG2B,GAAAA,GAAKC,EAAEK,IAAI,GAAGJ,GAAAA,KAAQK,UAAUN,EAAEO,IAAI,CAAA,GAAIJ,KAAAA,EAAO;EACnE;AACAxC,QAAMS,KAAK,GAAGN,GAAAA,GAAK;AACnBH,QAAMS,KAAK,GAAGL,MAAAA,GAAS;AACvB,SAAOJ;AACX;AAZS+B;AA8BF,SAASc,QAAQjC,KAAiBkC,aAAqBnD,qBAAmB;AAC7E,QAAMoD,QAAkB,CAAA;AAGxB,QAAMC,UAAUrC,kBAAkBC,GAAAA;AAClC,MAAIoC,QAASD,OAAMtC,KAAKuC,OAAAA;AAGxB,aAAWC,SAASrC,IAAIsC,QAAQ;AAC5B,QAAIH,MAAM/B,SAAS,EAAG+B,OAAMtC,KAAK,EAAA;AACjCsC,UAAMtC,KAAK0C,gBAAgBF,MAAMG,iBAAiBH,MAAMI,oBAAoBhC,SAAY4B,MAAMR,WAAW,IAAI,YAAYa,eAAeL,OAAOH,UAAAA,CAAAA,EAAa;EAChK;AAGA,QAAMS,cAA8B,CAAA;AACpC,QAAMC,WAAW;IAAE3D,OAAO;EAAE;AAC5B,aAAW4D,SAAS7C,IAAI8C,QAAQ;AAC5B,QAAIX,MAAM/B,SAAS,EAAG+B,OAAMtC,KAAK,EAAA;AACjC,UAAMkD,UAAUF,MAAMG,WAAW5C,SAAS,IAAIyC,MAAMG,UAAU,CAAA,CAAE,MAAM;AACtEb,UAAMtC,KAAK0C,gBAAgBM,MAAML,iBAAiBK,MAAMhB,WAAW,IAAI,YAAYkB,OAAAA,IAAWE,WAAWJ,OAAOF,aAAaC,UAAUM,QAAAA,CAAAA,EAAW;EACtJ;AAEA,SAAOf,MAAMd,KAAK,IAAA,IAAQ;AAC9B;AAvBgBY;AA8BhB,SAASM,gBAAgBC,iBAAuCX,aAA+B;AAC3F,QAAMzC,QAAkB,CAAA;AACxB,MAAIoD,iBAAiBpC,QAAQ;AACzB,eAAWT,KAAK6C,gBAAiBpD,OAAMS,KAAK,KAAKF,CAAAA,EAAG;AACpDP,UAAMS,KAAK,EAAA;EACf;AACA,MAAIgC,aAAa;AACb,eAAWsB,QAAQtB,YAAYuB,MAAM,IAAA,EAAOhE,OAAMS,KAAK,KAAKsD,IAAAA,EAAM;EACtE;AACA,SAAO/D,MAAMgB,SAAS,IAAIhB,MAAMiC,KAAK,IAAA,IAAQ,OAAO;AACxD;AAVSkB;;;AL/IT,IAAM,EAAEc,UAAUC,KAAI,IAAKC;AAE3B,SAASC,MAAMC,MAAY;AACvB,QAAMC,QAAQD,KAAKE,QAAO,EAAGC,MAAM,IAAA;AACnC,SAAON,KAAKD,UAAUK,KAAAA;AAC1B;AAHSF;AAKT,IAAMK,SAA6B;EAC/BC,WAAW;IACP;MACIC,MAAM;MACNC,SAAS;QAAC;;MACVC,YAAY;QAAC;;MACbC,mBAAmB;QAAC;;IACxB;;EAGJF,SAAS;IACL,eAAe;MACXG,MAAMV,MAAMW,UAAQ;AAChB,cAAMC,OAAO,IAAIC,oBAAAA;AACjB,eAAOC,QAAQd,MAAM,WAAWY,IAAAA;MACpC;MACAG,WAAW;MACXC,UAAU,6BAAM,GAAN;MACVC,QAAQC,wBAAAA,UAAS,GAATA;IACZ;EACJ;EAEAC,UAAU;IACN,eAAe;MACXC,MAAMC,MAAMC,SAAO;AACf,cAAMC,OAAOF,KAAKE;AAClB,eAAOxB,MAAMyB,QAAQD,MAAMD,QAAQG,UAAU,CAAA;MACjD;IACJ;EACJ;AACJ;AAEA,IAAA,gBAAerB;","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","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","hasBody","hasHeaders","optOut","headersOptOut","statusCode","I4","h","trail","DEFAULT_PRINT_WIDTH","quoteOptionsValue","value","test","emitOptionsEntries","lines","entries","comments","I2","INDENT","key","Object","c","leading","push","trailing","printOptionsBlock","ast","hasMeta","keys","meta","length","hasServices","services","hasSecurity","security","undefined","hasRequestHeaders","requestHeaders","hasResponseHeaders","responseHeaders","hasBodyComments","optionsComments","body","emitLeading","scope","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\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"]}
|
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;
|
|
1
|
+
{"version":3,"file":"print-ck.d.ts","sourceRoot":"","sources":["../src/print-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA8C,MAAM,mBAAmB,CAAC;AAMhG,+FAA+F;AAC/F,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA6GtC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,GAAE,MAA4B,GAAG,MAAM,CAuBzF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractkit/prettier-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Prettier plugin for ContractKit DSL",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marooned Software",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"prettier": "^3.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@contractkit/core": "0.
|
|
32
|
+
"@contractkit/core": "0.25.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"prettier": "^3.8.3",
|
|
36
|
-
"@repo/config-
|
|
37
|
-
"@repo/config-
|
|
36
|
+
"@repo/config-eslint": "0.3.1",
|
|
37
|
+
"@repo/config-typescript": "0.1.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration",
|
package/src/print-ck.ts
CHANGED
|
@@ -10,15 +10,18 @@ export const DEFAULT_PRINT_WIDTH = 80;
|
|
|
10
10
|
// ─── Options block ──────────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Render an options value in the form the author wrote it.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* `wasUnquoted` comes from the parser, so an unquoted subpath import
|
|
16
|
+
* (`PetService: #modules/pet/pet.service.js`) stays unquoted instead of being normalized. The
|
|
17
|
+
* conditions still gate it: a value the grammar could not read back bare — one containing a
|
|
18
|
+
* newline, a `}`, whitespace-then-`#`, or leading/trailing space — is quoted regardless, which
|
|
19
|
+
* matters for values built programmatically rather than parsed.
|
|
19
20
|
*/
|
|
20
|
-
function quoteOptionsValue(value: string): string {
|
|
21
|
-
|
|
21
|
+
function quoteOptionsValue(value: string, wasUnquoted = false): string {
|
|
22
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value)) return value;
|
|
23
|
+
const roundTripsBare = value.length > 0 && value === value.trim() && !/[\n\r}]/.test(value) && !/[ \t]#/.test(value) && !/^["']/.test(value);
|
|
24
|
+
return wasUnquoted && roundTripsBare ? value : `"${value}"`;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -26,11 +29,18 @@ function quoteOptionsValue(value: string): string {
|
|
|
26
29
|
* retained comments: leading comments before the entry they precede, and trailing comments
|
|
27
30
|
* after the last entry (before the closing `}`). Keeps comments inside these sub-blocks lossless.
|
|
28
31
|
*/
|
|
29
|
-
function emitOptionsEntries(
|
|
32
|
+
function emitOptionsEntries(
|
|
33
|
+
lines: string[],
|
|
34
|
+
entries: Record<string, string>,
|
|
35
|
+
comments: OptionsScopeComments | undefined,
|
|
36
|
+
unquoted: string[] | undefined,
|
|
37
|
+
): void {
|
|
30
38
|
const I2 = INDENT + INDENT;
|
|
39
|
+
const wasUnquoted = new Set(unquoted ?? []);
|
|
31
40
|
for (const [key, value] of Object.entries(entries)) {
|
|
32
41
|
for (const c of comments?.leading?.[key] ?? []) lines.push(`${I2}# ${c}`);
|
|
33
|
-
|
|
42
|
+
const inline = comments?.inline?.[key];
|
|
43
|
+
lines.push(`${I2}${key}: ${quoteOptionsValue(value, wasUnquoted.has(key))}${inline !== undefined ? ` # ${inline}` : ''}`);
|
|
34
44
|
}
|
|
35
45
|
for (const c of comments?.trailing ?? []) lines.push(`${I2}# ${c}`);
|
|
36
46
|
}
|
|
@@ -45,7 +55,8 @@ function printOptionsBlock(ast: CkRootNode): string | null {
|
|
|
45
55
|
const hasBodyComments = ast.optionsComments?.body !== undefined;
|
|
46
56
|
if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders && !hasBodyComments) return null;
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
// A `#` run above the `options` keyword is the file's header comment.
|
|
59
|
+
const lines: string[] = [...(ast.optionsComments?.leading ?? []).map(c => `# ${c}`), 'options {'];
|
|
49
60
|
const body = ast.optionsComments?.body;
|
|
50
61
|
/** Emit the comment run the author wrote directly above this sub-block. */
|
|
51
62
|
const emitLeading = (scope: string) => {
|
|
@@ -55,14 +66,14 @@ function printOptionsBlock(ast: CkRootNode): string | null {
|
|
|
55
66
|
if (hasMeta) {
|
|
56
67
|
emitLeading('keys');
|
|
57
68
|
lines.push(`${INDENT}keys: {`);
|
|
58
|
-
emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys);
|
|
69
|
+
emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys, ast.optionsUnquoted?.keys);
|
|
59
70
|
lines.push(`${INDENT}}`);
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
if (hasServices) {
|
|
63
74
|
emitLeading('services');
|
|
64
75
|
lines.push(`${INDENT}services: {`);
|
|
65
|
-
emitOptionsEntries(lines, ast.services, ast.optionsComments?.services);
|
|
76
|
+
emitOptionsEntries(lines, ast.services, ast.optionsComments?.services, ast.optionsUnquoted?.services);
|
|
66
77
|
lines.push(`${INDENT}}`);
|
|
67
78
|
}
|
|
68
79
|
|
package/src/print-operation.ts
CHANGED
|
@@ -177,7 +177,7 @@ function printOperationKey(op: OpOperationNode, key: OpBodyKey): string[] {
|
|
|
177
177
|
return lines;
|
|
178
178
|
}
|
|
179
179
|
case 'responses':
|
|
180
|
-
return op.responses.length > 0 ? printResponseBlock(op.responses) : [];
|
|
180
|
+
return op.responses.length > 0 ? printResponseBlock(op.responses, op.responsesTrailingComments) : [];
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -377,21 +377,33 @@ function printContentTypeLine(contentType: string, bodyType: ContractTypeNode, l
|
|
|
377
377
|
|
|
378
378
|
// ─── Response block ──────────────────────────────────────────────────────────
|
|
379
379
|
|
|
380
|
-
function printResponseBlock(responses: OpResponseNode[]): string[] {
|
|
380
|
+
function printResponseBlock(responses: OpResponseNode[], trailingComments?: string[]): string[] {
|
|
381
381
|
const lines: string[] = [`${I2}response: {`];
|
|
382
382
|
|
|
383
383
|
for (const resp of responses) {
|
|
384
|
-
const
|
|
384
|
+
for (const comment of resp.leadingComments ?? []) lines.push(`${I3}# ${comment}`);
|
|
385
|
+
const bodies = resp.bodies;
|
|
385
386
|
const hasHeaders = resp.headers && resp.headers.length > 0;
|
|
386
387
|
const optOut = resp.headersOptOut;
|
|
387
|
-
|
|
388
|
+
// `404(documented):` — the modifier changes what codegen does, so it has to survive.
|
|
389
|
+
const code = resp.emit ? `${resp.statusCode}(${resp.emit})` : `${resp.statusCode}`;
|
|
390
|
+
const inlinable = resp.inline && bodies.length > 0 && !hasHeaders && !optOut && bodies.every(b => b.bodyType.kind !== 'inlineObject');
|
|
391
|
+
if (inlinable) {
|
|
388
392
|
// Written on one line in the source, so keep it there: `200: { application/json: Pet }`.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
lines.push(`${I3}${
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
// Several mimes on that line stay on it too, space-separated as the grammar has them.
|
|
394
|
+
const inner = bodies.map(b => `${b.contentType}: ${printType(b.bodyType)}`).join(' ');
|
|
395
|
+
lines.push(`${I3}${code}: { ${inner} }`);
|
|
396
|
+
} else if (bodies.length === 0 && !hasHeaders && !optOut && resp.hasBlock) {
|
|
397
|
+
// An empty block means "emitted, no body" — collapsing it to `304:` would change
|
|
398
|
+
// the generated router, so it is not a formatting detail.
|
|
399
|
+
lines.push(`${I3}${code}: {}`);
|
|
400
|
+
} else if (bodies.length > 0 || hasHeaders || optOut || (resp.trailingComments?.length ?? 0) > 0) {
|
|
401
|
+
lines.push(`${I3}${code}: {`);
|
|
402
|
+
for (const body of bodies) {
|
|
403
|
+
for (const comment of body.leadingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
404
|
+
lines.push(...printContentTypeLine(body.contentType, body.bodyType, I4));
|
|
394
405
|
}
|
|
406
|
+
for (const comment of resp.headersLeadingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
395
407
|
if (optOut) {
|
|
396
408
|
lines.push(`${I4}headers: none`);
|
|
397
409
|
} else if (hasHeaders) {
|
|
@@ -403,12 +415,14 @@ function printResponseBlock(responses: OpResponseNode[]): string[] {
|
|
|
403
415
|
}
|
|
404
416
|
lines.push(`${I4}}`);
|
|
405
417
|
}
|
|
418
|
+
for (const comment of resp.trailingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
406
419
|
lines.push(`${I3}}`);
|
|
407
420
|
} else {
|
|
408
|
-
lines.push(`${I3}${
|
|
421
|
+
lines.push(`${I3}${code}:`);
|
|
409
422
|
}
|
|
410
423
|
}
|
|
411
424
|
|
|
425
|
+
for (const comment of trailingComments ?? []) lines.push(`${I3}# ${comment}`);
|
|
412
426
|
lines.push(`${I2}}`);
|
|
413
427
|
return lines;
|
|
414
428
|
}
|
package/tests/print-ck.test.ts
CHANGED
|
@@ -453,7 +453,7 @@ describe('printCk — request blocks', () => {
|
|
|
453
453
|
makeRoute('/users', [
|
|
454
454
|
makeOp('post', {
|
|
455
455
|
request: { bodies: [{ contentType: 'application/vnd.api+json', bodyType: { kind: 'ref', name: 'CreateUser' } }] },
|
|
456
|
-
responses: [{ statusCode: 201, contentType: 'application/vnd.api+json', bodyType: { kind: 'ref', name: 'User' } }],
|
|
456
|
+
responses: [{ statusCode: 201, hasBlock: true, bodies: [{ contentType: 'application/vnd.api+json', bodyType: { kind: 'ref', name: 'User' } }] }],
|
|
457
457
|
}),
|
|
458
458
|
]),
|
|
459
459
|
]);
|
|
@@ -493,8 +493,8 @@ describe('printCk — response headers', () => {
|
|
|
493
493
|
responses: [
|
|
494
494
|
{
|
|
495
495
|
statusCode: 200,
|
|
496
|
-
|
|
497
|
-
bodyType: { kind: 'ref', name: 'Transfer' },
|
|
496
|
+
hasBlock: true,
|
|
497
|
+
bodies: [{ contentType: 'application/json', bodyType: { kind: 'ref', name: 'Transfer' } }],
|
|
498
498
|
headers: [
|
|
499
499
|
{ name: 'preference-applied', optional: true, type: { kind: 'scalar', name: 'string' } },
|
|
500
500
|
{ name: 'etag', optional: false, type: { kind: 'scalar', name: 'string' }, description: 'cache validator' },
|
|
@@ -519,6 +519,8 @@ describe('printCk — response headers', () => {
|
|
|
519
519
|
responses: [
|
|
520
520
|
{
|
|
521
521
|
statusCode: 204,
|
|
522
|
+
hasBlock: true,
|
|
523
|
+
bodies: [],
|
|
522
524
|
headers: [{ name: 'x-deleted-at', optional: false, type: { kind: 'scalar', name: 'string' } }],
|
|
523
525
|
},
|
|
524
526
|
],
|
package/tests/round-trip.test.ts
CHANGED
|
@@ -111,6 +111,86 @@ contract Pet: {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
describe('round-trip — comments in the options block', () => {
|
|
114
|
+
it('keeps a header comment above the options keyword', () => {
|
|
115
|
+
const source = `# ContractKit contracts for billing.
|
|
116
|
+
# Owned by the payments team.
|
|
117
|
+
options {
|
|
118
|
+
keys: {
|
|
119
|
+
area: billing
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
contract Pet: {
|
|
124
|
+
id: uuid
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
expect(format(source)).toBe(source);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('keeps a trailing comment on an options entry, braces and all', () => {
|
|
131
|
+
// The value used to swallow the comment and stop at the first `}`, which closed the
|
|
132
|
+
// block early and silently mis-parsed everything after it.
|
|
133
|
+
const source = `options {
|
|
134
|
+
keys: {
|
|
135
|
+
area: billing # interpolated elsewhere as {{area}}
|
|
136
|
+
}
|
|
137
|
+
services: {
|
|
138
|
+
PetService: "#modules/pet/pet.service.js"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
contract Pet: {
|
|
143
|
+
id: uuid
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
expect(format(source)).toBe(source);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('keeps an unquoted subpath value unquoted', () => {
|
|
150
|
+
// Both forms parse to the same string, so the parser records which one the author wrote;
|
|
151
|
+
// without that the formatter normalizes every value to the quoted form.
|
|
152
|
+
const source = `options {
|
|
153
|
+
services: {
|
|
154
|
+
PetService: #modules/pet/pet.service.js
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
contract Pet: {
|
|
159
|
+
id: uuid
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
expect(format(source)).toBe(source);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('keeps a quoted value quoted', () => {
|
|
166
|
+
const source = `options {
|
|
167
|
+
services: {
|
|
168
|
+
PetService: "#modules/pet/pet.service.js"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
contract Pet: {
|
|
173
|
+
id: uuid
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
expect(format(source)).toBe(source);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('keeps both forms side by side, with their comments', () => {
|
|
180
|
+
const source = `options {
|
|
181
|
+
services: {
|
|
182
|
+
Bare: #modules/a/a.service.js # unquoted
|
|
183
|
+
Quoted: "#modules/b/b.service.js" # quoted
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
contract Pet: {
|
|
188
|
+
id: uuid
|
|
189
|
+
}
|
|
190
|
+
`;
|
|
191
|
+
expect(format(source)).toBe(source);
|
|
192
|
+
});
|
|
193
|
+
|
|
114
194
|
it('keeps a comment above a sub-block', () => {
|
|
115
195
|
const source = `options {
|
|
116
196
|
# where these come from
|
|
@@ -245,6 +325,147 @@ describe('round-trip — layout', () => {
|
|
|
245
325
|
}
|
|
246
326
|
}
|
|
247
327
|
}
|
|
328
|
+
`;
|
|
329
|
+
expect(format(source)).toBe(source);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('keeps an empty status block, which means emitted with no body', () => {
|
|
333
|
+
const source = `operation /art/{id}: {
|
|
334
|
+
get: {
|
|
335
|
+
response: {
|
|
336
|
+
200: { application/json: Art }
|
|
337
|
+
304: {}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
expect(format(source)).toBe(source);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('keeps a bare status bare', () => {
|
|
346
|
+
const source = `operation /art/{id}: {
|
|
347
|
+
get: {
|
|
348
|
+
response: {
|
|
349
|
+
200: { application/json: Art }
|
|
350
|
+
304:
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
`;
|
|
355
|
+
expect(format(source)).toBe(source);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('keeps the documented modifier on a status', () => {
|
|
359
|
+
const source = `operation /pet: {
|
|
360
|
+
get: {
|
|
361
|
+
response: {
|
|
362
|
+
200: { application/json: Pet }
|
|
363
|
+
404(documented): { application/json: Problem }
|
|
364
|
+
410(documented):
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
`;
|
|
369
|
+
expect(format(source)).toBe(source);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('keeps every declared mime for a status, in source order', () => {
|
|
373
|
+
const source = `operation /art/{id}: {
|
|
374
|
+
get: {
|
|
375
|
+
response: {
|
|
376
|
+
200: {
|
|
377
|
+
image/png: binary
|
|
378
|
+
image/jpeg: binary
|
|
379
|
+
headers: {
|
|
380
|
+
etag?: string
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
`;
|
|
387
|
+
expect(format(source)).toBe(source);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('keeps several mimes on one line when the source has them there', () => {
|
|
391
|
+
const source = `operation /art/{id}: {
|
|
392
|
+
get: {
|
|
393
|
+
response: {
|
|
394
|
+
200: { image/png: binary image/jpeg: binary }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
expect(format(source)).toBe(source);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// ─── Comments in the response block ──────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
describe('round-trip — comments in the response block', () => {
|
|
406
|
+
it('keeps a comment run above a status code', () => {
|
|
407
|
+
const source = `operation /art/{id}: {
|
|
408
|
+
get: {
|
|
409
|
+
response: {
|
|
410
|
+
# One declared mime per format, because the router pins ctx.type from this line
|
|
411
|
+
# and a service cannot vary it per request.
|
|
412
|
+
200: {
|
|
413
|
+
image/png: binary
|
|
414
|
+
}
|
|
415
|
+
# Produced by the conditional-GET middleware, not by the handler.
|
|
416
|
+
304:
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
expect(format(source)).toBe(source);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('keeps a comment above a mime line inside a status block', () => {
|
|
425
|
+
const source = `operation /art/{id}: {
|
|
426
|
+
get: {
|
|
427
|
+
response: {
|
|
428
|
+
200: {
|
|
429
|
+
# served straight from object storage
|
|
430
|
+
image/png: binary
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
`;
|
|
436
|
+
expect(format(source)).toBe(source);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('keeps a comment above a headers block', () => {
|
|
440
|
+
const source = `operation /art/{id}: {
|
|
441
|
+
get: {
|
|
442
|
+
response: {
|
|
443
|
+
200: {
|
|
444
|
+
image/png: binary
|
|
445
|
+
# set by the CDN, echoed here so the SDK types it
|
|
446
|
+
headers: {
|
|
447
|
+
etag?: string
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
`;
|
|
454
|
+
expect(format(source)).toBe(source);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('keeps trailing comments before either closing brace', () => {
|
|
458
|
+
const source = `operation /art/{id}: {
|
|
459
|
+
get: {
|
|
460
|
+
response: {
|
|
461
|
+
200: {
|
|
462
|
+
image/png: binary
|
|
463
|
+
# TODO: add image/avif once the encoder lands
|
|
464
|
+
}
|
|
465
|
+
# TODO: document the 429 the rate limiter returns
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
248
469
|
`;
|
|
249
470
|
expect(format(source)).toBe(source);
|
|
250
471
|
});
|