@contractkit/prettier-plugin 0.12.1 → 0.13.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.
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
5
5
  CLI tsup v8.5.1
6
6
  CLI Target: esnext
7
7
  ESM Build start
8
- ESM dist/index.js 20.80 KB
9
- ESM dist/index.js.map 49.78 KB
10
- ESM ⚡️ Build success in 150ms
8
+ ESM dist/index.js 24.87 KB
9
+ ESM dist/index.js.map 62.23 KB
10
+ ESM ⚡️ Build success in 191ms
11
11
  DTS Build start
12
- DTS ⚡️ Build success in 3942ms
13
- DTS dist/index.d.ts 630.00 B
12
+ DTS ⚡️ Build success in 4964ms
13
+ DTS dist/index.d.ts 1.13 KB
@@ -3,21 +3,24 @@ $ vitest run --coverage
3
3
   RUN  v4.1.5 /home/runner/work/ContractKit/ContractKit/apps/prettier-plugin
4
4
  Coverage enabled with v8
5
5
 
6
- ✓ tests/print-ck.test.ts (61 tests) 340ms
6
+ ✓ tests/print-ck.test.ts (81 tests) 676ms
7
+ ✓ tests/round-trip.test.ts (20 tests) 1570ms
8
+ ✓ formats test.ck to itself  587ms
9
+ ✓ formatting is a fixed point for source #0  769ms
7
10
 
8
-  Test Files  1 passed (1)
9
-  Tests  61 passed (61)
10
-  Start at  15:55:38
11
-  Duration  5.72s (transform 1.85s, setup 0ms, import 4.00s, tests 340ms, environment 0ms)
11
+  Test Files  2 passed (2)
12
+  Tests  101 passed (101)
13
+  Start at  15:07:02
14
+  Duration  7.40s (transform 3.64s, setup 0ms, import 9.29s, tests 2.25s, environment 0ms)
12
15
 
13
16
   % Coverage report from v8
14
17
  -------------------|---------|----------|---------|---------|-------------------
15
18
  File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
16
19
  -------------------|---------|----------|---------|---------|-------------------
17
- All files | 68.83 | 59.1 | 74.19 | 75.07 |
20
+ All files | 75.62 | 69.04 | 84.21 | 81.34 |
18
21
  indent.ts | 100 | 100 | 100 | 100 |
19
- print-ck.ts | 89.85 | 80 | 100 | 98.03 | 59
20
- print-contract.ts | 41.93 | 30.35 | 50 | 42.85 | 10,37-68
21
- ...t-operation.ts | 73.96 | 64.67 | 92.85 | 83.78 | ...74,282-293,325
22
- print-type.ts | 47.12 | 46.87 | 45.45 | 48.61 | ...10,139,142-145
22
+ print-ck.ts | 88.34 | 85 | 100 | 97.18 | 80-81
23
+ print-contract.ts | 45.71 | 38.23 | 50 | 46.66 | 20,52-85
24
+ ...t-operation.ts | 78.32 | 69.46 | 94.44 | 86.12 | ...37,354,362-373
25
+ print-type.ts | 64.13 | 63.46 | 63.63 | 66.66 | ...02-103,148-151
23
26
  -------------------|---------|----------|---------|---------|-------------------
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @contractkit/prettier-plugin-contractkit
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 23e4beb: Fix the formatter rewriting `.ck` files it should have left alone. Running Prettier on a contract folded standalone `#` comment blocks into a trailing comment on the following declaration (`# ─── Pet endpoints ───` became `operation /pet: { # ─── Pet endpoints ───`), reordered operation body keys into a canonical order, dropped blank lines between operations, and expanded single-line response bodies like `200: { application/json: Pet }` onto three lines. An inline contract comment (`contract Pet: { # A pet`) was also attributed to the first field, so it printed twice.
8
+
9
+ The parser now records the author's layout alongside the semantics — comment placement (`leadingComments`, `descriptionInline`), operation body key order (`keyOrder`), blank lines (`blankLineBefore`), and single-line response blocks (`inline`) — and the printer reproduces it. A `#` comment separated from the declaration below it by a blank line is a standalone divider rather than a doc comment; one written directly above is a doc comment and is emitted above the declaration, not on its header line.
10
+
11
+ Comments may now also sit directly inside an `options { ... }` block, between its sub-blocks, where the grammar previously rejected them.
12
+
13
+ These AST fields are additive and optional; codegen plugins ignore them.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [23e4beb]
18
+ - @contractkit/core@0.24.0
19
+
20
+ ## 0.12.2
21
+
22
+ ### Patch Changes
23
+
24
+ - 2bf01f1: Preserve trailing comments instead of dropping them on format: a comment as the last line of a contract/model body, an operation/route body, an inline object type, or an options `keys`/`services` block now round-trips.
25
+ - Updated dependencies [2bf01f1]
26
+ - @contractkit/core@0.23.0
27
+
3
28
  ## 0.12.1
