@contractkit/prettier-plugin 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$ci.log +6 -6
- package/.turbo/turbo-test$colon$ci.log +9 -9
- package/CHANGELOG.md +17 -0
- package/coverage/clover.xml +132 -131
- package/coverage/coverage-final.json +3 -3
- package/coverage/indent.ts.html +1 -1
- package/coverage/index.html +31 -31
- package/coverage/print-ck.ts.html +110 -56
- package/coverage/print-contract.ts.html +16 -16
- package/coverage/print-operation.ts.html +1 -1
- package/coverage/print-type.ts.html +56 -35
- package/dist/index.js +13 -6
- package/dist/index.js.map +1 -1
- package/dist/print-ck.d.ts +8 -0
- package/dist/print-ck.d.ts.map +1 -1
- package/dist/print-type.d.ts +3 -0
- package/dist/print-type.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/print-ck.ts +22 -4
- package/src/print-type.ts +9 -2
- package/tests/print-ck.test.ts +91 -0
package/src/print-ck.ts
CHANGED
|
@@ -8,6 +8,18 @@ export const DEFAULT_PRINT_WIDTH = 80;
|
|
|
8
8
|
|
|
9
9
|
// ─── Options block ──────────────────────────────────────────────────────────
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Quote an options-block value if it isn't a plain identifier.
|
|
13
|
+
*
|
|
14
|
+
* Plain identifiers (starts with letter/underscore/dollar, rest are
|
|
15
|
+
* alphanumeric/underscore/dollar/hyphen/dot) are left bare. Everything
|
|
16
|
+
* else — paths with slashes, values starting with `#`, values with spaces,
|
|
17
|
+
* etc. — is double-quoted so the round-trip parse is unambiguous.
|
|
18
|
+
*/
|
|
19
|
+
function quoteOptionsValue(value: string): string {
|
|
20
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value) ? value : `"${value}"`;
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
function printOptionsBlock(ast: CkRootNode): string | null {
|
|
12
24
|
const hasMeta = Object.keys(ast.meta).length > 0;
|
|
13
25
|
const hasServices = Object.keys(ast.services).length > 0;
|
|
@@ -22,8 +34,7 @@ function printOptionsBlock(ast: CkRootNode): string | null {
|
|
|
22
34
|
if (hasMeta) {
|
|
23
35
|
lines.push(`${INDENT}keys: {`);
|
|
24
36
|
for (const [key, value] of Object.entries(ast.meta)) {
|
|
25
|
-
|
|
26
|
-
lines.push(`${INDENT}${INDENT}${key}: ${v}`);
|
|
37
|
+
lines.push(`${INDENT}${INDENT}${key}: ${quoteOptionsValue(value)}`);
|
|
27
38
|
}
|
|
28
39
|
lines.push(`${INDENT}}`);
|
|
29
40
|
}
|
|
@@ -31,8 +42,7 @@ function printOptionsBlock(ast: CkRootNode): string | null {
|
|
|
31
42
|
if (hasServices) {
|
|
32
43
|
lines.push(`${INDENT}services: {`);
|
|
33
44
|
for (const [key, value] of Object.entries(ast.services)) {
|
|
34
|
-
|
|
35
|
-
lines.push(`${INDENT}${INDENT}${key}: ${v}`);
|
|
45
|
+
lines.push(`${INDENT}${INDENT}${key}: ${quoteOptionsValue(value)}`);
|
|
36
46
|
}
|
|
37
47
|
lines.push(`${INDENT}}`);
|
|
38
48
|
}
|
|
@@ -69,6 +79,14 @@ function printOptionsHeaderScope(keyword: 'request' | 'response', headers: OpRes
|
|
|
69
79
|
|
|
70
80
|
// ─── CK file printer ───────────────────────────────────────────────────────
|
|
71
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Render a parsed `.ck` AST back to source. Output is byte-identical on
|
|
84
|
+
* round-trip when the input is already canonically formatted: options block
|
|
85
|
+
* first, then contracts, then operations, separated by blank lines.
|
|
86
|
+
*
|
|
87
|
+
* `printWidth` is forwarded to per-model printing for line wrapping inside
|
|
88
|
+
* inline-object types.
|
|
89
|
+
*/
|
|
72
90
|
export function printCk(ast: CkRootNode, printWidth: number = DEFAULT_PRINT_WIDTH): string {
|
|
73
91
|
const parts: string[] = [];
|
|
74
92
|
|
package/src/print-type.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { INDENT } from './indent.js';
|
|
|
3
3
|
|
|
4
4
|
// ─── Type expression printer ────────────────────────────────────────────────
|
|
5
5
|
|
|
6
|
+
/** Render a `ContractTypeNode` back to its `.ck` source string. */
|
|
6
7
|
export function printType(type: ContractTypeNode): string {
|
|
7
8
|
switch (type.kind) {
|
|
8
9
|
case 'scalar': {
|
|
@@ -29,7 +30,7 @@ export function printType(type: ContractTypeNode): string {
|
|
|
29
30
|
case 'record':
|
|
30
31
|
return `record(${printType(type.key)}, ${printType(type.value)})`;
|
|
31
32
|
case 'enum':
|
|
32
|
-
return `enum(${type.values.join(', ')})`;
|
|
33
|
+
return `enum(${type.values.map(formatEnumValue).join(', ')})`;
|
|
33
34
|
case 'literal':
|
|
34
35
|
return typeof type.value === 'string' ? `literal("${type.value}")` : `literal(${type.value})`;
|
|
35
36
|
case 'union':
|
|
@@ -63,7 +64,7 @@ function printInlineObjectCompact(obj: InlineObjectTypeNode): string {
|
|
|
63
64
|
/** Multi-line enum form — one value per line, used when single-line would exceed print width. */
|
|
64
65
|
export function printEnumExpanded(values: string[], indent: string): string {
|
|
65
66
|
const innerIndent = indent + INDENT;
|
|
66
|
-
return `enum(\n${values.map(v => `${innerIndent}${v}`).join(',\n')}\n${indent})`;
|
|
67
|
+
return `enum(\n${values.map(v => `${innerIndent}${formatEnumValue(v)}`).join(',\n')}\n${indent})`;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
// ─── Field printer ──────────────────────────────────────────────────────────
|
|
@@ -111,6 +112,12 @@ export function printInlineObjectExpanded(obj: InlineObjectTypeNode, indent: str
|
|
|
111
112
|
|
|
112
113
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
113
114
|
|
|
115
|
+
/** Format a single enum value: bare identifier stays bare; anything else gets double-quoted. */
|
|
116
|
+
export function formatEnumValue(v: string): string {
|
|
117
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(v)) return v;
|
|
118
|
+
return `"${v}"`;
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
/** Format a default value: quote strings that aren't valid bare identifiers. */
|
|
115
122
|
export function formatDefault(val: string | number | boolean): string {
|
|
116
123
|
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
package/tests/print-ck.test.ts
CHANGED
|
@@ -649,3 +649,94 @@ operation /users/{slug}: {
|
|
|
649
649
|
expect(roundTrip(source)).toBe(source);
|
|
650
650
|
});
|
|
651
651
|
});
|
|
652
|
+
|
|
653
|
+
describe('printCk — options keys and services quoting', () => {
|
|
654
|
+
function roundTrip(source: string): string {
|
|
655
|
+
const diag = new DiagnosticCollector();
|
|
656
|
+
const ast = parseCk(source, 'test.ck', diag);
|
|
657
|
+
expect(diag.hasErrors()).toBe(false);
|
|
658
|
+
return printCk(ast);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
it('leaves simple identifier keys values unquoted', () => {
|
|
662
|
+
const source = `\
|
|
663
|
+
options {
|
|
664
|
+
keys: {
|
|
665
|
+
area: payments
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
`;
|
|
669
|
+
expect(roundTrip(source)).toBe(source);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('preserves quotes on path-like values containing slashes', () => {
|
|
673
|
+
const source = `\
|
|
674
|
+
options {
|
|
675
|
+
keys: {
|
|
676
|
+
bruno: "../../bruno/"
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
`;
|
|
680
|
+
expect(roundTrip(source)).toBe(source);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('quotes values that start with a dot', () => {
|
|
684
|
+
const source = `\
|
|
685
|
+
options {
|
|
686
|
+
keys: {
|
|
687
|
+
path: "../relative"
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
`;
|
|
691
|
+
expect(roundTrip(source)).toBe(source);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('preserves quotes on service paths starting with #', () => {
|
|
695
|
+
const source = `\
|
|
696
|
+
options {
|
|
697
|
+
services: {
|
|
698
|
+
AuthService: "#src/modules/auth/auth.service.js"
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
`;
|
|
702
|
+
expect(roundTrip(source)).toBe(source);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('leaves plain identifier service names unquoted', () => {
|
|
706
|
+
const source = `\
|
|
707
|
+
options {
|
|
708
|
+
services: {
|
|
709
|
+
AuthService: authService
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
`;
|
|
713
|
+
expect(roundTrip(source)).toBe(source);
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
describe('printCk — enum values with spaces (round-trip)', () => {
|
|
718
|
+
function roundTrip(source: string): string {
|
|
719
|
+
const diag = new DiagnosticCollector();
|
|
720
|
+
const ast = parseCk(source, 'test.ck', diag);
|
|
721
|
+
expect(diag.hasErrors()).toBe(false);
|
|
722
|
+
return printCk(ast);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
it('round-trips enum with bare identifiers unchanged', () => {
|
|
726
|
+
const source = `\
|
|
727
|
+
contract M: {
|
|
728
|
+
status: enum(active, inactive, pending)
|
|
729
|
+
}
|
|
730
|
+
`;
|
|
731
|
+
expect(roundTrip(source)).toBe(source);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('round-trips enum with quoted multi-word values', () => {
|
|
735
|
+
const source = `\
|
|
736
|
+
contract M: {
|
|
737
|
+
entityType: enum("Sole Proprietorship", LLC, "Limited Partnership")
|
|
738
|
+
}
|
|
739
|
+
`;
|
|
740
|
+
expect(roundTrip(source)).toBe(source);
|
|
741
|
+
});
|
|
742
|
+
});
|