@kernlang/core 3.3.9 → 3.4.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/dist/capability-matrix.d.ts +15 -0
- package/dist/capability-matrix.js +245 -0
- package/dist/capability-matrix.js.map +1 -0
- package/dist/codegen/data-layer.d.ts +1 -1
- package/dist/codegen/data-layer.js +59 -23
- package/dist/codegen/data-layer.js.map +1 -1
- package/dist/codegen/events.js +1 -1
- package/dist/codegen/events.js.map +1 -1
- package/dist/codegen/functions.js +29 -5
- package/dist/codegen/functions.js.map +1 -1
- package/dist/codegen/ground-layer.js +10 -6
- package/dist/codegen/ground-layer.js.map +1 -1
- package/dist/codegen/helpers.d.ts +3 -1
- package/dist/codegen/helpers.js +5 -1
- package/dist/codegen/helpers.js.map +1 -1
- package/dist/codegen/machines.js +4 -3
- package/dist/codegen/machines.js.map +1 -1
- package/dist/codegen/modules.d.ts +1 -0
- package/dist/codegen/modules.js +52 -1
- package/dist/codegen/modules.js.map +1 -1
- package/dist/codegen/screens.js +31 -9
- package/dist/codegen/screens.js.map +1 -1
- package/dist/codegen/stdlib-preamble.d.ts +39 -0
- package/dist/codegen/stdlib-preamble.js +213 -0
- package/dist/codegen/stdlib-preamble.js.map +1 -0
- package/dist/codegen/type-system.d.ts +113 -1
- package/dist/codegen/type-system.js +389 -30
- package/dist/codegen/type-system.js.map +1 -1
- package/dist/codegen-core.d.ts +2 -2
- package/dist/codegen-core.js +58 -10
- package/dist/codegen-core.js.map +1 -1
- package/dist/codegen-expression.d.ts +3 -0
- package/dist/codegen-expression.js +93 -0
- package/dist/codegen-expression.js.map +1 -0
- package/dist/concepts.d.ts +3 -3
- package/dist/config.d.ts +16 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/decompiler.js +356 -4
- package/dist/decompiler.js.map +1 -1
- package/dist/importer.d.ts +1 -0
- package/dist/importer.js +574 -34
- package/dist/importer.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/node-props.d.ts +177 -1
- package/dist/node-props.js.map +1 -1
- package/dist/parser-core.js +30 -5
- package/dist/parser-core.js.map +1 -1
- package/dist/parser-diagnostics.js +5 -0
- package/dist/parser-diagnostics.js.map +1 -1
- package/dist/parser-expression.d.ts +17 -0
- package/dist/parser-expression.js +498 -0
- package/dist/parser-expression.js.map +1 -0
- package/dist/parser-tokenizer.d.ts +5 -3
- package/dist/parser-tokenizer.js +97 -16
- package/dist/parser-tokenizer.js.map +1 -1
- package/dist/parser-validate-effects.d.ts +21 -0
- package/dist/parser-validate-effects.js +188 -0
- package/dist/parser-validate-effects.js.map +1 -0
- package/dist/parser-validate-expressions.d.ts +6 -0
- package/dist/parser-validate-expressions.js +41 -0
- package/dist/parser-validate-expressions.js.map +1 -0
- package/dist/parser-validate-union-kind.d.ts +24 -0
- package/dist/parser-validate-union-kind.js +97 -0
- package/dist/parser-validate-union-kind.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +373 -27
- package/dist/schema.js.map +1 -1
- package/dist/semantic-validator.js +15 -8
- package/dist/semantic-validator.js.map +1 -1
- package/dist/spec.d.ts +5 -2
- package/dist/spec.js +24 -2
- package/dist/spec.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/dist/value-ir.d.ts +69 -0
- package/dist/value-ir.js +20 -0
- package/dist/value-ir.js.map +1 -0
- package/package.json +1 -1
package/dist/importer.js
CHANGED
|
@@ -44,6 +44,16 @@ function typeToString(typeNode, source) {
|
|
|
44
44
|
// consumes the value as a string either way.
|
|
45
45
|
return /\s/.test(raw) ? `"${raw.replace(/"/g, '\\"')}"` : raw;
|
|
46
46
|
}
|
|
47
|
+
/** Slice 2f — extract `<T, U extends Foo = Bar>` from any declaration that
|
|
48
|
+
* carries typeParameters and return a `generics="..."` attribute fragment
|
|
49
|
+
* ready to splice into a KERN line. Returns empty string when there are none. */
|
|
50
|
+
function genericsAttr(typeParameters, source) {
|
|
51
|
+
if (!typeParameters || typeParameters.length === 0)
|
|
52
|
+
return '';
|
|
53
|
+
const parts = typeParameters.map((tp) => tp.getText(source));
|
|
54
|
+
const block = `<${parts.join(', ')}>`;
|
|
55
|
+
return ` generics="${escapeKernString(block)}"`;
|
|
56
|
+
}
|
|
47
57
|
function getJSDoc(node, source) {
|
|
48
58
|
const jsDocs = node.jsDoc;
|
|
49
59
|
if (!jsDocs || jsDocs.length === 0)
|
|
@@ -64,7 +74,7 @@ function _indent(lines, depth) {
|
|
|
64
74
|
const prefix = ' '.repeat(depth);
|
|
65
75
|
return lines.map((l) => `${prefix}${l}`);
|
|
66
76
|
}
|
|
67
|
-
function escapeKernString(s) {
|
|
77
|
+
export function escapeKernString(s) {
|
|
68
78
|
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
69
79
|
}
|
|
70
80
|
function formatParams(params, source) {
|
|
@@ -73,11 +83,153 @@ function formatParams(params, source) {
|
|
|
73
83
|
const name = p.name.getText(source);
|
|
74
84
|
const type = typeToString(p.type, source);
|
|
75
85
|
const optional = p.questionToken ? '?' : '';
|
|
86
|
+
// Codex review fix: variadic `...` was dropped by the legacy serializer.
|
|
87
|
+
// Without this, `function f(...[first]: T[])` and `function f(...args: T[])`
|
|
88
|
+
// both round-tripped without their rest marker, silently changing the
|
|
89
|
+
// call signature.
|
|
90
|
+
const dots = p.dotDotDotToken ? '...' : '';
|
|
76
91
|
const defaultVal = p.initializer ? `=${p.initializer.getText(source)}` : '';
|
|
77
|
-
return type ? `${name}${optional}:${type}${defaultVal}` : `${name}${optional}${defaultVal}`;
|
|
92
|
+
return type ? `${dots}${name}${optional}:${type}${defaultVal}` : `${dots}${name}${optional}${defaultVal}`;
|
|
78
93
|
})
|
|
79
94
|
.join(',');
|
|
80
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Slice 3c — try to emit fn parameters as structured `param` child nodes
|
|
98
|
+
* (for ValueIR canonicalisation of defaults). Returns the child lines on
|
|
99
|
+
* success, or `null` if the signature contains any feature not yet
|
|
100
|
+
* supported by structured params: optional `?`, variadic `...`, or
|
|
101
|
+
* destructuring patterns. Producers fall back to the legacy `params="..."`
|
|
102
|
+
* string for those signatures (all-or-nothing per signature).
|
|
103
|
+
*
|
|
104
|
+
* Caller is responsible for indenting the returned lines and omitting the
|
|
105
|
+
* `params="..."` attribute from the parent.
|
|
106
|
+
*/
|
|
107
|
+
function tryFormatParamChildren(parameters, source) {
|
|
108
|
+
if (parameters.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
for (const p of parameters) {
|
|
111
|
+
// Slice 3c-extension: optional `?`, variadic `...`, and destructured
|
|
112
|
+
// `{a,b}` / `[x,y]` are now structurable via `optional=true` /
|
|
113
|
+
// `variadic=true` / `binding`+`element` children (gates dropped).
|
|
114
|
+
// Anything else on the LHS still bails to legacy.
|
|
115
|
+
if (!ts.isIdentifier(p.name) && !ts.isObjectBindingPattern(p.name) && !ts.isArrayBindingPattern(p.name)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
// Multi-line types (inline object shapes spread across lines) can't be
|
|
119
|
+
// round-tripped through a single-line `type="..."` quoted attribute —
|
|
120
|
+
// KERN's tokeniser treats newlines as record separators. Leave the
|
|
121
|
+
// whole signature legacy when any param has a multi-line type.
|
|
122
|
+
if (p.type && /\n/.test(p.type.getText(source)))
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const lines = [];
|
|
126
|
+
for (const p of parameters) {
|
|
127
|
+
const isObj = ts.isObjectBindingPattern(p.name);
|
|
128
|
+
const isArr = ts.isArrayBindingPattern(p.name);
|
|
129
|
+
// Codex P1 fix: read the RAW type text here (not via `typeToString`,
|
|
130
|
+
// which pre-quotes whitespace types like `"string | null"`). Structured
|
|
131
|
+
// `param` children always wrap the type in quotes, so a pre-quoted
|
|
132
|
+
// value would double-wrap to `type="\"string | null\""` — invalid TS
|
|
133
|
+
// (the resulting fn would have `x: "string | null"` as a string-literal
|
|
134
|
+
// type, not a union). Legacy `params="..."` callers still go through
|
|
135
|
+
// `typeToString` and its space-aware pre-quoting.
|
|
136
|
+
const type = p.type ? p.type.getText(source) : '';
|
|
137
|
+
if (isObj || isArr) {
|
|
138
|
+
// Slice 3c-extension #3: destructured param. Convert the binding pattern
|
|
139
|
+
// to `binding`/`element` child lines (slice 3d's nodes). Bail to legacy
|
|
140
|
+
// if the pattern uses rest/defaults/nesting, since slice 3d's structured
|
|
141
|
+
// form doesn't represent those — the whole signature falls back to
|
|
142
|
+
// `params="..."` so the parent can still emit valid TS.
|
|
143
|
+
//
|
|
144
|
+
// Codex review fix: `function f(...[first]: T[])` (variadic + array
|
|
145
|
+
// destructure) is valid TS but the structured form has no slot for the
|
|
146
|
+
// outer `...`. Bail to legacy so the rest marker is preserved verbatim
|
|
147
|
+
// in `params="..."` rather than silently dropped on round-trip.
|
|
148
|
+
if (p.dotDotDotToken)
|
|
149
|
+
return null;
|
|
150
|
+
const childLines = tryFormatParamBindingPattern(p.name, source);
|
|
151
|
+
if (childLines === null)
|
|
152
|
+
return null;
|
|
153
|
+
// The destructured-param header line carries no `name=`. Type / optional
|
|
154
|
+
// / default still apply to the whole pattern: `({a,b}: T = {a:1,b:2})`.
|
|
155
|
+
const parts = ['param'];
|
|
156
|
+
if (type)
|
|
157
|
+
parts.push(`type="${escapeKernString(type)}"`);
|
|
158
|
+
if (p.questionToken)
|
|
159
|
+
parts.push('optional=true');
|
|
160
|
+
if (p.initializer)
|
|
161
|
+
parts.push(`value={{ ${p.initializer.getText(source)} }}`);
|
|
162
|
+
lines.push(parts.join(' '));
|
|
163
|
+
for (const child of childLines)
|
|
164
|
+
lines.push(` ${child}`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const name = p.name.getText(source);
|
|
168
|
+
const parts = [`param name=${name}`];
|
|
169
|
+
if (type)
|
|
170
|
+
parts.push(`type="${escapeKernString(type)}"`);
|
|
171
|
+
if (p.questionToken)
|
|
172
|
+
parts.push('optional=true');
|
|
173
|
+
if (p.dotDotDotToken)
|
|
174
|
+
parts.push('variadic=true');
|
|
175
|
+
if (p.initializer) {
|
|
176
|
+
parts.push(`value={{ ${p.initializer.getText(source)} }}`);
|
|
177
|
+
}
|
|
178
|
+
lines.push(parts.join(' '));
|
|
179
|
+
}
|
|
180
|
+
return lines;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Slice 3c-extension #3 — convert a TS parameter `BindingPattern` (object or
|
|
184
|
+
* array) into structured `binding` / `element` KERN child lines (slice 3d's
|
|
185
|
+
* shape). Returns null when the pattern uses features the structured form
|
|
186
|
+
* can't represent: rest (`...rest`), defaults (`{a = 1}`), or nested patterns
|
|
187
|
+
* (`{a: {b}}`). Caller falls back to legacy `params="..."` when null.
|
|
188
|
+
*
|
|
189
|
+
* Mirrors `tryFormatDestructure`'s sub-branch for slice 3d, but tightened to
|
|
190
|
+
* just the LHS pattern (no source/kind/type — those live on the param node).
|
|
191
|
+
*/
|
|
192
|
+
function tryFormatParamBindingPattern(pattern, source) {
|
|
193
|
+
const childLines = [];
|
|
194
|
+
if (ts.isObjectBindingPattern(pattern)) {
|
|
195
|
+
for (const el of pattern.elements) {
|
|
196
|
+
if (el.dotDotDotToken)
|
|
197
|
+
return null;
|
|
198
|
+
if (el.initializer)
|
|
199
|
+
return null;
|
|
200
|
+
if (!ts.isIdentifier(el.name))
|
|
201
|
+
return null;
|
|
202
|
+
const localName = el.name.getText(source);
|
|
203
|
+
let line = `binding name=${localName}`;
|
|
204
|
+
if (el.propertyName) {
|
|
205
|
+
if (!ts.isIdentifier(el.propertyName))
|
|
206
|
+
return null;
|
|
207
|
+
line += ` key=${el.propertyName.getText(source)}`;
|
|
208
|
+
}
|
|
209
|
+
childLines.push(line);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
let idx = 0;
|
|
214
|
+
for (const el of pattern.elements) {
|
|
215
|
+
if (ts.isOmittedExpression(el)) {
|
|
216
|
+
idx++;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (el.dotDotDotToken)
|
|
220
|
+
return null;
|
|
221
|
+
if (el.initializer)
|
|
222
|
+
return null;
|
|
223
|
+
if (!ts.isIdentifier(el.name))
|
|
224
|
+
return null;
|
|
225
|
+
childLines.push(`element name=${el.name.getText(source)} index=${idx}`);
|
|
226
|
+
idx++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (childLines.length === 0)
|
|
230
|
+
return null;
|
|
231
|
+
return childLines;
|
|
232
|
+
}
|
|
81
233
|
function getBodyText(body, source) {
|
|
82
234
|
if (!body)
|
|
83
235
|
return undefined;
|
|
@@ -151,6 +303,7 @@ function convertTypeAlias(node, source) {
|
|
|
151
303
|
const lines = [];
|
|
152
304
|
const name = node.name.getText(source);
|
|
153
305
|
const exp = isExported(node) ? ' export=true' : '';
|
|
306
|
+
const generics = genericsAttr(node.typeParameters, source);
|
|
154
307
|
const doc = getJSDoc(node, source);
|
|
155
308
|
if (doc)
|
|
156
309
|
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
@@ -160,19 +313,20 @@ function convertTypeAlias(node, source) {
|
|
|
160
313
|
const allStringLiterals = members.every((m) => ts.isLiteralTypeNode(m) && m.literal.kind === ts.SyntaxKind.StringLiteral);
|
|
161
314
|
if (allStringLiterals) {
|
|
162
315
|
const values = members.map((m) => m.literal.text).join('|');
|
|
163
|
-
lines.push(`type name=${name} values="${values}"${exp}`);
|
|
316
|
+
lines.push(`type name=${name} values="${values}"${generics}${exp}`);
|
|
164
317
|
return lines;
|
|
165
318
|
}
|
|
166
319
|
}
|
|
167
320
|
// General type alias
|
|
168
321
|
const typeText = typeToString(node.type, source);
|
|
169
|
-
lines.push(`type name=${name} alias="${escapeKernString(typeText)}"${exp}`);
|
|
322
|
+
lines.push(`type name=${name} alias="${escapeKernString(typeText)}"${generics}${exp}`);
|
|
170
323
|
return lines;
|
|
171
324
|
}
|
|
172
325
|
function convertInterface(node, source) {
|
|
173
326
|
const lines = [];
|
|
174
327
|
const name = node.name.getText(source);
|
|
175
328
|
const exp = isExported(node) ? ' export=true' : '';
|
|
329
|
+
const generics = genericsAttr(node.typeParameters, source);
|
|
176
330
|
const doc = getJSDoc(node, source);
|
|
177
331
|
if (doc)
|
|
178
332
|
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
@@ -180,7 +334,7 @@ function convertInterface(node, source) {
|
|
|
180
334
|
?.filter((h) => h.token === ts.SyntaxKind.ExtendsKeyword)
|
|
181
335
|
.flatMap((h) => h.types.map((t) => t.getText(source)));
|
|
182
336
|
const extendsStr = extends_ && extends_.length > 0 ? ` extends=${extends_.join(',')}` : '';
|
|
183
|
-
lines.push(`interface name=${name}${extendsStr}${exp}`);
|
|
337
|
+
lines.push(`interface name=${name}${generics}${extendsStr}${exp}`);
|
|
184
338
|
for (const member of node.members) {
|
|
185
339
|
if (ts.isPropertySignature(member)) {
|
|
186
340
|
const fieldName = member.name.getText(source);
|
|
@@ -196,10 +350,26 @@ function convertInterface(node, source) {
|
|
|
196
350
|
const methodName = member.name.getText(source);
|
|
197
351
|
const params = member.parameters ? formatParams(member.parameters, source) : '';
|
|
198
352
|
const returns = typeToString(member.type, source) || 'void';
|
|
199
|
-
|
|
353
|
+
// Slice 2f — preserve method-level generics by prepending <T,...> to the function-type
|
|
354
|
+
// string when the method has typeParameters. Format: `<T>(params) => R`.
|
|
355
|
+
const tp = member.typeParameters;
|
|
356
|
+
const genericsBlock = tp && tp.length > 0 ? `<${tp.map((t) => t.getText(source)).join(', ')}>` : '';
|
|
357
|
+
const funcType = `${genericsBlock}(${params}) => ${returns}`;
|
|
200
358
|
const optional = member.questionToken ? ' optional=true' : '';
|
|
201
359
|
lines.push(` field name=${methodName} type="${escapeKernString(funcType)}"${optional}`);
|
|
202
360
|
}
|
|
361
|
+
else if (ts.isIndexSignatureDeclaration(member)) {
|
|
362
|
+
// [keyName: keyType]: type → indexer keyName=... keyType=... type=...
|
|
363
|
+
const param = member.parameters[0];
|
|
364
|
+
if (param) {
|
|
365
|
+
const keyName = param.name.getText(source);
|
|
366
|
+
const keyType = typeToString(param.type, source) || 'string';
|
|
367
|
+
const valueType = typeToString(member.type, source) || 'unknown';
|
|
368
|
+
const isReadonly = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword);
|
|
369
|
+
const ro = isReadonly ? ' readonly=true' : '';
|
|
370
|
+
lines.push(` indexer keyName=${keyName} keyType=${keyType} type=${valueType}${ro}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
203
373
|
}
|
|
204
374
|
return lines;
|
|
205
375
|
}
|
|
@@ -207,19 +377,47 @@ function convertEnum(node, source) {
|
|
|
207
377
|
const lines = [];
|
|
208
378
|
const name = node.name.getText(source);
|
|
209
379
|
const exp = isExported(node) ? ' export=true' : '';
|
|
380
|
+
const isConst = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword);
|
|
381
|
+
const constStr = isConst ? ' const=true' : '';
|
|
210
382
|
const doc = getJSDoc(node, source);
|
|
211
383
|
if (doc)
|
|
212
384
|
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
213
|
-
//
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
const values = node.members.map((m) => m.initializer.text).join('|');
|
|
217
|
-
lines.push(`type name=${name} values="${values}"${exp}`);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
// Numeric or mixed enum → type alias
|
|
385
|
+
// Auto-increment numeric enum: no explicit initializers → compact `values="A|B|C"` form.
|
|
386
|
+
const allBare = node.members.length > 0 && node.members.every((m) => !m.initializer);
|
|
387
|
+
if (allBare) {
|
|
221
388
|
const values = node.members.map((m) => m.name.getText(source)).join('|');
|
|
222
|
-
lines.push(`
|
|
389
|
+
lines.push(`enum name=${name} values="${values}"${constStr}${exp}`);
|
|
390
|
+
return lines;
|
|
391
|
+
}
|
|
392
|
+
// Otherwise emit `member` children to preserve explicit values.
|
|
393
|
+
lines.push(`enum name=${name}${constStr}${exp}`);
|
|
394
|
+
for (const m of node.members) {
|
|
395
|
+
const mname = m.name.getText(source);
|
|
396
|
+
if (!m.initializer) {
|
|
397
|
+
lines.push(` member name=${mname}`);
|
|
398
|
+
}
|
|
399
|
+
else if (ts.isStringLiteral(m.initializer)) {
|
|
400
|
+
const text = m.initializer.text;
|
|
401
|
+
if (text === '') {
|
|
402
|
+
// Empty string would be dropped by the codegen's "no value" guard
|
|
403
|
+
// (`if (rawVal === '') valueStr = ''`). Route via the expression
|
|
404
|
+
// form so it round-trips as the literal `""` instead of `Empty,`.
|
|
405
|
+
lines.push(` member name=${mname} value={{""}}`);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
lines.push(` member name=${mname} value="${escapeKernString(text)}"`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (ts.isNumericLiteral(m.initializer)) {
|
|
412
|
+
lines.push(` member name=${mname} value=${m.initializer.text}`);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
// Any other initializer (computed `1 << 2`, identifier reference, prefix
|
|
416
|
+
// unary like `-1`, etc.) — emit via {{expr}} so the codegen round-trips
|
|
417
|
+
// the raw expression instead of fabricating a string-quoted version.
|
|
418
|
+
const exprText = m.initializer.getText(source);
|
|
419
|
+
lines.push(` member name=${mname} value={{${exprText}}}`);
|
|
420
|
+
}
|
|
223
421
|
}
|
|
224
422
|
return lines;
|
|
225
423
|
}
|
|
@@ -227,19 +425,32 @@ function convertFunction(node, source) {
|
|
|
227
425
|
const lines = [];
|
|
228
426
|
const name = node.name?.getText(source) ?? 'anonymous';
|
|
229
427
|
const exp = isExported(node) ? ' export=true' : '';
|
|
428
|
+
const generics = genericsAttr(node.typeParameters, source);
|
|
230
429
|
const doc = getJSDoc(node, source);
|
|
231
430
|
const asyncStr = isAsync(node) ? ' async=true' : '';
|
|
232
431
|
const isGenerator = node.asteriskToken != null;
|
|
233
432
|
const generatorStr = isGenerator ? (isAsync(node) ? ' stream=true' : ' generator=true') : '';
|
|
234
433
|
if (doc)
|
|
235
434
|
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
236
|
-
|
|
435
|
+
// Slice 3c — prefer structured `param` child nodes when the signature is
|
|
436
|
+
// simple enough (no `?`/`...`/destructuring). Falls back to the legacy
|
|
437
|
+
// `params="..."` string for unsupported shapes.
|
|
438
|
+
const paramChildren = tryFormatParamChildren(node.parameters, source);
|
|
237
439
|
const returns = typeToString(node.type, source);
|
|
238
|
-
const paramsStr = params ? ` params="${params}"` : '';
|
|
239
440
|
const returnsStr = returns ? ` returns=${returns}` : '';
|
|
240
|
-
// For async generators, use stream=true instead of async=true + generator=true
|
|
241
441
|
const asyncFinal = isGenerator && isAsync(node) ? '' : asyncStr;
|
|
242
|
-
|
|
442
|
+
const paramsStr = paramChildren !== null
|
|
443
|
+
? ''
|
|
444
|
+
: (() => {
|
|
445
|
+
const params = formatParams(node.parameters, source);
|
|
446
|
+
return params ? ` params="${params}"` : '';
|
|
447
|
+
})();
|
|
448
|
+
lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${generatorStr}${generics}${exp}`);
|
|
449
|
+
if (paramChildren) {
|
|
450
|
+
for (const childLine of paramChildren) {
|
|
451
|
+
lines.push(` ${childLine}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
243
454
|
const body = getBodyText(node.body, source);
|
|
244
455
|
if (body) {
|
|
245
456
|
lines.push(' handler <<<');
|
|
@@ -250,10 +461,46 @@ function convertFunction(node, source) {
|
|
|
250
461
|
}
|
|
251
462
|
return lines;
|
|
252
463
|
}
|
|
464
|
+
/** Slice 2e — convert a TS overload group (N signatures + 1 implementation)
|
|
465
|
+
* to a single KERN `fn` node with `overload` children. The implementation is
|
|
466
|
+
* the declaration that has a body; if none has a body (rare — ambient module
|
|
467
|
+
* declarations), we treat the LAST as implementation for round-trip safety. */
|
|
468
|
+
function convertFunctionGroup(group, source, stats) {
|
|
469
|
+
// Find the implementation (the one with a body). If none, fall back to last.
|
|
470
|
+
const implIdx = group.findIndex((fn) => fn.body != null);
|
|
471
|
+
const impl = group[implIdx >= 0 ? implIdx : group.length - 1];
|
|
472
|
+
const overloads = group.filter((fn) => fn !== impl);
|
|
473
|
+
// Convert the implementation as a normal function (fn). Then append overload children.
|
|
474
|
+
stats.functions++;
|
|
475
|
+
const implLines = convertFunction(impl, source);
|
|
476
|
+
// Insert overload child lines BEFORE the handler block (which lives under
|
|
477
|
+
// the `fn name=...` header line). We slice at the index of the handler open.
|
|
478
|
+
const overloadLines = [];
|
|
479
|
+
for (const ov of overloads) {
|
|
480
|
+
const params = formatParams(ov.parameters, source);
|
|
481
|
+
const returns = typeToString(ov.type, source);
|
|
482
|
+
const paramsStr = params ? ` params="${params}"` : '';
|
|
483
|
+
const returnsStr = returns ? ` returns=${returns}` : '';
|
|
484
|
+
// Slice 2f — overload signatures can declare their own type parameters
|
|
485
|
+
// (e.g. `function id<T>(x: T): T;` paired with non-generic impl).
|
|
486
|
+
// Without this, the overload would round-trip to invalid TS where T is undefined.
|
|
487
|
+
const generics = genericsAttr(ov.typeParameters, source);
|
|
488
|
+
overloadLines.push(` overload${paramsStr}${returnsStr}${generics}`);
|
|
489
|
+
}
|
|
490
|
+
// Splice overload lines after the `fn name=...` (and its leading doc, if any)
|
|
491
|
+
// and before the ` handler <<<` block. We find the first line that starts
|
|
492
|
+
// with ` handler` (handler is the first child of fn) — overloads go before it.
|
|
493
|
+
const handlerIdx = implLines.findIndex((l) => l.startsWith(' handler'));
|
|
494
|
+
if (handlerIdx === -1) {
|
|
495
|
+
return [...implLines, ...overloadLines];
|
|
496
|
+
}
|
|
497
|
+
return [...implLines.slice(0, handlerIdx), ...overloadLines, ...implLines.slice(handlerIdx)];
|
|
498
|
+
}
|
|
253
499
|
function convertClass(node, source) {
|
|
254
500
|
const lines = [];
|
|
255
501
|
const name = node.name?.getText(source) ?? 'AnonymousClass';
|
|
256
502
|
const exp = isExported(node) ? ' export=true' : '';
|
|
503
|
+
const generics = genericsAttr(node.typeParameters, source);
|
|
257
504
|
const doc = getJSDoc(node, source);
|
|
258
505
|
// Check if it extends Error → error node
|
|
259
506
|
const extendsClause = node.heritageClauses?.find((h) => h.token === ts.SyntaxKind.ExtendsKeyword);
|
|
@@ -272,7 +519,7 @@ function convertClass(node, source) {
|
|
|
272
519
|
: '';
|
|
273
520
|
const extendsStr = extendsClause && !isError ? ` extends=${extendsClause.types.map((t) => t.getText(source)).join(',')}` : '';
|
|
274
521
|
const abstractStr = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ? ' abstract=true' : '';
|
|
275
|
-
lines.push(`class name=${name}${extendsStr}${implementsStr}${abstractStr}${exp}`);
|
|
522
|
+
lines.push(`class name=${name}${extendsStr}${implementsStr}${abstractStr}${generics}${exp}`);
|
|
276
523
|
for (const member of node.members) {
|
|
277
524
|
if (ts.isPropertyDeclaration(member)) {
|
|
278
525
|
const fieldName = member.name.getText(source);
|
|
@@ -280,16 +527,33 @@ function convertClass(node, source) {
|
|
|
280
527
|
const priv = isPrivate(member) ? ' private=true' : '';
|
|
281
528
|
const ro = isReadonly(member) ? ' readonly=true' : '';
|
|
282
529
|
const staticStr = isStatic(member) ? ' static=true' : '';
|
|
283
|
-
|
|
530
|
+
// Slice 3b: emit `value={{ <init> }}` (canonical), not `default={{ ... }}`.
|
|
531
|
+
// The `{{...}}` wrap stays — TS initializers can be arbitrary expressions
|
|
532
|
+
// (function calls, ternaries, etc.) that ValueIR may not parse, and the
|
|
533
|
+
// ExprObject form is the safe escape hatch through emitConstValue.
|
|
534
|
+
const valueAttr = member.initializer ? ` value={{ ${member.initializer.getText(source)} }}` : '';
|
|
284
535
|
const memberDoc = getJSDoc(member, source);
|
|
285
536
|
if (memberDoc)
|
|
286
537
|
lines.push(` doc text="${escapeKernString(memberDoc)}"`);
|
|
287
|
-
lines.push(` field name=${fieldName}${fieldType ? ` type=${fieldType}` : ''}${priv}${staticStr}${ro}${
|
|
538
|
+
lines.push(` field name=${fieldName}${fieldType ? ` type=${fieldType}` : ''}${priv}${staticStr}${ro}${valueAttr}`);
|
|
288
539
|
}
|
|
289
540
|
else if (ts.isConstructorDeclaration(member)) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
541
|
+
// Slice 3c — try structured `param` children for default-bearing
|
|
542
|
+
// signatures; fall back to legacy `params="..."` string otherwise.
|
|
543
|
+
const ctorParamChildren = tryFormatParamChildren(member.parameters, source);
|
|
544
|
+
const ctorParamsStr = ctorParamChildren !== null
|
|
545
|
+
? ''
|
|
546
|
+
: (() => {
|
|
547
|
+
const ctorParams = formatParams(member.parameters, source);
|
|
548
|
+
return ctorParams ? ` params="${ctorParams}"` : '';
|
|
549
|
+
})();
|
|
550
|
+
const generics = genericsAttr(member.typeParameters, source);
|
|
551
|
+
lines.push(` constructor${ctorParamsStr}${generics}`);
|
|
552
|
+
if (ctorParamChildren) {
|
|
553
|
+
for (const childLine of ctorParamChildren) {
|
|
554
|
+
lines.push(` ${childLine}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
293
557
|
const body = getBodyText(member.body, source);
|
|
294
558
|
if (body) {
|
|
295
559
|
lines.push(' handler <<<');
|
|
@@ -301,17 +565,28 @@ function convertClass(node, source) {
|
|
|
301
565
|
}
|
|
302
566
|
else if (ts.isMethodDeclaration(member)) {
|
|
303
567
|
const methodName = member.name.getText(source);
|
|
304
|
-
const
|
|
568
|
+
const paramChildren = tryFormatParamChildren(member.parameters, source);
|
|
305
569
|
const returns = typeToString(member.type, source);
|
|
570
|
+
const generics = genericsAttr(member.typeParameters, source);
|
|
306
571
|
const asyncStr = isAsync(member) ? ' async=true' : '';
|
|
307
572
|
const staticStr = isStatic(member) ? ' static=true' : '';
|
|
308
573
|
const privStr = isPrivate(member) ? ' private=true' : '';
|
|
309
|
-
const paramsStr =
|
|
574
|
+
const paramsStr = paramChildren !== null
|
|
575
|
+
? ''
|
|
576
|
+
: (() => {
|
|
577
|
+
const params = formatParams(member.parameters, source);
|
|
578
|
+
return params ? ` params="${params}"` : '';
|
|
579
|
+
})();
|
|
310
580
|
const returnsStr = returns ? ` returns=${returns}` : '';
|
|
311
581
|
const memberDoc = getJSDoc(member, source);
|
|
312
582
|
if (memberDoc)
|
|
313
583
|
lines.push(` doc text="${escapeKernString(memberDoc)}"`);
|
|
314
|
-
lines.push(` method name=${methodName}${paramsStr}${returnsStr}${asyncStr}${staticStr}${privStr}`);
|
|
584
|
+
lines.push(` method name=${methodName}${paramsStr}${returnsStr}${asyncStr}${staticStr}${privStr}${generics}`);
|
|
585
|
+
if (paramChildren) {
|
|
586
|
+
for (const childLine of paramChildren) {
|
|
587
|
+
lines.push(` ${childLine}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
315
590
|
const body = getBodyText(member.body, source);
|
|
316
591
|
if (body) {
|
|
317
592
|
lines.push(' handler <<<');
|
|
@@ -338,11 +613,21 @@ function convertClass(node, source) {
|
|
|
338
613
|
}
|
|
339
614
|
else if (ts.isSetAccessorDeclaration(member)) {
|
|
340
615
|
const sname = member.name.getText(source);
|
|
341
|
-
const
|
|
342
|
-
const paramsStr =
|
|
616
|
+
const setterParamChildren = tryFormatParamChildren(member.parameters, source);
|
|
617
|
+
const paramsStr = setterParamChildren !== null
|
|
618
|
+
? ''
|
|
619
|
+
: (() => {
|
|
620
|
+
const params = formatParams(member.parameters, source);
|
|
621
|
+
return params ? ` params="${params}"` : '';
|
|
622
|
+
})();
|
|
343
623
|
const privStr = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword) ? ' private=true' : '';
|
|
344
624
|
const staticStr = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ? ' static=true' : '';
|
|
345
625
|
lines.push(` setter name=${sname}${paramsStr}${privStr}${staticStr}`);
|
|
626
|
+
if (setterParamChildren) {
|
|
627
|
+
for (const childLine of setterParamChildren) {
|
|
628
|
+
lines.push(` ${childLine}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
346
631
|
const body = getBodyText(member.body, source);
|
|
347
632
|
if (body) {
|
|
348
633
|
lines.push(' handler <<<');
|
|
@@ -386,11 +671,223 @@ function convertErrorClass(node, source, name, baseClass, exp, lines) {
|
|
|
386
671
|
}
|
|
387
672
|
return lines;
|
|
388
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Slice 3d — try to format a destructured `const`/`let` declaration as a
|
|
676
|
+
* structured `destructure` IR node with `binding` (object) or `element`
|
|
677
|
+
* (array) children. Returns `null` for non-destructured patterns; returns
|
|
678
|
+
* a single fallback line carrying the raw statement in `expr={{...}}` for
|
|
679
|
+
* patterns we can't structure (rest `...`, defaults `=v`, nested `{a:{b}}`,
|
|
680
|
+
* computed/string keys).
|
|
681
|
+
*/
|
|
682
|
+
function tryFormatDestructure(decl, kind, exported, source, fullStmtText) {
|
|
683
|
+
if (!decl.initializer)
|
|
684
|
+
return null;
|
|
685
|
+
const isObj = ts.isObjectBindingPattern(decl.name);
|
|
686
|
+
const isArr = ts.isArrayBindingPattern(decl.name);
|
|
687
|
+
if (!isObj && !isArr)
|
|
688
|
+
return null;
|
|
689
|
+
const fallback = () => [`destructure expr={{ ${fullStmtText} }}`];
|
|
690
|
+
const childLines = [];
|
|
691
|
+
if (isObj) {
|
|
692
|
+
for (const el of decl.name.elements) {
|
|
693
|
+
if (el.dotDotDotToken)
|
|
694
|
+
return fallback();
|
|
695
|
+
if (el.initializer)
|
|
696
|
+
return fallback();
|
|
697
|
+
if (!ts.isIdentifier(el.name))
|
|
698
|
+
return fallback();
|
|
699
|
+
const localName = el.name.getText(source);
|
|
700
|
+
let line = `binding name=${localName}`;
|
|
701
|
+
if (el.propertyName) {
|
|
702
|
+
if (!ts.isIdentifier(el.propertyName))
|
|
703
|
+
return fallback();
|
|
704
|
+
line += ` key=${el.propertyName.getText(source)}`;
|
|
705
|
+
}
|
|
706
|
+
childLines.push(line);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
let idx = 0;
|
|
711
|
+
for (const el of decl.name.elements) {
|
|
712
|
+
if (ts.isOmittedExpression(el)) {
|
|
713
|
+
idx++;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (el.dotDotDotToken)
|
|
717
|
+
return fallback();
|
|
718
|
+
if (el.initializer)
|
|
719
|
+
return fallback();
|
|
720
|
+
if (!ts.isIdentifier(el.name))
|
|
721
|
+
return fallback();
|
|
722
|
+
childLines.push(`element name=${el.name.getText(source)} index=${idx}`);
|
|
723
|
+
idx++;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (childLines.length === 0)
|
|
727
|
+
return fallback();
|
|
728
|
+
const sourceText = decl.initializer.getText(source);
|
|
729
|
+
const isSimpleIdent = /^[A-Za-z_$][\w$]*$/.test(sourceText);
|
|
730
|
+
const sourceAttr = isSimpleIdent ? ` source=${sourceText}` : ` source={{ ${sourceText} }}`;
|
|
731
|
+
const type = typeToString(decl.type, source);
|
|
732
|
+
const typeAttr = type ? ` type=${type}` : '';
|
|
733
|
+
const expAttr = exported ? ' export=true' : '';
|
|
734
|
+
const lines = [`destructure kind=${kind}${typeAttr}${sourceAttr}${expAttr}`];
|
|
735
|
+
for (const line of childLines)
|
|
736
|
+
lines.push(` ${line}`);
|
|
737
|
+
return lines;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Slice 3e — pick the right KERN attribute form for a TS expression node
|
|
741
|
+
* used as a key/value in a Map/Set literal entry.
|
|
742
|
+
*
|
|
743
|
+
* - String literals → `prop="foo"` so the quoted source flows through
|
|
744
|
+
* __quotedProps and codegen JSON-stringifies.
|
|
745
|
+
* - Primitive literals (numeric, true, false, null) → `prop=42` bare so
|
|
746
|
+
* they go through ValueIR canonicalisation per slice 3a-c precedent.
|
|
747
|
+
* - Anything else → `prop={{ raw-ts }}` ExprObject escape hatch.
|
|
748
|
+
*/
|
|
749
|
+
function formatLitEntryAttr(propName, expr, source) {
|
|
750
|
+
const text = expr.getText(source);
|
|
751
|
+
if (ts.isStringLiteral(expr))
|
|
752
|
+
return `${propName}=${text}`;
|
|
753
|
+
if (ts.isNumericLiteral(expr) ||
|
|
754
|
+
expr.kind === ts.SyntaxKind.TrueKeyword ||
|
|
755
|
+
expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
756
|
+
expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
757
|
+
return `${propName}=${text}`;
|
|
758
|
+
}
|
|
759
|
+
return `${propName}={{ ${text} }}`;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Slice 3e — try to format `const x = new Map([['k', v], ...])` as a
|
|
763
|
+
* structured `mapLit` IR node with `mapEntry` children. Returns null when
|
|
764
|
+
* the initializer is not a Map constructor call, or when the array argument
|
|
765
|
+
* contains non-canonical entries (spread, computed keys, non-tuple entries).
|
|
766
|
+
*/
|
|
767
|
+
function tryFormatMapLit(decl, kind, exported, source) {
|
|
768
|
+
if (!decl.initializer || !ts.isNewExpression(decl.initializer))
|
|
769
|
+
return null;
|
|
770
|
+
if (decl.initializer.expression.getText(source) !== 'Map')
|
|
771
|
+
return null;
|
|
772
|
+
if (!ts.isIdentifier(decl.name))
|
|
773
|
+
return null;
|
|
774
|
+
// Empty `new Map()` or unusual form — pass through.
|
|
775
|
+
const args = decl.initializer.arguments;
|
|
776
|
+
if (!args || args.length === 0)
|
|
777
|
+
return null;
|
|
778
|
+
const arg = args[0];
|
|
779
|
+
if (!ts.isArrayLiteralExpression(arg))
|
|
780
|
+
return null;
|
|
781
|
+
const entryLines = [];
|
|
782
|
+
for (const el of arg.elements) {
|
|
783
|
+
if (!ts.isArrayLiteralExpression(el))
|
|
784
|
+
return null; // non-tuple entry
|
|
785
|
+
if (el.elements.length !== 2)
|
|
786
|
+
return null; // not [k, v]
|
|
787
|
+
if (el.elements.some((e) => ts.isSpreadElement(e)))
|
|
788
|
+
return null;
|
|
789
|
+
const keyAttr = formatLitEntryAttr('key', el.elements[0], source);
|
|
790
|
+
const valAttr = formatLitEntryAttr('value', el.elements[1], source);
|
|
791
|
+
entryLines.push(`mapEntry ${keyAttr} ${valAttr}`);
|
|
792
|
+
}
|
|
793
|
+
if (entryLines.length === 0)
|
|
794
|
+
return null; // empty array — pass through
|
|
795
|
+
const name = decl.name.getText(source);
|
|
796
|
+
const type = typeToString(decl.type, source);
|
|
797
|
+
const typeAttr = type ? ` type=${type}` : '';
|
|
798
|
+
const expAttr = exported ? ' export=true' : '';
|
|
799
|
+
const kindAttr = kind === 'let' ? ' kind=let' : '';
|
|
800
|
+
const lines = [`mapLit name=${name}${typeAttr}${kindAttr}${expAttr}`];
|
|
801
|
+
for (const l of entryLines)
|
|
802
|
+
lines.push(` ${l}`);
|
|
803
|
+
return lines;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Slice 3e — try to format `const x = new Set([v1, v2, ...])` as a
|
|
807
|
+
* structured `setLit` IR node with `setItem` children. Returns null when
|
|
808
|
+
* the initializer is not a Set constructor call, or when the array argument
|
|
809
|
+
* contains non-canonical members (spread).
|
|
810
|
+
*/
|
|
811
|
+
function tryFormatSetLit(decl, kind, exported, source) {
|
|
812
|
+
if (!decl.initializer || !ts.isNewExpression(decl.initializer))
|
|
813
|
+
return null;
|
|
814
|
+
if (decl.initializer.expression.getText(source) !== 'Set')
|
|
815
|
+
return null;
|
|
816
|
+
if (!ts.isIdentifier(decl.name))
|
|
817
|
+
return null;
|
|
818
|
+
const args = decl.initializer.arguments;
|
|
819
|
+
if (!args || args.length === 0)
|
|
820
|
+
return null;
|
|
821
|
+
const arg = args[0];
|
|
822
|
+
if (!ts.isArrayLiteralExpression(arg))
|
|
823
|
+
return null;
|
|
824
|
+
const itemLines = [];
|
|
825
|
+
for (const el of arg.elements) {
|
|
826
|
+
if (ts.isSpreadElement(el))
|
|
827
|
+
return null;
|
|
828
|
+
itemLines.push(`setItem ${formatLitEntryAttr('value', el, source)}`);
|
|
829
|
+
}
|
|
830
|
+
if (itemLines.length === 0)
|
|
831
|
+
return null;
|
|
832
|
+
const name = decl.name.getText(source);
|
|
833
|
+
const type = typeToString(decl.type, source);
|
|
834
|
+
const typeAttr = type ? ` type=${type}` : '';
|
|
835
|
+
const expAttr = exported ? ' export=true' : '';
|
|
836
|
+
const kindAttr = kind === 'let' ? ' kind=let' : '';
|
|
837
|
+
const lines = [`setLit name=${name}${typeAttr}${kindAttr}${expAttr}`];
|
|
838
|
+
for (const l of itemLines)
|
|
839
|
+
lines.push(` ${l}`);
|
|
840
|
+
return lines;
|
|
841
|
+
}
|
|
389
842
|
function convertVariableStatement(node, source) {
|
|
390
843
|
const lines = [];
|
|
391
844
|
const exp = isExported(node) ? ' export=true' : '';
|
|
392
845
|
const doc = getJSDoc(node, source);
|
|
846
|
+
// Slice 3d — derive const|let|var from the declaration list flags.
|
|
847
|
+
const declKind = (node.declarationList.flags & ts.NodeFlags.Const) !== 0
|
|
848
|
+
? 'const'
|
|
849
|
+
: (node.declarationList.flags & ts.NodeFlags.Let) !== 0
|
|
850
|
+
? 'let'
|
|
851
|
+
: 'const';
|
|
393
852
|
for (const decl of node.declarationList.declarations) {
|
|
853
|
+
// Slice 3d — destructured LHS handled BEFORE the simple-value branch
|
|
854
|
+
// because `decl.name` for `const {a,b}=obj` is a binding pattern, not
|
|
855
|
+
// an identifier. Falls through to the legacy paths only when LHS is a
|
|
856
|
+
// plain identifier.
|
|
857
|
+
if (ts.isObjectBindingPattern(decl.name) || ts.isArrayBindingPattern(decl.name)) {
|
|
858
|
+
if (doc)
|
|
859
|
+
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
860
|
+
// Reconstruct the full statement text for the expr= fallback path.
|
|
861
|
+
// Prefer node.getText(source) so we capture `export const {a}=obj;` shape.
|
|
862
|
+
const fullStmtText = node.getText(source).trim().replace(/;$/, '');
|
|
863
|
+
const result = tryFormatDestructure(decl, declKind, isExported(node), source, fullStmtText);
|
|
864
|
+
if (result) {
|
|
865
|
+
for (const l of result)
|
|
866
|
+
lines.push(l);
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
// Slice 3e — Map/Set literal detection. Falls through to legacy const
|
|
871
|
+
// when the constructor argument shape isn't structurable (spread,
|
|
872
|
+
// computed keys, non-tuple entries, etc.).
|
|
873
|
+
{
|
|
874
|
+
const mapLines = tryFormatMapLit(decl, declKind, isExported(node), source);
|
|
875
|
+
if (mapLines) {
|
|
876
|
+
if (doc)
|
|
877
|
+
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
878
|
+
for (const l of mapLines)
|
|
879
|
+
lines.push(l);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const setLines = tryFormatSetLit(decl, declKind, isExported(node), source);
|
|
883
|
+
if (setLines) {
|
|
884
|
+
if (doc)
|
|
885
|
+
lines.push(`doc text="${escapeKernString(doc)}"`);
|
|
886
|
+
for (const l of setLines)
|
|
887
|
+
lines.push(l);
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
394
891
|
const name = decl.name.getText(source);
|
|
395
892
|
const type = typeToString(decl.type, source);
|
|
396
893
|
const typeStr = type ? ` type=${type}` : '';
|
|
@@ -419,14 +916,27 @@ function convertVariableStatement(node, source) {
|
|
|
419
916
|
// Arrow function or function expression → fn
|
|
420
917
|
const func = decl.initializer;
|
|
421
918
|
const asyncStr = isAsync(func) ? ' async=true' : '';
|
|
422
|
-
|
|
919
|
+
// Slice 3c — try structured `param` children for default-bearing
|
|
920
|
+
// signatures; fall back to legacy `params="..."` string otherwise.
|
|
921
|
+
const paramChildren = tryFormatParamChildren(func.parameters, source);
|
|
423
922
|
const returns = typeToString(func.type, source);
|
|
424
|
-
const
|
|
923
|
+
const generics = genericsAttr(func.typeParameters, source);
|
|
924
|
+
const paramsStr = paramChildren !== null
|
|
925
|
+
? ''
|
|
926
|
+
: (() => {
|
|
927
|
+
const params = formatParams(func.parameters, source);
|
|
928
|
+
return params ? ` params="${params}"` : '';
|
|
929
|
+
})();
|
|
425
930
|
const returnsStr = returns ? ` returns=${returns}` : '';
|
|
426
931
|
const isGen = ts.isFunctionExpression(func) && func.asteriskToken != null;
|
|
427
932
|
const genStr = isGen ? (isAsync(func) ? ' stream=true' : ' generator=true') : '';
|
|
428
933
|
const asyncFinal = isGen && isAsync(func) ? '' : asyncStr;
|
|
429
|
-
lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${genStr}${exp}`);
|
|
934
|
+
lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${genStr}${generics}${exp}`);
|
|
935
|
+
if (paramChildren) {
|
|
936
|
+
for (const childLine of paramChildren) {
|
|
937
|
+
lines.push(` ${childLine}`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
430
940
|
const body = ts.isArrowFunction(func)
|
|
431
941
|
? getBodyText(func.body, source)
|
|
432
942
|
: getBodyText(func.body, source);
|
|
@@ -1084,7 +1594,37 @@ export function importTypeScript(tsSource, fileName = 'input.ts') {
|
|
|
1084
1594
|
enums: 0,
|
|
1085
1595
|
components: 0,
|
|
1086
1596
|
};
|
|
1087
|
-
|
|
1597
|
+
// Slice 2e — TS overloaded functions are a sequence of FunctionDeclaration
|
|
1598
|
+
// nodes sharing a name, where the LAST one has a body and the earlier ones
|
|
1599
|
+
// are signature-only. Group them so the importer emits a single `fn` with
|
|
1600
|
+
// `overload` children rather than N separate fn nodes.
|
|
1601
|
+
const statements = sourceFile.statements;
|
|
1602
|
+
for (let i = 0; i < statements.length; i++) {
|
|
1603
|
+
const statement = statements[i];
|
|
1604
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) {
|
|
1605
|
+
const fnName = statement.name.getText(sourceFile);
|
|
1606
|
+
// Look ahead to collect consecutive same-named function declarations.
|
|
1607
|
+
const group = [statement];
|
|
1608
|
+
while (i + 1 < statements.length) {
|
|
1609
|
+
const next = statements[i + 1];
|
|
1610
|
+
if (ts.isFunctionDeclaration(next) && next.name && next.name.getText(sourceFile) === fnName) {
|
|
1611
|
+
group.push(next);
|
|
1612
|
+
i++;
|
|
1613
|
+
}
|
|
1614
|
+
else {
|
|
1615
|
+
break;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if (group.length > 1) {
|
|
1619
|
+
const converted = convertFunctionGroup(group, sourceFile, stats);
|
|
1620
|
+
if (converted.length > 0) {
|
|
1621
|
+
kernLines.push(...converted);
|
|
1622
|
+
kernLines.push('');
|
|
1623
|
+
}
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
// Single declaration — fall through to ordinary path.
|
|
1627
|
+
}
|
|
1088
1628
|
const converted = convertStatement(statement, sourceFile, unmapped, stats);
|
|
1089
1629
|
if (converted.length > 0) {
|
|
1090
1630
|
kernLines.push(...converted);
|