4
29
 
5
30
  ### Patch Changes
package/README.md CHANGED
@@ -33,12 +33,10 @@ Most editors with a Prettier integration (VS Code, JetBrains, Neovim) pick the p
33
33
 
34
34
  ## What it does
35
35
 
36
- The printer round-trips the parser's AST back into canonical `.ck` source:
36
+ The printer round-trips the parser's AST back into `.ck` source:
37
37
 
38
38
  - 4-space indentation (matches Prettier's default `tabWidth`)
39
39
  - Canonical modifier order on fields: `override → deprecated → readonly|writeonly`
40
- - Stable ordering of `options` block items, route bodies, and operation blocks
41
- - Inline `# comment` placement preserved on field/operation/status lines
42
40
  - Multi-base inheritance: `contract C: A & B & { ... }` with the inline block always last
43
41
  - Multi-line unions: a leading `|` is preserved on type aliases like `contract X: A | B | C`
44
42
  - Discriminated unions render as `discriminated(by=field, A | B | C)`
@@ -46,6 +44,17 @@ The printer round-trips the parser's AST back into canonical `.ck` source:
46
44
 
47
45
  The plugin honours Prettier's `printWidth` for line-wrapping decisions where applicable, but most CK constructs format to a fixed multi-line shape regardless of width.
48
46
 
47
+ ## What it preserves
48
+
49
+ Formatting a well-formed `.ck` file leaves it byte-identical. The formatter deliberately does **not** impose a canonical layout where the language allows more than one form — it reproduces what the author wrote:
50
+
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
+ - **Operation body key order.** `sdk` before `service` stays that way; the printer never sorts a user's keys into a canonical order.
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.
55
+
56
+ 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
+
49
58
  ## Source layout
50
59
 
51
60
  | Path | Purpose |
package/dist/index.js CHANGED
@@ -106,7 +106,11 @@ function printField(field, indent, printWidth = 80) {
106
106
  }
107
107
  __name(printField, "printField");
108
108
  function printInlineObjectExpanded(obj, indent, printWidth = 80) {
109
- return obj.fields.map((f) => printField(f, indent, printWidth));
109
+ const lines = obj.fields.map((f) => printField(f, indent, printWidth));
110
+ for (const comment of obj.trailingComments ?? []) {
111
+ lines.push(`${indent}# ${comment}`);
112
+ }
113
+ return lines;
110
114
  }
111
115
  __name(printInlineObjectExpanded, "printInlineObjectExpanded");
112
116
  function formatEnumValue(v) {
@@ -146,7 +150,7 @@ function printModelDecl(model, printWidth = 80) {
146
150
  if (model.type !== void 0) {
147
151
  return printTypeAlias(model, printWidth);
148
152
  }
149
- const commentSuffix = model.description ? ` # ${model.description}` : "";
153
+ const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : "";
150
154
  const modifiers = [
151
155
  model.deprecated ? "deprecated" : "",
152
156
  model.inputCase || model.outputCase ? `format(${[
@@ -164,13 +168,16 @@ function printModelDecl(model, printWidth = 80) {
164
168
  for (const field of model.fields) {
165
169
  lines.push(printField(field, INDENT, printWidth));
166
170
  }
171
+ for (const comment of model.trailingComments ?? []) {
172
+ lines.push(`${INDENT}# ${comment}`);
173
+ }
167
174
  lines.push("}");
168
175
  return lines.join("\n");
169
176
  }
170
177
  __name(printModelDecl, "printModelDecl");
171
178
  function printTypeAlias(model, printWidth) {
172
179
  const type = model.type;
173
- const commentSuffix = model.description ? ` # ${model.description}` : "";
180
+ const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : "";
174
181
  const modifiers = [
175
182
  model.deprecated ? "deprecated" : "",
176
183
  model.inputCase || model.outputCase ? `format(${[
@@ -215,8 +222,7 @@ function flushBlocks(out, blocks, idx, beforeLine, _indent = "") {
215
222
  __name(flushBlocks, "flushBlocks");
216
223
  function printRoute(route, blocks, idx, nextRouteStart) {
217
224
  const lines = [];
218
- const commentSuffix = route.description ? ` # ${route.description}` : "";
219
- lines.push(`${route.path}: {${commentSuffix}`);
225
+ lines.push(`${route.path}: {`);
220
226
  if (route.params !== void 0) {
221
227
  lines.push(...printParamsBlock(route.params, I1, route.paramsMode));
222
228
  }
@@ -224,10 +230,14 @@ function printRoute(route, blocks, idx, nextRouteStart) {
224
230
  lines.push(...printSecurity(route.security, I1, I2));
225
231
  }
226
232
  for (const op of route.operations) {
233
+ if (op.blankLineBefore && lines.length > 1) lines.push("");
227
234
  flushBlocks(lines, blocks, idx, op.loc.line, I1);
228
235
  lines.push(...printOperation(op));
229
236
  }
230
237
  flushBlocks(lines, blocks, idx, nextRouteStart, I1);
238
+ for (const comment of route.trailingComments ?? []) {
239
+ lines.push(`${I1}# ${comment}`);
240
+ }
231
241
  lines.push("}");
232
242
  return lines.join("\n");
233
243
  }
@@ -260,53 +270,131 @@ function printParamsBlock(source, indent, mode) {
260
270
  ];
261
271
  }
262
272
  __name(printParamsBlock, "printParamsBlock");
263
- function printOperation(op) {
264
- const lines = [];
265
- const commentSuffix = op.description ? ` # ${op.description}` : "";
266
- const modPart = op.modifiers?.length ? `(${op.modifiers[0]})` : "";
267
- lines.push(`${I1}${op.method}${modPart}: {${commentSuffix}`);
268
- if (op.name) lines.push(`${I2}name: ${op.name}`);
269
- if (op.service) lines.push(`${I2}service: ${op.service}`);
270
- if (op.sdk) lines.push(`${I2}sdk: ${op.sdk}`);
271
- if (op.signature) {
272
- const comment = op.signatureDescription ? ` # ${op.signatureDescription}` : "";
273
- if (op.signaturePolicy) {
274
- lines.push(`${I2}signature: {`);
275
- lines.push(`${I3}options: ${formatSignatureValue(op.signature)}${comment}`);
276
- lines.push(`${I3}policy: ${op.signaturePolicy}`);
273
+ var CANONICAL_KEY_ORDER = [
274
+ "name",
275
+ "service",
276
+ "sdk",
277
+ "mcp",
278
+ "signature",
279
+ "security",
280
+ "plugins",
281
+ "query",
282
+ "headers",
283
+ "request",
284
+ "responses"
285
+ ];
286
+ function printOperationKey(op, key) {
287
+ switch (key) {
288
+ case "name":
289
+ return op.name ? [
290
+ `${I2}name: ${op.name}`
291
+ ] : [];
292
+ case "service":
293
+ return op.service ? [
294
+ `${I2}service: ${op.service}`
295
+ ] : [];
296
+ case "sdk":
297
+ return op.sdk ? [
298
+ `${I2}sdk: ${op.sdk}`
299
+ ] : [];
300
+ case "mcp":
301
+ if (op.mcp === true) return [
302
+ `${I2}mcp: true`
303
+ ];
304
+ if (op.mcp === false) return [
305
+ `${I2}mcp: false`
306
+ ];
307
+ return op.mcp ? printMcpBlock(op.mcp) : [];
308
+ case "signature": {
309
+ if (!op.signature) return [];
310
+ const comment = op.signatureDescription ? ` # ${op.signatureDescription}` : "";
311
+ if (op.signaturePolicy) {
312
+ return [
313
+ `${I2}signature: {`,
314
+ `${I3}options: ${formatSignatureValue(op.signature)}${comment}`,
315
+ `${I3}policy: ${op.signaturePolicy}`,
316
+ `${I2}}`
317
+ ];
318
+ }
319
+ return [
320
+ `${I2}signature: ${formatSignatureValue(op.signature)}${comment}`
321
+ ];
322
+ }
323
+ case "security":
324
+ return op.security !== void 0 ? printSecurity(op.security) : [];
325
+ case "plugins": {
326
+ if (!op.plugins || Object.keys(op.plugins).length === 0) return [];
327
+ const lines = [
328
+ `${I2}plugins: {`
329
+ ];
330
+ for (const [k, val] of Object.entries(op.plugins)) lines.push(...printPluginEntry(k, val, I3));
277
331
  lines.push(`${I2}}`);
278
- } else {
279
- lines.push(`${I2}signature: ${formatSignatureValue(op.signature)}${comment}`);
332
+ return lines;
280
333
  }
281
- }
282
- if (op.security !== void 0) lines.push(...printSecurity(op.security));
283
- if (op.plugins && Object.keys(op.plugins).length > 0) {
284
- lines.push(`${I2}plugins: {`);
285
- for (const [key, val] of Object.entries(op.plugins)) {
286
- lines.push(...printPluginEntry(key, val, I3));
334
+ case "query":
335
+ return op.query !== void 0 ? printQueryOrHeaders("query", op.query, op.queryMode) : [];
336
+ case "headers":
337
+ if (op.requestHeadersOptOut) return [
338
+ `${I2}headers: none`
339
+ ];
340
+ return op.headers !== void 0 ? printQueryOrHeaders("headers", op.headers, op.headersMode) : [];
341
+ case "request": {
342
+ if (!op.request) return [];
343
+ const lines = [
344
+ `${I2}request: {`
345
+ ];
346
+ for (const body of op.request.bodies) lines.push(...printContentTypeLine(body.contentType, body.bodyType, I3));
347
+ lines.push(`${I2}}`);
348
+ return lines;
287
349
  }
288
- lines.push(`${I2}}`);
289
- }
290
- if (op.query !== void 0) lines.push(...printQueryOrHeaders("query", op.query, op.queryMode));
291
- if (op.requestHeadersOptOut) {
292
- lines.push(`${I2}headers: none`);
293
- } else if (op.headers !== void 0) {
294
- lines.push(...printQueryOrHeaders("headers", op.headers, op.headersMode));
350
+ case "responses":
351
+ return op.responses.length > 0 ? printResponseBlock(op.responses) : [];
295
352
  }
296
- if (op.request) {
297
- lines.push(`${I2}request: {`);
298
- for (const body of op.request.bodies) {
299
- lines.push(...printContentTypeLine(body.contentType, body.bodyType, I3));
300
- }
301
- lines.push(`${I2}}`);
353
+ }
354
+ __name(printOperationKey, "printOperationKey");
355
+ function printOperation(op) {
356
+ const lines = [];
357
+ const modPart = op.modifiers?.length ? `(${op.modifiers[0]})` : "";
358
+ const inlineDescription = op.descriptionInline ?? true;
359
+ if (op.description && !inlineDescription) {
360
+ for (const line of op.description.split("\n")) lines.push(`${I1}# ${line}`);
302
361
  }
303
- if (op.responses.length > 0) {
304
- lines.push(...printResponseBlock(op.responses));
362
+ const commentSuffix = op.description && inlineDescription ? ` # ${op.description}` : "";
363
+ lines.push(`${I1}${op.method}${modPart}: {${commentSuffix}`);
364
+ const order = op.keyOrder ?? [];
365
+ const rest = CANONICAL_KEY_ORDER.filter((k) => !order.includes(k));
366
+ for (const key of [
367
+ ...order,
368
+ ...rest
369
+ ]) {
370
+ lines.push(...printOperationKey(op, key));
305
371
  }
306
372
  lines.push(`${I1}}`);
307
373
  return lines;
308
374
  }
309
375
  __name(printOperation, "printOperation");
376
+ function mcpHintTokens(mcp) {
377
+ const tokens = [];
378
+ if (mcp.readOnlyHint !== void 0) tokens.push(mcp.readOnlyHint ? "readOnly" : "nonReadOnly");
379
+ if (mcp.idempotentHint !== void 0) tokens.push(mcp.idempotentHint ? "idempotent" : "nonIdempotent");
380
+ if (mcp.destructiveHint !== void 0) tokens.push(mcp.destructiveHint ? "destructive" : "nonDestructive");
381
+ if (mcp.openWorldHint !== void 0) tokens.push(mcp.openWorldHint ? "openWorld" : "closedWorld");
382
+ return tokens;
383
+ }
384
+ __name(mcpHintTokens, "mcpHintTokens");
385
+ function printMcpBlock(mcp) {
386
+ const lines = [
387
+ `${I2}mcp: {`
388
+ ];
389
+ if (mcp.name !== void 0) lines.push(`${I3}name: "${escapeString(mcp.name)}"`);
390
+ if (mcp.title !== void 0) lines.push(`${I3}title: "${escapeString(mcp.title)}"`);
391
+ if (mcp.description !== void 0) lines.push(`${I3}description: "${escapeString(mcp.description)}"`);
392
+ const tokens = mcpHintTokens(mcp);
393
+ if (tokens.length > 0) lines.push(`${I3}hint: ${tokens.join(", ")}`);
394
+ lines.push(`${I2}}`);
395
+ return lines;
396
+ }
397
+ __name(printMcpBlock, "printMcpBlock");
310
398
  var IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
311
399
  function escapeString(s) {
312
400
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -451,7 +539,9 @@ function printResponseBlock(responses) {
451
539
  const hasBody = resp.contentType && resp.bodyType;
452
540
  const hasHeaders = resp.headers && resp.headers.length > 0;
453
541
  const optOut = resp.headersOptOut;
454
- if (hasBody || hasHeaders || optOut) {
542
+ if (resp.inline && hasBody && !hasHeaders && !optOut && resp.bodyType.kind !== "inlineObject") {
543
+ lines.push(`${I3}${resp.statusCode}: { ${resp.contentType}: ${printType(resp.bodyType)} }`);
544
+ } else if (hasBody || hasHeaders || optOut) {
455
545
  lines.push(`${I3}${resp.statusCode}: {`);
456
546
  if (hasBody) {
457
547
  lines.push(...printContentTypeLine(resp.contentType, resp.bodyType, I4));
@@ -483,39 +573,55 @@ function quoteOptionsValue(value) {
483
573
  return /^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value) ? value : `"${value}"`;
484
574
  }
485
575
  __name(quoteOptionsValue, "quoteOptionsValue");
576
+ function emitOptionsEntries(lines, entries, comments) {
577
+ const I22 = INDENT + INDENT;
578
+ for (const [key, value] of Object.entries(entries)) {
579
+ for (const c of comments?.leading?.[key] ?? []) lines.push(`${I22}# ${c}`);
580
+ lines.push(`${I22}${key}: ${quoteOptionsValue(value)}`);
581
+ }
582
+ for (const c of comments?.trailing ?? []) lines.push(`${I22}# ${c}`);
583
+ }
584
+ __name(emitOptionsEntries, "emitOptionsEntries");
486
585
  function printOptionsBlock(ast) {
487
586
  const hasMeta = Object.keys(ast.meta).length > 0;
488
587
  const hasServices = Object.keys(ast.services).length > 0;
489
588
  const hasSecurity = ast.security !== void 0;
490
589
  const hasRequestHeaders = (ast.requestHeaders?.length ?? 0) > 0;
491
590
  const hasResponseHeaders = (ast.responseHeaders?.length ?? 0) > 0;
492
- if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders) return null;
591
+ const hasBodyComments = ast.optionsComments?.body !== void 0;
592
+ if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders && !hasBodyComments) return null;
493
593
  const lines = [
494
594
  "options {"
495
595
  ];
596
+ const body = ast.optionsComments?.body;
597
+ const emitLeading = /* @__PURE__ */ __name((scope) => {
598
+ for (const c of body?.leading?.[scope] ?? []) lines.push(`${INDENT}# ${c}`);
599
+ }, "emitLeading");
496
600
  if (hasMeta) {
601
+ emitLeading("keys");
497
602
  lines.push(`${INDENT}keys: {`);
498
- for (const [key, value] of Object.entries(ast.meta)) {
499
- lines.push(`${INDENT}${INDENT}${key}: ${quoteOptionsValue(value)}`);
500
- }
603
+ emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys);
501
604
  lines.push(`${INDENT}}`);
502
605
  }
503
606
  if (hasServices) {
607
+ emitLeading("services");
504
608
  lines.push(`${INDENT}services: {`);
505
- for (const [key, value] of Object.entries(ast.services)) {
506
- lines.push(`${INDENT}${INDENT}${key}: ${quoteOptionsValue(value)}`);
507
- }
609
+ emitOptionsEntries(lines, ast.services, ast.optionsComments?.services);
508
610
  lines.push(`${INDENT}}`);
509
611
  }
510
612
  if (hasRequestHeaders) {
613
+ emitLeading("request");
511
614
  lines.push(...printOptionsHeaderScope("request", ast.requestHeaders));
512
615
  }
513
616
  if (hasResponseHeaders) {
617
+ emitLeading("response");
514
618
  lines.push(...printOptionsHeaderScope("response", ast.responseHeaders));
515
619
  }
516
620
  if (hasSecurity) {
621
+ emitLeading("security");
517
622
  lines.push(...printSecurity(ast.security, INDENT, INDENT + INDENT));
518
623
  }
624
+ for (const c of body?.trailing ?? []) lines.push(`${INDENT}# ${c}`);
519
625
  lines.push("}");
520
626
  return lines.join("\n");
521
627
  }
@@ -543,7 +649,7 @@ function printCk(ast, printWidth = DEFAULT_PRINT_WIDTH) {
543
649
  if (options) parts.push(options);
544
650
  for (const model of ast.models) {
545
651
  if (parts.length > 0) parts.push("");
546
- parts.push(`contract ${printModelDecl(model, printWidth)}`);
652
+ parts.push(printDeclLeadIn(model.leadingComments, model.descriptionInline ? void 0 : model.description) + `contract ${printModelDecl(model, printWidth)}`);
547
653
  }
548
654
  const emptyBlocks = [];
549
655
  const emptyIdx = {
@@ -552,11 +658,23 @@ function printCk(ast, printWidth = DEFAULT_PRINT_WIDTH) {
552
658
  for (const route of ast.routes) {
553
659
  if (parts.length > 0) parts.push("");
554
660
  const modPart = route.modifiers?.length ? `(${route.modifiers[0]})` : "";
555
- parts.push(`operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
661
+ parts.push(printDeclLeadIn(route.leadingComments, route.description) + `operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
556
662
  }
557
663
  return parts.join("\n") + "\n";
558
664
  }
559
665
  __name(printCk, "printCk");
666
+ function printDeclLeadIn(leadingComments, description) {
667
+ const lines = [];
668
+ if (leadingComments?.length) {
669
+ for (const c of leadingComments) lines.push(`# ${c}`);
670
+ lines.push("");
671
+ }
672
+ if (description) {
673
+ for (const line of description.split("\n")) lines.push(`# ${line}`);
674
+ }
675
+ return lines.length > 0 ? lines.join("\n") + "\n" : "";
676
+ }
677
+ __name(printDeclLeadIn, "printDeclLeadIn");
560
678
 
561
679
  // src/index.ts
562
680
  var { hardline, join } = builders;