@kernlang/core 3.1.3 → 3.1.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/dist/codegen/emitters.d.ts +25 -0
- package/dist/codegen/emitters.js +124 -0
- package/dist/codegen/emitters.js.map +1 -0
- package/dist/codegen/helpers.d.ts +22 -0
- package/dist/codegen/helpers.js +159 -0
- package/dist/codegen/helpers.js.map +1 -0
- package/dist/codegen-core.d.ts +4 -35
- package/dist/codegen-core.js +87 -277
- package/dist/codegen-core.js.map +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +15 -17
- package/dist/parser.js +209 -55
- package/dist/parser.js.map +1 -1
- package/dist/runtime.d.ts +57 -0
- package/dist/runtime.js +121 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +39 -0
- package/dist/schema.js +481 -0
- package/dist/schema.js.map +1 -0
- package/dist/spec.d.ts +3 -2
- package/dist/spec.js +28 -17
- package/dist/spec.js.map +1 -1
- package/dist/styles-tailwind.js +29 -0
- package/dist/styles-tailwind.js.map +1 -1
- package/dist/template-engine.d.ts +3 -2
- package/dist/template-engine.js +15 -13
- package/dist/template-engine.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/package.json +21 -1
package/dist/codegen-core.js
CHANGED
|
@@ -8,135 +8,49 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { isTemplateNode, expandTemplateNode } from './template-engine.js';
|
|
10
10
|
import { KernCodegenError } from './errors.js';
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
return v;
|
|
26
|
-
}
|
|
27
|
-
/** Escape a string for safe interpolation into a single-quoted JS string literal. */
|
|
28
|
-
export function emitStringLiteral(value) {
|
|
29
|
-
const escaped = value
|
|
30
|
-
.replace(/\\/g, '\\\\')
|
|
31
|
-
.replace(/'/g, "\\'")
|
|
32
|
-
.replace(/`/g, '\\`')
|
|
33
|
-
.replace(/\$/g, '\\$')
|
|
34
|
-
.replace(/\n/g, '\\n')
|
|
35
|
-
.replace(/\r/g, '\\r');
|
|
36
|
-
return `'${escaped}'`;
|
|
37
|
-
}
|
|
38
|
-
/** Validate and emit a safe filesystem path for generated code. */
|
|
39
|
-
export function emitPath(value, node) {
|
|
40
|
-
if (!SAFE_PATH_RE.test(value)) {
|
|
41
|
-
throw new KernCodegenError(`Invalid path: '${value.slice(0, 80)}' — contains unsafe characters`, node);
|
|
42
|
-
}
|
|
43
|
-
if (value.includes('..')) {
|
|
44
|
-
throw new KernCodegenError(`Invalid path: '${value.slice(0, 80)}' — path traversal (..) not allowed`, node);
|
|
45
|
-
}
|
|
46
|
-
return emitStringLiteral(value);
|
|
47
|
-
}
|
|
48
|
-
/** Escape a value for interpolation into a template literal in generated code. */
|
|
49
|
-
export function emitTemplateSafe(value) {
|
|
50
|
-
return value
|
|
51
|
-
.replace(/\\/g, '\\\\')
|
|
52
|
-
.replace(/`/g, '\\`')
|
|
53
|
-
.replace(/\$\{/g, '\\${');
|
|
54
|
-
}
|
|
11
|
+
import { defaultRuntime } from './runtime.js';
|
|
12
|
+
// Re-export emitters and helpers from extracted modules for backward compatibility.
|
|
13
|
+
// All existing `import { emitIdentifier } from './codegen-core.js'` paths continue to work.
|
|
14
|
+
export { emitIdentifier, emitStringLiteral, emitPath, emitTemplateSafe, emitTypeAnnotation, emitImportSpecifier } from './codegen/emitters.js';
|
|
15
|
+
export { getProps, getChildren, getFirstChild, getStyles, getPseudoStyles, getThemeRefs, dedent, cssPropertyName, handlerCode, exportPrefix, capitalize, parseParamList, emitReasonAnnotations, emitLowConfidenceTodo } from './codegen/helpers.js';
|
|
16
|
+
// Import for local use within this file
|
|
17
|
+
import { emitIdentifier, emitStringLiteral, emitPath, emitTemplateSafe, emitTypeAnnotation, emitImportSpecifier } from './codegen/emitters.js';
|
|
18
|
+
import { getProps, getChildren, getFirstChild, dedent, handlerCode, exportPrefix, capitalize, parseParamList, emitReasonAnnotations, emitLowConfidenceTodo } from './codegen/helpers.js';
|
|
19
|
+
// ── Safe Emitters & Helpers ───────────────────────────────────────────────
|
|
20
|
+
// Implementations extracted to codegen/emitters.ts and codegen/helpers.ts.
|
|
21
|
+
// Re-exported above for backward compatibility.
|
|
22
|
+
// (emitter implementations in codegen/emitters.ts)
|
|
23
|
+
// (emitTypeAnnotation, emitImportSpecifier implementations in codegen/emitters.ts)
|
|
55
24
|
// ── Evolved Generators (v4) ─────────────────────────────────────────────
|
|
56
25
|
// Populated at startup by evolved-node-loader. Checked in generateCoreNode
|
|
57
26
|
// before the default case, allowing graduated nodes to produce output.
|
|
58
|
-
|
|
59
|
-
const _evolvedTargetGenerators = new Map();
|
|
27
|
+
// Evolved generators now live in defaultRuntime. These functions delegate for backward compatibility.
|
|
60
28
|
/** Register an evolved generator (called at startup). */
|
|
61
29
|
export function registerEvolvedGenerator(keyword, fn) {
|
|
62
|
-
|
|
30
|
+
defaultRuntime.registerEvolvedGenerator(keyword, fn);
|
|
63
31
|
}
|
|
64
32
|
/** Register a target-specific evolved generator (called at startup). */
|
|
65
33
|
export function registerEvolvedTargetGenerator(keyword, target, fn) {
|
|
66
|
-
|
|
67
|
-
_evolvedTargetGenerators.set(keyword, new Map());
|
|
68
|
-
}
|
|
69
|
-
_evolvedTargetGenerators.get(keyword).set(target, fn);
|
|
34
|
+
defaultRuntime.registerEvolvedTargetGenerator(keyword, target, fn);
|
|
70
35
|
}
|
|
71
36
|
/** Unregister an evolved generator (for rollback/testing). */
|
|
72
37
|
export function unregisterEvolvedGenerator(keyword) {
|
|
73
|
-
|
|
74
|
-
_evolvedTargetGenerators.delete(keyword);
|
|
38
|
+
defaultRuntime.unregisterEvolvedGenerator(keyword);
|
|
75
39
|
}
|
|
76
40
|
/** Clear all evolved generators (for test isolation). */
|
|
77
41
|
export function clearEvolvedGenerators() {
|
|
78
|
-
|
|
79
|
-
_evolvedTargetGenerators.clear();
|
|
42
|
+
defaultRuntime.clearEvolvedGenerators();
|
|
80
43
|
}
|
|
81
44
|
/** Check if an evolved generator exists for a type. */
|
|
82
45
|
export function hasEvolvedGenerator(type) {
|
|
83
|
-
return
|
|
46
|
+
return defaultRuntime.hasEvolvedGenerator(type);
|
|
84
47
|
}
|
|
85
48
|
// ── Shared IR node helpers ───────────────────────────────────────────────
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
export function getProps(node) {
|
|
89
|
-
return node.props || {};
|
|
90
|
-
}
|
|
91
|
-
/** Get children, optionally filtered by type. */
|
|
92
|
-
export function getChildren(node, type) {
|
|
93
|
-
const c = node.children || [];
|
|
94
|
-
return type ? c.filter(n => n.type === type) : c;
|
|
95
|
-
}
|
|
96
|
-
/** Get first child of a given type. */
|
|
97
|
-
export function getFirstChild(node, type) {
|
|
98
|
-
return getChildren(node, type)[0];
|
|
99
|
-
}
|
|
100
|
-
/** Extract styles from node props. */
|
|
101
|
-
export function getStyles(node) {
|
|
102
|
-
return getProps(node).styles || {};
|
|
103
|
-
}
|
|
104
|
-
/** Extract pseudo-styles from node props. */
|
|
105
|
-
export function getPseudoStyles(node) {
|
|
106
|
-
return getProps(node).pseudoStyles || {};
|
|
107
|
-
}
|
|
108
|
-
/** Extract theme refs from node props. */
|
|
109
|
-
export function getThemeRefs(node) {
|
|
110
|
-
return getProps(node).themeRefs || [];
|
|
111
|
-
}
|
|
112
|
-
/** Strip common leading whitespace from multiline handler code. */
|
|
113
|
-
export function dedent(code) {
|
|
114
|
-
const lines = code.split('\n');
|
|
115
|
-
const nonEmpty = lines.filter(l => l.trim().length > 0);
|
|
116
|
-
if (nonEmpty.length === 0)
|
|
117
|
-
return code;
|
|
118
|
-
const min = Math.min(...nonEmpty.map(l => l.match(/^(\s*)/)?.[1].length ?? 0));
|
|
119
|
-
return lines.map(l => l.slice(min)).join('\n');
|
|
120
|
-
}
|
|
121
|
-
/** Convert camelCase to kebab-case for CSS property names. */
|
|
122
|
-
export function cssPropertyName(camel) {
|
|
123
|
-
return camel.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
124
|
-
}
|
|
125
|
-
/** Extract handler code from a node (finds handler child, dedents). */
|
|
126
|
-
export function handlerCode(node) {
|
|
127
|
-
const handler = getFirstChild(node, 'handler');
|
|
128
|
-
if (!handler)
|
|
129
|
-
return '';
|
|
130
|
-
const raw = getProps(handler).code || '';
|
|
131
|
-
return dedent(raw);
|
|
132
|
-
}
|
|
133
|
-
// Internal aliases for backward compat within this file
|
|
49
|
+
// Implementations extracted to codegen/helpers.ts. Re-exported above.
|
|
50
|
+
// Internal aliases for local use within this file
|
|
134
51
|
const p = getProps;
|
|
135
52
|
const kids = getChildren;
|
|
136
53
|
const firstChild = getFirstChild;
|
|
137
|
-
export function exportPrefix(node) {
|
|
138
|
-
return p(node).export === 'false' ? '' : 'export ';
|
|
139
|
-
}
|
|
140
54
|
// ── Type Alias ───────────────────────────────────────────────────────────
|
|
141
55
|
// type name=PlanState values="draft|approved|running|paused|completed|failed|cancelled"
|
|
142
56
|
// → export type PlanState = 'draft' | 'approved' | 'running' | ...;
|
|
@@ -149,7 +63,7 @@ export function generateType(node) {
|
|
|
149
63
|
return [`${exp}type ${name} = ${members};`];
|
|
150
64
|
}
|
|
151
65
|
if (alias) {
|
|
152
|
-
return [`${exp}type ${name} = ${alias};`];
|
|
66
|
+
return [`${exp}type ${name} = ${emitTypeAnnotation(alias, 'unknown', node)};`];
|
|
153
67
|
}
|
|
154
68
|
return [`${exp}type ${name} = unknown;`];
|
|
155
69
|
}
|
|
@@ -162,7 +76,7 @@ export function generateType(node) {
|
|
|
162
76
|
export function generateInterface(node) {
|
|
163
77
|
const props = p(node);
|
|
164
78
|
const name = emitIdentifier(props.name, 'UnknownInterface', node);
|
|
165
|
-
const ext = props.extends ? ` extends ${props.extends}` : '';
|
|
79
|
+
const ext = props.extends ? ` extends ${emitTypeAnnotation(props.extends, 'unknown', node)}` : '';
|
|
166
80
|
const exp = exportPrefix(node);
|
|
167
81
|
const lines = [];
|
|
168
82
|
lines.push(`${exp}interface ${name}${ext} {`);
|
|
@@ -170,7 +84,7 @@ export function generateInterface(node) {
|
|
|
170
84
|
const fp = p(field);
|
|
171
85
|
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
172
86
|
const opt = fp.optional === 'true' || fp.optional === true ? '?' : '';
|
|
173
|
-
lines.push(` ${fieldName}${opt}: ${fp.type};`);
|
|
87
|
+
lines.push(` ${fieldName}${opt}: ${emitTypeAnnotation(fp.type, 'unknown', field)};`);
|
|
174
88
|
}
|
|
175
89
|
lines.push('}');
|
|
176
90
|
return lines;
|
|
@@ -203,7 +117,7 @@ export function generateUnion(node) {
|
|
|
203
117
|
for (const field of fields) {
|
|
204
118
|
const fp = p(field);
|
|
205
119
|
const opt = fp.optional === 'true' || fp.optional === true ? '?' : '';
|
|
206
|
-
fieldParts.push(`${fp.name}${opt}: ${fp.type}`);
|
|
120
|
+
fieldParts.push(`${emitIdentifier(fp.name, 'field', field)}${opt}: ${emitTypeAnnotation(fp.type, 'unknown', field)}`);
|
|
207
121
|
}
|
|
208
122
|
const semi = i === variants.length - 1 ? ';' : '';
|
|
209
123
|
lines.push(` | { ${fieldParts.join('; ')} }${semi}`);
|
|
@@ -230,17 +144,19 @@ export function generateService(node) {
|
|
|
230
144
|
const impl = props.implements;
|
|
231
145
|
const exp = exportPrefix(node);
|
|
232
146
|
const lines = [];
|
|
233
|
-
const implClause = impl ? ` implements ${impl}` : '';
|
|
147
|
+
const implClause = impl ? ` implements ${emitTypeAnnotation(impl, 'unknown', node)}` : '';
|
|
234
148
|
lines.push(`${exp}class ${name}${implClause} {`);
|
|
235
149
|
// Fields
|
|
236
150
|
for (const field of kids(node, 'field')) {
|
|
237
151
|
const fp = p(field);
|
|
152
|
+
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
238
153
|
const vis = fp.private === 'true' || fp.private === true ? 'private ' : '';
|
|
239
154
|
const readonly = fp.readonly === 'true' || fp.readonly === true ? 'readonly ' : '';
|
|
240
|
-
const typeAnnotation = fp.type ? `: ${fp.type}` : '';
|
|
155
|
+
const typeAnnotation = fp.type ? `: ${emitTypeAnnotation(fp.type, 'unknown', field)}` : '';
|
|
241
156
|
const defaultVal = fp.default;
|
|
157
|
+
// default values are by-design raw code (escape hatch) — documented, not sanitized
|
|
242
158
|
const init = defaultVal !== undefined ? ` = ${defaultVal}` : '';
|
|
243
|
-
lines.push(` ${vis}${readonly}${
|
|
159
|
+
lines.push(` ${vis}${readonly}${fieldName}${typeAnnotation}${init};`);
|
|
244
160
|
}
|
|
245
161
|
// Constructor (if any constructor child exists)
|
|
246
162
|
const ctorNode = firstChild(node, 'constructor');
|
|
@@ -272,8 +188,8 @@ export function generateService(node) {
|
|
|
272
188
|
const mcode = handlerCode(method);
|
|
273
189
|
// stream=true → AsyncGenerator return type
|
|
274
190
|
const mreturns = isStream
|
|
275
|
-
? `: AsyncGenerator<${mp.returns
|
|
276
|
-
: mp.returns ? `: ${mp.returns}` : '';
|
|
191
|
+
? `: AsyncGenerator<${emitTypeAnnotation(mp.returns, 'unknown', method)}>`
|
|
192
|
+
: mp.returns ? `: ${emitTypeAnnotation(mp.returns, 'unknown', method)}` : '';
|
|
277
193
|
lines.push('');
|
|
278
194
|
lines.push(` ${vis}${staticKw}${asyncKw}${star}${mname}(${mparams})${mreturns} {`);
|
|
279
195
|
if (mcode) {
|
|
@@ -313,7 +229,7 @@ export function generateFunction(node) {
|
|
|
313
229
|
const paramList = params ? parseParamList(params) : '';
|
|
314
230
|
// stream=true → async generator function
|
|
315
231
|
if (isStream) {
|
|
316
|
-
const yieldType = returns
|
|
232
|
+
const yieldType = emitTypeAnnotation(returns, 'unknown', node);
|
|
317
233
|
const retClause = `: AsyncGenerator<${yieldType}>`;
|
|
318
234
|
const code = handlerCode(node);
|
|
319
235
|
lines.push(`${exp}async function* ${name}(${paramList})${retClause} {`);
|
|
@@ -325,7 +241,7 @@ export function generateFunction(node) {
|
|
|
325
241
|
lines.push('}');
|
|
326
242
|
return lines;
|
|
327
243
|
}
|
|
328
|
-
const retClause = returns ? `: ${returns}` : '';
|
|
244
|
+
const retClause = returns ? `: ${emitTypeAnnotation(returns, 'unknown', node)}` : '';
|
|
329
245
|
const asyncKw = isAsync ? 'async ' : '';
|
|
330
246
|
const code = handlerCode(node);
|
|
331
247
|
// Gap 3: signal + cleanup support for async functions
|
|
@@ -390,11 +306,13 @@ export function generateError(node) {
|
|
|
390
306
|
const opt = fp.optional === 'true' || fp.optional === true ? '?' : '';
|
|
391
307
|
const isMessage = fp.name === 'message';
|
|
392
308
|
// 'message' param is not readonly — it's passed to super
|
|
309
|
+
const fName = emitIdentifier(fp.name, 'field', field);
|
|
310
|
+
const fType = emitTypeAnnotation(fp.type, 'unknown', field);
|
|
393
311
|
if (isMessage) {
|
|
394
|
-
lines.push(` ${
|
|
312
|
+
lines.push(` ${fName}${opt}: ${fType},`);
|
|
395
313
|
}
|
|
396
314
|
else {
|
|
397
|
-
lines.push(` public readonly ${
|
|
315
|
+
lines.push(` public readonly ${fName}${opt}: ${fType},`);
|
|
398
316
|
}
|
|
399
317
|
}
|
|
400
318
|
lines.push(` ) {`);
|
|
@@ -583,7 +501,7 @@ export function generateConfig(node) {
|
|
|
583
501
|
const fp = p(field);
|
|
584
502
|
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
585
503
|
const opt = fp.default !== undefined ? '?' : '';
|
|
586
|
-
lines.push(` ${fieldName}${opt}: ${fp.type};`);
|
|
504
|
+
lines.push(` ${fieldName}${opt}: ${emitTypeAnnotation(fp.type, 'unknown', field)};`);
|
|
587
505
|
}
|
|
588
506
|
lines.push('}');
|
|
589
507
|
lines.push('');
|
|
@@ -592,7 +510,7 @@ export function generateConfig(node) {
|
|
|
592
510
|
for (const field of fields) {
|
|
593
511
|
const fp = p(field);
|
|
594
512
|
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
595
|
-
const ftype = fp.type;
|
|
513
|
+
const ftype = emitTypeAnnotation(fp.type, 'unknown', field);
|
|
596
514
|
let def = fp.default;
|
|
597
515
|
if (def === undefined) {
|
|
598
516
|
if (ftype === 'number')
|
|
@@ -749,7 +667,7 @@ export function generateEvent(node) {
|
|
|
749
667
|
for (const t of types) {
|
|
750
668
|
const tp = p(t);
|
|
751
669
|
const tname = emitTemplateSafe((tp.name || tp.value));
|
|
752
|
-
const data = tp.data
|
|
670
|
+
const data = emitTypeAnnotation(tp.data, 'Record<string, unknown>', t);
|
|
753
671
|
lines.push(` '${tname}': ${data};`);
|
|
754
672
|
}
|
|
755
673
|
lines.push('}');
|
|
@@ -910,38 +828,41 @@ export function generateModule(node) {
|
|
|
910
828
|
lines.push('');
|
|
911
829
|
for (const exp of kids(node, 'export')) {
|
|
912
830
|
const ep = p(exp);
|
|
913
|
-
const
|
|
914
|
-
const
|
|
915
|
-
const
|
|
831
|
+
const rawFrom = ep.from;
|
|
832
|
+
const safeFrom = rawFrom ? emitImportSpecifier(rawFrom, exp) : '';
|
|
833
|
+
const rawNames = ep.names;
|
|
834
|
+
const safeNames = rawNames ? rawNames.split(',').map(s => emitIdentifier(s.trim(), 'export', exp)).join(', ') : '';
|
|
835
|
+
const rawTypeNames = ep.types;
|
|
836
|
+
const safeTypeNames = rawTypeNames ? rawTypeNames.split(',').map(s => emitIdentifier(s.trim(), 'export', exp)).join(', ') : '';
|
|
916
837
|
const star = ep.star === 'true' || ep.star === true;
|
|
917
|
-
const
|
|
838
|
+
const safeDefault = ep.default ? emitIdentifier(ep.default, 'default', exp) : '';
|
|
918
839
|
// export * from './foo.js'
|
|
919
|
-
if (
|
|
920
|
-
lines.push(`export * from '${
|
|
840
|
+
if (safeFrom && !safeNames && !safeTypeNames && star) {
|
|
841
|
+
lines.push(`export * from '${safeFrom}';`);
|
|
921
842
|
}
|
|
922
843
|
// export { a, b } from './foo.js'
|
|
923
|
-
if (
|
|
924
|
-
lines.push(`export { ${
|
|
844
|
+
if (safeFrom && safeNames) {
|
|
845
|
+
lines.push(`export { ${safeNames} } from '${safeFrom}';`);
|
|
925
846
|
}
|
|
926
847
|
// export type { A, B } from './types.js'
|
|
927
|
-
if (
|
|
928
|
-
lines.push(`export type { ${
|
|
848
|
+
if (safeFrom && safeTypeNames) {
|
|
849
|
+
lines.push(`export type { ${safeTypeNames} } from '${safeFrom}';`);
|
|
929
850
|
}
|
|
930
851
|
// export default foo
|
|
931
|
-
if (
|
|
932
|
-
lines.push(`export default ${
|
|
852
|
+
if (safeDefault && !safeFrom) {
|
|
853
|
+
lines.push(`export default ${safeDefault};`);
|
|
933
854
|
}
|
|
934
855
|
// export default from './foo.js' (re-export default)
|
|
935
|
-
if (
|
|
936
|
-
lines.push(`export { default as ${
|
|
856
|
+
if (safeDefault && safeFrom) {
|
|
857
|
+
lines.push(`export { default as ${safeDefault} } from '${safeFrom}';`);
|
|
937
858
|
}
|
|
938
859
|
// export { a, b } (no from — local re-export)
|
|
939
|
-
if (!
|
|
940
|
-
lines.push(`export { ${
|
|
860
|
+
if (!safeFrom && safeNames && !safeDefault) {
|
|
861
|
+
lines.push(`export { ${safeNames} };`);
|
|
941
862
|
}
|
|
942
863
|
// export type { A, B } (no from — local type re-export)
|
|
943
|
-
if (!
|
|
944
|
-
lines.push(`export type { ${
|
|
864
|
+
if (!safeFrom && safeTypeNames && !safeDefault) {
|
|
865
|
+
lines.push(`export type { ${safeTypeNames} };`);
|
|
945
866
|
}
|
|
946
867
|
}
|
|
947
868
|
// Inline child definitions
|
|
@@ -973,21 +894,23 @@ export function generateImport(node) {
|
|
|
973
894
|
const isTypeOnly = props.types === 'true' || props.types === true;
|
|
974
895
|
if (!from)
|
|
975
896
|
return [];
|
|
897
|
+
const safePath = emitImportSpecifier(from, node);
|
|
976
898
|
const typeKw = isTypeOnly ? 'type ' : '';
|
|
899
|
+
const safeDefault = defaultImport ? emitIdentifier(defaultImport, 'default', node) : '';
|
|
977
900
|
const namedList = names
|
|
978
|
-
? names.split(',').map(s => s.trim()).join(', ')
|
|
901
|
+
? names.split(',').map(s => emitIdentifier(s.trim(), 'import', node)).join(', ')
|
|
979
902
|
: '';
|
|
980
|
-
if (
|
|
981
|
-
return [`import ${typeKw}${
|
|
903
|
+
if (safeDefault && namedList) {
|
|
904
|
+
return [`import ${typeKw}${safeDefault}, { ${namedList} } from '${safePath}';`];
|
|
982
905
|
}
|
|
983
|
-
if (
|
|
984
|
-
return [`import ${typeKw}${
|
|
906
|
+
if (safeDefault) {
|
|
907
|
+
return [`import ${typeKw}${safeDefault} from '${safePath}';`];
|
|
985
908
|
}
|
|
986
909
|
if (namedList) {
|
|
987
|
-
return [`import ${typeKw}{ ${namedList} } from '${
|
|
910
|
+
return [`import ${typeKw}{ ${namedList} } from '${safePath}';`];
|
|
988
911
|
}
|
|
989
912
|
// Side-effect import
|
|
990
|
-
return [`import '${
|
|
913
|
+
return [`import '${safePath}';`];
|
|
991
914
|
}
|
|
992
915
|
// ── Const ───────────────────────────────────────────────────────────────
|
|
993
916
|
// const name=AGON_HOME type=string
|
|
@@ -1005,7 +928,7 @@ export function generateConst(node) {
|
|
|
1005
928
|
const value = props.value;
|
|
1006
929
|
const exp = exportPrefix(node);
|
|
1007
930
|
const code = handlerCode(node);
|
|
1008
|
-
const typeAnnotation = constType ? `: ${constType}` : '';
|
|
931
|
+
const typeAnnotation = constType ? `: ${emitTypeAnnotation(constType, 'unknown', node)}` : '';
|
|
1009
932
|
if (code) {
|
|
1010
933
|
return [`${exp}const ${name}${typeAnnotation} = ${code.trim()};`];
|
|
1011
934
|
}
|
|
@@ -1014,125 +937,9 @@ export function generateConst(node) {
|
|
|
1014
937
|
}
|
|
1015
938
|
return [`${exp}const ${name}${typeAnnotation};`];
|
|
1016
939
|
}
|
|
1017
|
-
// ── Shared Helpers
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
export function parseParamList(params) {
|
|
1021
|
-
if (!params)
|
|
1022
|
-
return '';
|
|
1023
|
-
return splitParamsRespectingDepth(params).map(s => {
|
|
1024
|
-
const trimmed = s.trim();
|
|
1025
|
-
// Split name from type:default — find the first ':'
|
|
1026
|
-
const colonIdx = trimmed.indexOf(':');
|
|
1027
|
-
if (colonIdx === -1)
|
|
1028
|
-
return trimmed;
|
|
1029
|
-
const pname = trimmed.slice(0, colonIdx).trim();
|
|
1030
|
-
const rest = trimmed.slice(colonIdx + 1).trim();
|
|
1031
|
-
// Split type from default value — find '=' not inside angle brackets or parens
|
|
1032
|
-
const eqIdx = findDefaultSeparator(rest);
|
|
1033
|
-
if (eqIdx === -1) {
|
|
1034
|
-
return `${pname}: ${rest}`;
|
|
1035
|
-
}
|
|
1036
|
-
const ptype = rest.slice(0, eqIdx).trim();
|
|
1037
|
-
const pdefault = rest.slice(eqIdx + 1).trim();
|
|
1038
|
-
return `${pname}: ${ptype} = ${pdefault}`;
|
|
1039
|
-
}).join(', ');
|
|
1040
|
-
}
|
|
1041
|
-
/** Split param string on commas while respecting <>, (), {} depth.
|
|
1042
|
-
* Handles => (arrow) without decrementing depth. */
|
|
1043
|
-
function splitParamsRespectingDepth(s) {
|
|
1044
|
-
const parts = [];
|
|
1045
|
-
let depth = 0;
|
|
1046
|
-
let current = '';
|
|
1047
|
-
for (let i = 0; i < s.length; i++) {
|
|
1048
|
-
const ch = s[i];
|
|
1049
|
-
if (ch === '<' || ch === '(' || ch === '{')
|
|
1050
|
-
depth++;
|
|
1051
|
-
else if ((ch === '>' || ch === ')' || ch === '}') && depth > 0)
|
|
1052
|
-
depth--;
|
|
1053
|
-
if (ch === ',' && depth === 0) {
|
|
1054
|
-
parts.push(current);
|
|
1055
|
-
current = '';
|
|
1056
|
-
}
|
|
1057
|
-
else {
|
|
1058
|
-
current += ch;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
if (current.trim())
|
|
1062
|
-
parts.push(current);
|
|
1063
|
-
return parts;
|
|
1064
|
-
}
|
|
1065
|
-
/** Find the index of '=' that separates type from default value,
|
|
1066
|
-
* skipping '=' inside arrow functions (=>), generics, or parens. */
|
|
1067
|
-
function findDefaultSeparator(rest) {
|
|
1068
|
-
let depth = 0;
|
|
1069
|
-
for (let i = 0; i < rest.length; i++) {
|
|
1070
|
-
const ch = rest[i];
|
|
1071
|
-
if (ch === '<' || ch === '(' || ch === '{')
|
|
1072
|
-
depth++;
|
|
1073
|
-
else if (ch === '>' || ch === ')' || ch === '}')
|
|
1074
|
-
depth--;
|
|
1075
|
-
else if (ch === '=' && depth === 0) {
|
|
1076
|
-
// Skip '=>' (arrow function in type)
|
|
1077
|
-
if (rest[i + 1] === '>')
|
|
1078
|
-
continue;
|
|
1079
|
-
return i;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
return -1;
|
|
1083
|
-
}
|
|
1084
|
-
export function capitalize(s) {
|
|
1085
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1086
|
-
}
|
|
1087
|
-
// Hook codegen moved to @kernlang/react (generateHook in codegen-react.ts)
|
|
1088
|
-
// ── Reason & Confidence Annotations ──────────────────────────────────────
|
|
1089
|
-
export function emitReasonAnnotations(node) {
|
|
1090
|
-
const reasonNode = firstChild(node, 'reason');
|
|
1091
|
-
const evidenceNode = firstChild(node, 'evidence');
|
|
1092
|
-
const needsNodes = kids(node, 'needs');
|
|
1093
|
-
const confidence = p(node).confidence;
|
|
1094
|
-
if (!reasonNode && !evidenceNode && !confidence && needsNodes.length === 0)
|
|
1095
|
-
return [];
|
|
1096
|
-
const lines = ['/**'];
|
|
1097
|
-
if (confidence)
|
|
1098
|
-
lines.push(` * @confidence ${confidence}`);
|
|
1099
|
-
if (reasonNode) {
|
|
1100
|
-
const rp = p(reasonNode);
|
|
1101
|
-
lines.push(` * @reason ${rp.because || ''}`);
|
|
1102
|
-
if (rp.basis)
|
|
1103
|
-
lines.push(` * @basis ${rp.basis}`);
|
|
1104
|
-
if (rp.survives)
|
|
1105
|
-
lines.push(` * @survives ${rp.survives}`);
|
|
1106
|
-
}
|
|
1107
|
-
if (evidenceNode) {
|
|
1108
|
-
const ep = p(evidenceNode);
|
|
1109
|
-
const parts = [`source=${ep.source}`];
|
|
1110
|
-
if (ep.method)
|
|
1111
|
-
parts.push(`method=${ep.method}`);
|
|
1112
|
-
if (ep.authority)
|
|
1113
|
-
parts.push(`authority=${ep.authority}`);
|
|
1114
|
-
lines.push(` * @evidence ${parts.join(', ')}`);
|
|
1115
|
-
}
|
|
1116
|
-
for (const needsNode of needsNodes) {
|
|
1117
|
-
const np = p(needsNode);
|
|
1118
|
-
const desc = np.what || np.description || '';
|
|
1119
|
-
const wouldRaise = np['would-raise-to'];
|
|
1120
|
-
const tag = wouldRaise ? `${desc} (would raise to ${wouldRaise})` : desc;
|
|
1121
|
-
lines.push(` * @needs ${tag}`);
|
|
1122
|
-
}
|
|
1123
|
-
lines.push(' */');
|
|
1124
|
-
return lines;
|
|
1125
|
-
}
|
|
1126
|
-
/** Emit a TODO comment for nodes with low literal confidence (< 0.5). */
|
|
1127
|
-
export function emitLowConfidenceTodo(node, confidence) {
|
|
1128
|
-
if (!confidence)
|
|
1129
|
-
return [];
|
|
1130
|
-
const val = parseFloat(confidence);
|
|
1131
|
-
if (isNaN(val) || val >= 0.5 || confidence.includes(':'))
|
|
1132
|
-
return [];
|
|
1133
|
-
const name = p(node).name || node.type;
|
|
1134
|
-
return [`// TODO(low-confidence): ${name} confidence=${confidence}`];
|
|
1135
|
-
}
|
|
940
|
+
// ── Shared Helpers ───────────────────────────────────────────────────────
|
|
941
|
+
// parseParamList, capitalize, emitReasonAnnotations, emitLowConfidenceTodo
|
|
942
|
+
// implementations extracted to codegen/helpers.ts. Re-exported at top of file.
|
|
1136
943
|
// ── Ground Layer: derive ─────────────────────────────────────────────────
|
|
1137
944
|
// derive name=loudness expr={{average(stems)}} type=number deps="stems"
|
|
1138
945
|
// → export const loudness: number = average(stems);
|
|
@@ -1142,10 +949,11 @@ export function generateDerive(node) {
|
|
|
1142
949
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1143
950
|
const props = p(node);
|
|
1144
951
|
const name = emitIdentifier(props.name, 'derived', node);
|
|
952
|
+
// expr is by-design raw code (escape hatch)
|
|
1145
953
|
const expr = props.expr;
|
|
1146
954
|
const constType = props.type;
|
|
1147
955
|
const exp = exportPrefix(node);
|
|
1148
|
-
const typeAnnotation = constType ? `: ${constType}` : '';
|
|
956
|
+
const typeAnnotation = constType ? `: ${emitTypeAnnotation(constType, 'unknown', node)}` : '';
|
|
1149
957
|
return [...todo, ...annotations, `${exp}const ${name}${typeAnnotation} = ${expr};`];
|
|
1150
958
|
}
|
|
1151
959
|
// ── Ground Layer: transform ──────────────────────────────────────────────
|
|
@@ -1157,12 +965,13 @@ export function generateTransform(node) {
|
|
|
1157
965
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1158
966
|
const props = p(node);
|
|
1159
967
|
const name = emitIdentifier(props.name, 'transform', node);
|
|
968
|
+
// target and via are by-design raw code (escape hatches)
|
|
1160
969
|
const target = props.target;
|
|
1161
970
|
const via = props.via;
|
|
1162
971
|
const constType = props.type;
|
|
1163
972
|
const exp = exportPrefix(node);
|
|
1164
973
|
const code = handlerCode(node);
|
|
1165
|
-
const typeAnnotation = constType ? `: ${constType}` : '';
|
|
974
|
+
const typeAnnotation = constType ? `: ${emitTypeAnnotation(constType, 'unknown', node)}` : '';
|
|
1166
975
|
if (code) {
|
|
1167
976
|
// Handler block form — generate a function
|
|
1168
977
|
const lines = [...todo, ...annotations];
|
|
@@ -1207,7 +1016,7 @@ export function generateAction(node) {
|
|
|
1207
1016
|
lines.push(`/** @action ${metaParts.join(' ')} */`);
|
|
1208
1017
|
}
|
|
1209
1018
|
const paramList = params ? parseParamList(params) : '';
|
|
1210
|
-
const retClause = returns ? `: Promise<${returns}>` : ': Promise<void>';
|
|
1019
|
+
const retClause = returns ? `: Promise<${emitTypeAnnotation(returns, 'void', node)}>` : ': Promise<void>';
|
|
1211
1020
|
lines.push(`${exp}async function ${name}(${paramList})${retClause} {`);
|
|
1212
1021
|
if (code) {
|
|
1213
1022
|
for (const line of code.split('\n')) {
|
|
@@ -1638,7 +1447,7 @@ export function generateRepository(node) {
|
|
|
1638
1447
|
const mparams = mp.params ? parseParamList(mp.params) : '';
|
|
1639
1448
|
const isAsync = mp.async === 'true' || mp.async === true;
|
|
1640
1449
|
const asyncKw = isAsync ? 'async ' : '';
|
|
1641
|
-
const mreturns = mp.returns ? `: ${mp.returns}` : '';
|
|
1450
|
+
const mreturns = mp.returns ? `: ${emitTypeAnnotation(mp.returns, 'unknown', method)}` : '';
|
|
1642
1451
|
const mcode = handlerCode(method);
|
|
1643
1452
|
lines.push(` ${asyncKw}${mname}(${mparams})${mreturns} {`);
|
|
1644
1453
|
if (mcode) {
|
|
@@ -1795,7 +1604,8 @@ export function isCoreNode(type) {
|
|
|
1795
1604
|
return CORE_NODE_TYPES.has(type);
|
|
1796
1605
|
}
|
|
1797
1606
|
/** Generate TypeScript for any core language node. */
|
|
1798
|
-
export function generateCoreNode(node, target) {
|
|
1607
|
+
export function generateCoreNode(node, target, runtime) {
|
|
1608
|
+
const rt = runtime ?? defaultRuntime;
|
|
1799
1609
|
switch (node.type) {
|
|
1800
1610
|
case 'type': return generateType(node);
|
|
1801
1611
|
case 'interface': return generateInterface(node);
|
|
@@ -1863,14 +1673,14 @@ export function generateCoreNode(node, target) {
|
|
|
1863
1673
|
case 'option': return [];
|
|
1864
1674
|
default: {
|
|
1865
1675
|
// Check evolved generators (v4) — target-specific first, then default
|
|
1866
|
-
const targetMap = target ?
|
|
1676
|
+
const targetMap = target ? rt.evolvedTargetGenerators.get(node.type) : undefined;
|
|
1867
1677
|
const targetGen = targetMap && target ? targetMap.get(target) : undefined;
|
|
1868
|
-
const evolvedGen = targetGen ||
|
|
1678
|
+
const evolvedGen = targetGen || rt.evolvedGenerators.get(node.type);
|
|
1869
1679
|
if (evolvedGen)
|
|
1870
1680
|
return evolvedGen(node);
|
|
1871
1681
|
// Check if this is a template instance
|
|
1872
|
-
if (isTemplateNode(node.type))
|
|
1873
|
-
return expandTemplateNode(node);
|
|
1682
|
+
if (isTemplateNode(node.type, rt))
|
|
1683
|
+
return expandTemplateNode(node, 0, rt);
|
|
1874
1684
|
return [];
|
|
1875
1685
|
}
|
|
1876
1686
|
}
|