@contractkit/prettier-plugin 0.12.2 → 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 +5 -5
- package/.turbo/turbo-test$colon$ci.log +13 -10
- package/CHANGELOG.md +49 -0
- package/README.md +16 -3
- package/dist/index.js +175 -57
- package/dist/index.js.map +1 -1
- package/dist/print-ck.d.ts +9 -3
- package/dist/print-ck.d.ts.map +1 -1
- package/dist/print-contract.d.ts +8 -5
- package/dist/print-contract.d.ts.map +1 -1
- package/dist/print-operation.d.ts +10 -6
- package/dist/print-operation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/print-ck.ts +65 -18
- package/src/print-contract.ts +14 -7
- package/src/print-operation.ts +136 -53
- package/tests/print-ck.test.ts +84 -4
- package/tests/round-trip.test.ts +489 -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
|
|
13
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 4442ms
|
|
13
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.13 KB[39m
|
|
@@ -3,21 +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[
|
|
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
|
|
7
10
|
|
|
8
|
-
[2m Test Files [22m [1m[
|
|
9
|
-
[2m Tests [22m [1m[
|
|
10
|
-
[2m Start at [22m
|
|
11
|
-
[2m Duration [22m
|
|
11
|
+
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
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
|
|
12
15
|
|
|
13
16
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
14
17
|
-------------------|---------|----------|---------|---------|-------------------
|
|
15
18
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
16
19
|
-------------------|---------|----------|---------|---------|-------------------
|
|
17
|
-
All files |
|
|
20
|
+
All files | 76.13 | 71.15 | 85.36 | 82.04 |
|
|
18
21
|
indent.ts | 100 | 100 | 100 | 100 |
|
|
19
|
-
print-ck.ts | 89.
|
|
20
|
-
print-contract.ts | 45.71 |
|
|
21
|
-
...t-operation.ts |
|
|
22
|
-
print-type.ts |
|
|
22
|
+
print-ck.ts | 89.09 | 87.56 | 100 | 97.36 | 91-92
|
|
23
|
+
print-contract.ts | 45.71 | 38.23 | 50 | 46.66 | 20,52-85
|
|
24
|
+
...t-operation.ts | 78.41 | 70.42 | 95 | 86.75 | ...37,354,362-373
|
|
25
|
+
print-type.ts | 64.13 | 63.46 | 63.63 | 66.66 | ...02-103,148-151
|
|
23
26
|
-------------------|---------|----------|---------|---------|-------------------
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
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
|
+
|
|
35
|
+
## 0.13.0
|
|
36
|
+
|
|
37
|
+
### Minor Changes
|
|
38
|
+
|
|
39
|
+
- 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.
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
Comments may now also sit directly inside an `options { ... }` block, between its sub-blocks, where the grammar previously rejected them.
|
|
44
|
+
|
|
45
|
+
These AST fields are additive and optional; codegen plugins ignore them.
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [23e4beb]
|
|
50
|
+
- @contractkit/core@0.24.0
|
|
51
|
+
|
|
3
52
|
## 0.12.2
|
|
4
53
|
|
|
5
54
|
### 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
|
|
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,21 @@ 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. 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.
|
|
59
|
+
|
|
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.
|
|
61
|
+
|
|
49
62
|
## Source layout
|
|
50
63
|
|
|
51
64
|
| Path | Purpose |
|
package/dist/index.js
CHANGED
|
@@ -150,7 +150,7 @@ function printModelDecl(model, printWidth = 80) {
|
|
|
150
150
|
if (model.type !== void 0) {
|
|
151
151
|
return printTypeAlias(model, printWidth);
|
|
152
152
|
}
|
|
153
|
-
const commentSuffix = model.description ? ` # ${model.description}` : "";
|
|
153
|
+
const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : "";
|
|
154
154
|
const modifiers = [
|
|
155
155
|
model.deprecated ? "deprecated" : "",
|
|
156
156
|
model.inputCase || model.outputCase ? `format(${[
|
|
@@ -177,7 +177,7 @@ function printModelDecl(model, printWidth = 80) {
|
|
|
177
177
|
__name(printModelDecl, "printModelDecl");
|
|
178
178
|
function printTypeAlias(model, printWidth) {
|
|
179
179
|
const type = model.type;
|
|
180
|
-
const commentSuffix = model.description ? ` # ${model.description}` : "";
|
|
180
|
+
const commentSuffix = model.description && model.descriptionInline ? ` # ${model.description}` : "";
|
|
181
181
|
const modifiers = [
|
|
182
182
|
model.deprecated ? "deprecated" : "",
|
|
183
183
|
model.inputCase || model.outputCase ? `format(${[
|
|
@@ -222,8 +222,7 @@ function flushBlocks(out, blocks, idx, beforeLine, _indent = "") {
|
|
|
222
222
|
__name(flushBlocks, "flushBlocks");
|
|
223
223
|
function printRoute(route, blocks, idx, nextRouteStart) {
|
|
224
224
|
const lines = [];
|
|
225
|
-
|
|
226
|
-
lines.push(`${route.path}: {${commentSuffix}`);
|
|
225
|
+
lines.push(`${route.path}: {`);
|
|
227
226
|
if (route.params !== void 0) {
|
|
228
227
|
lines.push(...printParamsBlock(route.params, I1, route.paramsMode));
|
|
229
228
|
}
|
|
@@ -231,6 +230,7 @@ function printRoute(route, blocks, idx, nextRouteStart) {
|
|
|
231
230
|
lines.push(...printSecurity(route.security, I1, I2));
|
|
232
231
|
}
|
|
233
232
|
for (const op of route.operations) {
|
|
233
|
+
if (op.blankLineBefore && lines.length > 1) lines.push("");
|
|
234
234
|
flushBlocks(lines, blocks, idx, op.loc.line, I1);
|
|
235
235
|
lines.push(...printOperation(op));
|
|
236
236
|
}
|
|
@@ -270,53 +270,131 @@ function printParamsBlock(source, indent, mode) {
|
|
|
270
270
|
];
|
|
271
271
|
}
|
|
272
272
|
__name(printParamsBlock, "printParamsBlock");
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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));
|
|
287
331
|
lines.push(`${I2}}`);
|
|
288
|
-
|
|
289
|
-
lines.push(`${I2}signature: ${formatSignatureValue(op.signature)}${comment}`);
|
|
332
|
+
return lines;
|
|
290
333
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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;
|
|
297
349
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (op.query !== void 0) lines.push(...printQueryOrHeaders("query", op.query, op.queryMode));
|
|
301
|
-
if (op.requestHeadersOptOut) {
|
|
302
|
-
lines.push(`${I2}headers: none`);
|
|
303
|
-
} else if (op.headers !== void 0) {
|
|
304
|
-
lines.push(...printQueryOrHeaders("headers", op.headers, op.headersMode));
|
|
350
|
+
case "responses":
|
|
351
|
+
return op.responses.length > 0 ? printResponseBlock(op.responses, op.responsesTrailingComments) : [];
|
|
305
352
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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}`);
|
|
312
361
|
}
|
|
313
|
-
|
|
314
|
-
|
|
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));
|
|
315
371
|
}
|
|
316
372
|
lines.push(`${I1}}`);
|
|
317
373
|
return lines;
|
|
318
374
|
}
|
|
319
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");
|
|
320
398
|
var IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
321
399
|
function escapeString(s) {
|
|
322
400
|
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
@@ -453,19 +531,29 @@ function printContentTypeLine(contentType, bodyType, lineIndent) {
|
|
|
453
531
|
];
|
|
454
532
|
}
|
|
455
533
|
__name(printContentTypeLine, "printContentTypeLine");
|
|
456
|
-
function printResponseBlock(responses) {
|
|
534
|
+
function printResponseBlock(responses, trailingComments) {
|
|
457
535
|
const lines = [
|
|
458
536
|
`${I2}response: {`
|
|
459
537
|
];
|
|
460
538
|
for (const resp of responses) {
|
|
461
|
-
const
|
|
539
|
+
for (const comment of resp.leadingComments ?? []) lines.push(`${I3}# ${comment}`);
|
|
540
|
+
const bodies = resp.bodies;
|
|
462
541
|
const hasHeaders = resp.headers && resp.headers.length > 0;
|
|
463
542
|
const optOut = resp.headersOptOut;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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));
|
|
468
555
|
}
|
|
556
|
+
for (const comment of resp.headersLeadingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
469
557
|
if (optOut) {
|
|
470
558
|
lines.push(`${I4}headers: none`);
|
|
471
559
|
} else if (hasHeaders) {
|
|
@@ -477,11 +565,13 @@ function printResponseBlock(responses) {
|
|
|
477
565
|
}
|
|
478
566
|
lines.push(`${I4}}`);
|
|
479
567
|
}
|
|
568
|
+
for (const comment of resp.trailingComments ?? []) lines.push(`${I4}# ${comment}`);
|
|
480
569
|
lines.push(`${I3}}`);
|
|
481
570
|
} else {
|
|
482
|
-
lines.push(`${I3}${
|
|
571
|
+
lines.push(`${I3}${code}:`);
|
|
483
572
|
}
|
|
484
573
|
}
|
|
574
|
+
for (const comment of trailingComments ?? []) lines.push(`${I3}# ${comment}`);
|
|
485
575
|
lines.push(`${I2}}`);
|
|
486
576
|
return lines;
|
|
487
577
|
}
|
|
@@ -489,15 +579,19 @@ __name(printResponseBlock, "printResponseBlock");
|
|
|
489
579
|
|
|
490
580
|
// src/print-ck.ts
|
|
491
581
|
var DEFAULT_PRINT_WIDTH = 80;
|
|
492
|
-
function quoteOptionsValue(value) {
|
|
493
|
-
|
|
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}"`;
|
|
494
586
|
}
|
|
495
587
|
__name(quoteOptionsValue, "quoteOptionsValue");
|
|
496
|
-
function emitOptionsEntries(lines, entries, comments) {
|
|
588
|
+
function emitOptionsEntries(lines, entries, comments, unquoted) {
|
|
497
589
|
const I22 = INDENT + INDENT;
|
|
590
|
+
const wasUnquoted = new Set(unquoted ?? []);
|
|
498
591
|
for (const [key, value] of Object.entries(entries)) {
|
|
499
592
|
for (const c of comments?.leading?.[key] ?? []) lines.push(`${I22}# ${c}`);
|
|
500
|
-
|
|
593
|
+
const inline = comments?.inline?.[key];
|
|
594
|
+
lines.push(`${I22}${key}: ${quoteOptionsValue(value, wasUnquoted.has(key))}${inline !== void 0 ? ` # ${inline}` : ""}`);
|
|
501
595
|
}
|
|
502
596
|
for (const c of comments?.trailing ?? []) lines.push(`${I22}# ${c}`);
|
|
503
597
|
}
|
|
@@ -508,29 +602,41 @@ function printOptionsBlock(ast) {
|
|
|
508
602
|
const hasSecurity = ast.security !== void 0;
|
|
509
603
|
const hasRequestHeaders = (ast.requestHeaders?.length ?? 0) > 0;
|
|
510
604
|
const hasResponseHeaders = (ast.responseHeaders?.length ?? 0) > 0;
|
|
511
|
-
|
|
605
|
+
const hasBodyComments = ast.optionsComments?.body !== void 0;
|
|
606
|
+
if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders && !hasBodyComments) return null;
|
|
512
607
|
const lines = [
|
|
608
|
+
...(ast.optionsComments?.leading ?? []).map((c) => `# ${c}`),
|
|
513
609
|
"options {"
|
|
514
610
|
];
|
|
611
|
+
const body = ast.optionsComments?.body;
|
|
612
|
+
const emitLeading = /* @__PURE__ */ __name((scope) => {
|
|
613
|
+
for (const c of body?.leading?.[scope] ?? []) lines.push(`${INDENT}# ${c}`);
|
|
614
|
+
}, "emitLeading");
|
|
515
615
|
if (hasMeta) {
|
|
616
|
+
emitLeading("keys");
|
|
516
617
|
lines.push(`${INDENT}keys: {`);
|
|
517
|
-
emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys);
|
|
618
|
+
emitOptionsEntries(lines, ast.meta, ast.optionsComments?.keys, ast.optionsUnquoted?.keys);
|
|
518
619
|
lines.push(`${INDENT}}`);
|
|
519
620
|
}
|
|
520
621
|
if (hasServices) {
|
|
622
|
+
emitLeading("services");
|
|
521
623
|
lines.push(`${INDENT}services: {`);
|
|
522
|
-
emitOptionsEntries(lines, ast.services, ast.optionsComments?.services);
|
|
624
|
+
emitOptionsEntries(lines, ast.services, ast.optionsComments?.services, ast.optionsUnquoted?.services);
|
|
523
625
|
lines.push(`${INDENT}}`);
|
|
524
626
|
}
|
|
525
627
|
if (hasRequestHeaders) {
|
|
628
|
+
emitLeading("request");
|
|
526
629
|
lines.push(...printOptionsHeaderScope("request", ast.requestHeaders));
|
|
527
630
|
}
|
|
528
631
|
if (hasResponseHeaders) {
|
|
632
|
+
emitLeading("response");
|
|
529
633
|
lines.push(...printOptionsHeaderScope("response", ast.responseHeaders));
|
|
530
634
|
}
|
|
531
635
|
if (hasSecurity) {
|
|
636
|
+
emitLeading("security");
|
|
532
637
|
lines.push(...printSecurity(ast.security, INDENT, INDENT + INDENT));
|
|
533
638
|
}
|
|
639
|
+
for (const c of body?.trailing ?? []) lines.push(`${INDENT}# ${c}`);
|
|
534
640
|
lines.push("}");
|
|
535
641
|
return lines.join("\n");
|
|
536
642
|
}
|
|
@@ -558,7 +664,7 @@ function printCk(ast, printWidth = DEFAULT_PRINT_WIDTH) {
|
|
|
558
664
|
if (options) parts.push(options);
|
|
559
665
|
for (const model of ast.models) {
|
|
560
666
|
if (parts.length > 0) parts.push("");
|
|
561
|
-
parts.push(`contract ${printModelDecl(model, printWidth)}`);
|
|
667
|
+
parts.push(printDeclLeadIn(model.leadingComments, model.descriptionInline ? void 0 : model.description) + `contract ${printModelDecl(model, printWidth)}`);
|
|
562
668
|
}
|
|
563
669
|
const emptyBlocks = [];
|
|
564
670
|
const emptyIdx = {
|
|
@@ -567,11 +673,23 @@ function printCk(ast, printWidth = DEFAULT_PRINT_WIDTH) {
|
|
|
567
673
|
for (const route of ast.routes) {
|
|
568
674
|
if (parts.length > 0) parts.push("");
|
|
569
675
|
const modPart = route.modifiers?.length ? `(${route.modifiers[0]})` : "";
|
|
570
|
-
parts.push(`operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
|
|
676
|
+
parts.push(printDeclLeadIn(route.leadingComments, route.description) + `operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
|
|
571
677
|
}
|
|
572
678
|
return parts.join("\n") + "\n";
|
|
573
679
|
}
|
|
574
680
|
__name(printCk, "printCk");
|
|
681
|
+
function printDeclLeadIn(leadingComments, description) {
|
|
682
|
+
const lines = [];
|
|
683
|
+
if (leadingComments?.length) {
|
|
684
|
+
for (const c of leadingComments) lines.push(`# ${c}`);
|
|
685
|
+
lines.push("");
|
|
686
|
+
}
|
|
687
|
+
if (description) {
|
|
688
|
+
for (const line of description.split("\n")) lines.push(`# ${line}`);
|
|
689
|
+
}
|
|
690
|
+
return lines.length > 0 ? lines.join("\n") + "\n" : "";
|
|
691
|
+
}
|
|
692
|
+
__name(printDeclLeadIn, "printDeclLeadIn");
|
|
575
693
|
|
|
576
694
|
// src/index.ts
|
|
577
695
|
var { hardline, join } = builders;
|