@contractkit/prettier-plugin 0.14.2 → 0.14.4

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/src/print-type.ts DELETED
@@ -1,155 +0,0 @@
1
- import type { ContractTypeNode, FieldNode, InlineObjectTypeNode } from '@contractkit/core';
2
- import { INDENT } from './indent.js';
3
-
4
- // ─── Type expression printer ────────────────────────────────────────────────
5
-
6
- /** Render a `ContractTypeNode` back to its `.ck` source string. */
7
- export function printType(type: ContractTypeNode): string {
8
- switch (type.kind) {
9
- case 'scalar': {
10
- const constraints: string[] = [];
11
- if (type.format !== undefined) {
12
- // Print unquoted when format contains only safe chars; quote otherwise
13
- const fmt = type.format;
14
- constraints.push(/^[a-zA-Z0-9\-.:/]+$/.test(fmt) ? fmt : `"${fmt}"`);
15
- }
16
- if (type.min !== undefined) constraints.push(`min=${type.min}`);
17
- if (type.max !== undefined) constraints.push(`max=${type.max}`);
18
- if (type.len !== undefined) constraints.push(`len=${type.len}`);
19
- if (type.regex !== undefined) constraints.push(`regex=/${type.regex}/`);
20
- return constraints.length > 0 ? `${type.name}(${constraints.join(', ')})` : type.name;
21
- }
22
- case 'array': {
23
- const args: string[] = [printType(type.item)];
24
- if (type.min !== undefined) args.push(`min=${type.min}`);
25
- if (type.max !== undefined) args.push(`max=${type.max}`);
26
- return `array(${args.join(', ')})`;
27
- }
28
- case 'tuple':
29
- return `tuple(${type.items.map(printType).join(', ')})`;
30
- case 'record':
31
- return `record(${printType(type.key)}, ${printType(type.value)})`;
32
- case 'enum':
33
- return `enum(${type.values.map(formatEnumValue).join(', ')})`;
34
- case 'literal':
35
- return typeof type.value === 'string' ? `literal("${type.value}")` : `literal(${type.value})`;
36
- case 'union':
37
- return type.members.map(printType).join(' | ');
38
- case 'discriminatedUnion':
39
- return `discriminated(by=${type.discriminator}, ${type.members.map(printType).join(' | ')})`;
40
- case 'intersection':
41
- return type.members.map(printType).join(' & ');
42
- case 'ref':
43
- return type.name;
44
- case 'inlineObject':
45
- return printInlineObjectCompact(type);
46
- case 'lazy':
47
- return `lazy(${printType(type.inner)})`;
48
- }
49
- }
50
-
51
- /** Compact single-line form — used when inline object appears nested inside another type. */
52
- function printInlineObjectCompact(obj: InlineObjectTypeNode): string {
53
- const prefix = obj.mode ? `mode(${obj.mode}) ` : '';
54
- if (obj.fields.length === 0) return `${prefix}{}`;
55
- const parts = obj.fields.map(f => {
56
- const opt = f.optional ? '?' : '';
57
- let t = printType(f.type);
58
- if (f.nullable) t += ' | null';
59
- return `${f.name}${opt}: ${t}`;
60
- });
61
- return `${prefix}{ ${parts.join(', ')} }`;
62
- }
63
-
64
- /** Multi-line enum form — one value per line, used when single-line would exceed print width. */
65
- export function printEnumExpanded(values: string[], indent: string): string {
66
- const innerIndent = indent + INDENT;
67
- return `enum(\n${values.map(v => `${innerIndent}${formatEnumValue(v)}`).join(',\n')}\n${indent})`;
68
- }
69
-
70
- // ─── Field printer ──────────────────────────────────────────────────────────
71
-
72
- /** Print a full field declaration, including visibility, default, and inline comment.
73
- * Modifier order is canonical: override → deprecated → readonly|writeonly → type. */
74
- export function printField(field: FieldNode, indent: string, printWidth: number = 80): string {
75
- const opt = field.optional ? '?' : '';
76
- const ovr = field.override ? 'override ' : '';
77
- const dep = field.deprecated ? 'deprecated ' : '';
78
- const vis = field.visibility !== 'normal' ? `${field.visibility} ` : '';
79
- const mods = `${ovr}${dep}${vis}`;
80
- const def = field.default !== undefined ? ` = ${formatDefault(field.default)}` : '';
81
- const comment = field.description ? ` # ${field.description}` : '';
82
- const innerIndent = indent + INDENT;
83
-
84
- // Expand inline object types to multi-line — same rule as type aliases.
85
- // Only when there's no default and no nullable union (those can't split cleanly).
86
- if (!field.nullable && field.default === undefined) {
87
- const trailing = extractTrailingInlineObject(field.type);
88
- if (trailing) {
89
- const { prefix, inlineObj } = trailing;
90
- const modePart = inlineObj.mode ? `mode(${inlineObj.mode}) ` : '';
91
- const header = prefix
92
- ? `${indent}${field.name}${opt}: ${mods}${prefix} & ${modePart}{${comment}`
93
- : `${indent}${field.name}${opt}: ${mods}${modePart}{${comment}`;
94
- return [header, ...printInlineObjectExpanded(inlineObj, innerIndent, printWidth), `${indent}}`].join('\n');
95
- }
96
- }
97
-
98
- let typeStr = printType(field.type);
99
- if (field.nullable) typeStr += ' | null';
100
- const fullLine = `${indent}${field.name}${opt}: ${mods}${typeStr}${def}${comment}`;
101
- if (field.type.kind === 'enum' && !field.nullable && field.default === undefined && fullLine.length > printWidth) {
102
- const enumStr = printEnumExpanded(field.type.values, indent);
103
- return `${indent}${field.name}${opt}: ${mods}${enumStr}${comment}`;
104
- }
105
- return fullLine;
106
- }
107
-
108
- /** Print inline-object fields expanded (used when an inline brace object trails a type alias).
109
- * Any `trailingComments` (comments after the last field, before `}`) are emitted as indented
110
- * `# text` lines after the fields, matching how model bodies round-trip trailing comments. */
111
- export function printInlineObjectExpanded(obj: InlineObjectTypeNode, indent: string, printWidth: number = 80): string[] {
112
- const lines = obj.fields.map(f => printField(f, indent, printWidth));
113
- for (const comment of obj.trailingComments ?? []) {
114
- lines.push(`${indent}# ${comment}`);
115
- }
116
- return lines;
117
- }
118
-
119
- // ─── Helpers ────────────────────────────────────────────────────────────────
120
-
121
- /** Format a single enum value: bare identifier stays bare; anything else gets double-quoted. */
122
- export function formatEnumValue(v: string): string {
123
- if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(v)) return v;
124
- return `"${v}"`;
125
- }
126
-
127
- /** Format a default value: quote strings that aren't valid bare identifiers. */
128
- export function formatDefault(val: string | number | boolean): string {
129
- if (typeof val === 'number' || typeof val === 'boolean') return String(val);
130
- // If it looks like a bare identifier (enum value, unquoted token), keep it bare.
131
- if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(val)) return val;
132
- return `"${val}"`;
133
- }
134
-
135
- /**
136
- * Detect whether the last member of a type is an inline brace object, and if so
137
- * return the prefix type string and the inline object for expanded printing.
138
- * Returns null if the type doesn't end with an inline object.
139
- */
140
- export function extractTrailingInlineObject(type: ContractTypeNode): {
141
- prefix: string | null;
142
- inlineObj: InlineObjectTypeNode;
143
- } | null {
144
- if (type.kind === 'inlineObject') {
145
- return { prefix: null, inlineObj: type };
146
- }
147
- if (type.kind === 'intersection') {
148
- const last = type.members[type.members.length - 1];
149
- if (last?.kind === 'inlineObject') {
150
- const prefixStr = type.members.slice(0, -1).map(printType).join(' & ');
151
- return { prefix: prefixStr, inlineObj: last };
152
- }
153
- }
154
- return null;
155
- }