@kernlang/core 3.1.1 → 3.1.3
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/README.md +3 -0
- package/dist/codegen-core.d.ts +10 -2
- package/dist/codegen-core.js +128 -227
- package/dist/codegen-core.js.map +1 -1
- package/dist/concepts.d.ts +12 -2
- package/dist/concepts.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +13 -0
- package/dist/parser.js +152 -41
- package/dist/parser.js.map +1 -1
- package/dist/spec.d.ts +3 -3
- package/dist/spec.js +5 -3
- package/dist/spec.js.map +1 -1
- package/dist/template-engine.js +9 -5
- package/dist/template-engine.js.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/utils.d.ts +9 -1
- package/dist/utils.js +63 -1
- package/dist/utils.js.map +1 -1
- package/package.json +10 -1
package/dist/codegen-core.js
CHANGED
|
@@ -8,6 +8,50 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { isTemplateNode, expandTemplateNode } from './template-engine.js';
|
|
10
10
|
import { KernCodegenError } from './errors.js';
|
|
11
|
+
// ── Safe Emitters (prompt-injection immunity) ────────────────────────────
|
|
12
|
+
// Every prop value interpolated into generated code MUST go through these.
|
|
13
|
+
// Raw string splicing is the root cause of codegen injection (audit 2026-03-25).
|
|
14
|
+
// Matches valid JS/TS identifiers — KERN hyphens are converted to camelCase by the parser.
|
|
15
|
+
// Allows $ for React patterns (e.g., $state). Does NOT allow hyphens since
|
|
16
|
+
// generated TypeScript rejects them (e.g., `interface My-User` is invalid TS).
|
|
17
|
+
const SAFE_IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
18
|
+
const SAFE_PATH_RE = /^[A-Za-z0-9/_.\-~]+$/;
|
|
19
|
+
/** Validate and emit a safe identifier for generated code. Throws on invalid. */
|
|
20
|
+
export function emitIdentifier(value, fallback, node) {
|
|
21
|
+
const v = value || fallback;
|
|
22
|
+
if (!SAFE_IDENT_RE.test(v)) {
|
|
23
|
+
throw new KernCodegenError(`Invalid identifier: '${v.slice(0, 50)}' — must match KERN identifier grammar [A-Za-z_$][A-Za-z0-9_$-]*`, node);
|
|
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
55
|
// ── Evolved Generators (v4) ─────────────────────────────────────────────
|
|
12
56
|
// Populated at startup by evolved-node-loader. Checked in generateCoreNode
|
|
13
57
|
// before the default case, allowing graduated nodes to produce output.
|
|
@@ -90,17 +134,18 @@ export function handlerCode(node) {
|
|
|
90
134
|
const p = getProps;
|
|
91
135
|
const kids = getChildren;
|
|
92
136
|
const firstChild = getFirstChild;
|
|
93
|
-
function exportPrefix(node) {
|
|
137
|
+
export function exportPrefix(node) {
|
|
94
138
|
return p(node).export === 'false' ? '' : 'export ';
|
|
95
139
|
}
|
|
96
140
|
// ── Type Alias ───────────────────────────────────────────────────────────
|
|
97
141
|
// type name=PlanState values="draft|approved|running|paused|completed|failed|cancelled"
|
|
98
142
|
// → export type PlanState = 'draft' | 'approved' | 'running' | ...;
|
|
99
143
|
export function generateType(node) {
|
|
100
|
-
const { name, values, alias } = p(node);
|
|
144
|
+
const { name: rawName, values, alias } = p(node);
|
|
145
|
+
const name = emitIdentifier(rawName, 'UnknownType', node);
|
|
101
146
|
const exp = exportPrefix(node);
|
|
102
147
|
if (values) {
|
|
103
|
-
const members = values.split('|').map(v => `'${v.trim()}'`).join(' | ');
|
|
148
|
+
const members = values.split('|').map(v => `'${emitTemplateSafe(v.trim())}'`).join(' | ');
|
|
104
149
|
return [`${exp}type ${name} = ${members};`];
|
|
105
150
|
}
|
|
106
151
|
if (alias) {
|
|
@@ -116,15 +161,16 @@ export function generateType(node) {
|
|
|
116
161
|
// field name=engineId type=string optional=true
|
|
117
162
|
export function generateInterface(node) {
|
|
118
163
|
const props = p(node);
|
|
119
|
-
const name = props.name;
|
|
164
|
+
const name = emitIdentifier(props.name, 'UnknownInterface', node);
|
|
120
165
|
const ext = props.extends ? ` extends ${props.extends}` : '';
|
|
121
166
|
const exp = exportPrefix(node);
|
|
122
167
|
const lines = [];
|
|
123
168
|
lines.push(`${exp}interface ${name}${ext} {`);
|
|
124
169
|
for (const field of kids(node, 'field')) {
|
|
125
170
|
const fp = p(field);
|
|
171
|
+
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
126
172
|
const opt = fp.optional === 'true' || fp.optional === true ? '?' : '';
|
|
127
|
-
lines.push(` ${
|
|
173
|
+
lines.push(` ${fieldName}${opt}: ${fp.type};`);
|
|
128
174
|
}
|
|
129
175
|
lines.push('}');
|
|
130
176
|
return lines;
|
|
@@ -141,8 +187,8 @@ export function generateInterface(node) {
|
|
|
141
187
|
// | { type: 'code'; language: string; code: string };
|
|
142
188
|
export function generateUnion(node) {
|
|
143
189
|
const props = p(node);
|
|
144
|
-
const name = props.name;
|
|
145
|
-
const discriminant = props.discriminant
|
|
190
|
+
const name = emitIdentifier(props.name, 'UnknownUnion', node);
|
|
191
|
+
const discriminant = emitIdentifier(props.discriminant, 'type', node);
|
|
146
192
|
const exp = exportPrefix(node);
|
|
147
193
|
const variants = kids(node, 'variant');
|
|
148
194
|
if (variants.length === 0) {
|
|
@@ -151,9 +197,9 @@ export function generateUnion(node) {
|
|
|
151
197
|
const lines = [`${exp}type ${name} =`];
|
|
152
198
|
for (let i = 0; i < variants.length; i++) {
|
|
153
199
|
const vp = p(variants[i]);
|
|
154
|
-
const vname = vp.name;
|
|
200
|
+
const vname = emitIdentifier(vp.name, 'variant', variants[i]);
|
|
155
201
|
const fields = kids(variants[i], 'field');
|
|
156
|
-
const fieldParts = [`${discriminant}: '${vname}'`];
|
|
202
|
+
const fieldParts = [`${discriminant}: '${emitTemplateSafe(vname)}'`];
|
|
157
203
|
for (const field of fields) {
|
|
158
204
|
const fp = p(field);
|
|
159
205
|
const opt = fp.optional === 'true' || fp.optional === true ? '?' : '';
|
|
@@ -180,7 +226,7 @@ export function generateUnion(node) {
|
|
|
180
226
|
// → export const tracker = new TokenTracker();
|
|
181
227
|
export function generateService(node) {
|
|
182
228
|
const props = p(node);
|
|
183
|
-
const name = props.name;
|
|
229
|
+
const name = emitIdentifier(props.name, 'UnknownService', node);
|
|
184
230
|
const impl = props.implements;
|
|
185
231
|
const exp = exportPrefix(node);
|
|
186
232
|
const lines = [];
|
|
@@ -214,7 +260,7 @@ export function generateService(node) {
|
|
|
214
260
|
// Methods
|
|
215
261
|
for (const method of kids(node, 'method')) {
|
|
216
262
|
const mp = p(method);
|
|
217
|
-
const mname = mp.name;
|
|
263
|
+
const mname = emitIdentifier(mp.name, 'method', method);
|
|
218
264
|
const mparams = mp.params ? parseParamList(mp.params) : '';
|
|
219
265
|
const isAsync = mp.async === 'true' || mp.async === true;
|
|
220
266
|
const isStream = mp.stream === 'true' || mp.stream === true;
|
|
@@ -241,8 +287,8 @@ export function generateService(node) {
|
|
|
241
287
|
// Singleton instances
|
|
242
288
|
for (const singleton of kids(node, 'singleton')) {
|
|
243
289
|
const sp = p(singleton);
|
|
244
|
-
const sname = sp.name;
|
|
245
|
-
const stype = sp.type
|
|
290
|
+
const sname = emitIdentifier(sp.name, 'instance', singleton);
|
|
291
|
+
const stype = emitIdentifier(sp.type, name, singleton);
|
|
246
292
|
lines.push('');
|
|
247
293
|
lines.push(`${exp}const ${sname} = new ${stype}();`);
|
|
248
294
|
}
|
|
@@ -255,7 +301,7 @@ export function generateService(node) {
|
|
|
255
301
|
// >>>
|
|
256
302
|
export function generateFunction(node) {
|
|
257
303
|
const props = p(node);
|
|
258
|
-
const name = props.name;
|
|
304
|
+
const name = emitIdentifier(props.name, 'unknownFn', node);
|
|
259
305
|
const params = props.params || '';
|
|
260
306
|
const returns = props.returns;
|
|
261
307
|
const isAsync = props.async === 'true' || props.async === true;
|
|
@@ -290,7 +336,7 @@ export function generateFunction(node) {
|
|
|
290
336
|
lines.push(`${exp}${asyncKw}function ${name}(${paramList})${retClause} {`);
|
|
291
337
|
// Signal → AbortController setup
|
|
292
338
|
if (hasSignal) {
|
|
293
|
-
const signalName = p(signalNode).name
|
|
339
|
+
const signalName = emitIdentifier(p(signalNode).name, 'abort', signalNode);
|
|
294
340
|
lines.push(` const ${signalName} = new AbortController();`);
|
|
295
341
|
}
|
|
296
342
|
// Wrap body in try/finally if cleanup exists
|
|
@@ -327,8 +373,8 @@ export function generateFunction(node) {
|
|
|
327
373
|
// message "Invalid plan state: expected ${expected}, got ${actual}"
|
|
328
374
|
export function generateError(node) {
|
|
329
375
|
const props = p(node);
|
|
330
|
-
const name = props.name;
|
|
331
|
-
const ext = props.extends
|
|
376
|
+
const name = emitIdentifier(props.name, 'UnknownError', node);
|
|
377
|
+
const ext = emitIdentifier(props.extends, 'Error', node);
|
|
332
378
|
const message = props.message;
|
|
333
379
|
const exp = exportPrefix(node);
|
|
334
380
|
const fields = kids(node, 'field');
|
|
@@ -412,18 +458,18 @@ export function generateError(node) {
|
|
|
412
458
|
// - approvePlan(), startPlan(), cancelPlan(), failPlan() functions
|
|
413
459
|
export function generateMachine(node) {
|
|
414
460
|
const props = p(node);
|
|
415
|
-
const name = props.name;
|
|
461
|
+
const name = emitIdentifier(props.name, 'UnknownMachine', node);
|
|
416
462
|
const exp = exportPrefix(node);
|
|
417
463
|
const lines = [];
|
|
418
464
|
// Collect states
|
|
419
465
|
const states = kids(node, 'state');
|
|
420
466
|
const stateNames = states.map(s => {
|
|
421
467
|
const sp = p(s);
|
|
422
|
-
return (sp.name || sp.value);
|
|
468
|
+
return emitIdentifier((sp.name || sp.value), 'state', s);
|
|
423
469
|
});
|
|
424
470
|
// State type
|
|
425
471
|
const stateType = `${name}State`;
|
|
426
|
-
lines.push(`${exp}type ${stateType} = ${stateNames.map(s => `'${s}'`).join(' | ')};`);
|
|
472
|
+
lines.push(`${exp}type ${stateType} = ${stateNames.map(s => `'${emitTemplateSafe(s)}'`).join(' | ')};`);
|
|
427
473
|
lines.push('');
|
|
428
474
|
// Error class
|
|
429
475
|
const errorName = `${name}StateError`;
|
|
@@ -442,7 +488,7 @@ export function generateMachine(node) {
|
|
|
442
488
|
const transitions = kids(node, 'transition');
|
|
443
489
|
for (const t of transitions) {
|
|
444
490
|
const tp = p(t);
|
|
445
|
-
const tname = tp.name;
|
|
491
|
+
const tname = emitIdentifier(tp.name, 'transition', t);
|
|
446
492
|
const from = tp.from;
|
|
447
493
|
const to = tp.to;
|
|
448
494
|
const fromStates = from.split('|').map(s => s.trim());
|
|
@@ -480,7 +526,7 @@ export function generateMachine(node) {
|
|
|
480
526
|
// Called by transpiler-ink.ts when target=ink.
|
|
481
527
|
export function generateMachineReducer(node) {
|
|
482
528
|
const props = p(node);
|
|
483
|
-
const name = props.name;
|
|
529
|
+
const name = emitIdentifier(props.name, 'UnknownMachine', node);
|
|
484
530
|
const exp = exportPrefix(node);
|
|
485
531
|
const lines = [];
|
|
486
532
|
// First emit the standard machine output
|
|
@@ -496,7 +542,7 @@ export function generateMachineReducer(node) {
|
|
|
496
542
|
const transitions = kids(node, 'transition');
|
|
497
543
|
const stateType = `${name}State`;
|
|
498
544
|
// Action type union
|
|
499
|
-
const actionNames = transitions.map(t => p(t).name);
|
|
545
|
+
const actionNames = transitions.map(t => emitIdentifier(p(t).name, 'action', t));
|
|
500
546
|
lines.push(`${exp}type ${name}Action = ${actionNames.map(a => `'${a}'`).join(' | ')};`);
|
|
501
547
|
lines.push('');
|
|
502
548
|
// Reducer function
|
|
@@ -505,9 +551,9 @@ export function generateMachineReducer(node) {
|
|
|
505
551
|
lines.push(` switch (action) {`);
|
|
506
552
|
for (const t of transitions) {
|
|
507
553
|
const tp = p(t);
|
|
508
|
-
const tname = tp.name;
|
|
554
|
+
const tname = emitIdentifier(tp.name, 'action', t);
|
|
509
555
|
const fnName = `${tname}${name}`;
|
|
510
|
-
lines.push(` case '${tname}': return ${fnName}(entity).state;`);
|
|
556
|
+
lines.push(` case '${emitTemplateSafe(tname)}': return ${fnName}(entity).state;`);
|
|
511
557
|
}
|
|
512
558
|
lines.push(` default: return state;`);
|
|
513
559
|
lines.push(` }`);
|
|
@@ -527,7 +573,7 @@ export function generateMachineReducer(node) {
|
|
|
527
573
|
// field name=approvalLevel type=ApprovalLevel default="plan"
|
|
528
574
|
export function generateConfig(node) {
|
|
529
575
|
const props = p(node);
|
|
530
|
-
const name = props.name;
|
|
576
|
+
const name = emitIdentifier(props.name, 'Config', node);
|
|
531
577
|
const exp = exportPrefix(node);
|
|
532
578
|
const fields = kids(node, 'field');
|
|
533
579
|
const lines = [];
|
|
@@ -535,8 +581,9 @@ export function generateConfig(node) {
|
|
|
535
581
|
lines.push(`${exp}interface ${name} {`);
|
|
536
582
|
for (const field of fields) {
|
|
537
583
|
const fp = p(field);
|
|
584
|
+
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
538
585
|
const opt = fp.default !== undefined ? '?' : '';
|
|
539
|
-
lines.push(` ${
|
|
586
|
+
lines.push(` ${fieldName}${opt}: ${fp.type};`);
|
|
540
587
|
}
|
|
541
588
|
lines.push('}');
|
|
542
589
|
lines.push('');
|
|
@@ -544,6 +591,7 @@ export function generateConfig(node) {
|
|
|
544
591
|
lines.push(`${exp}const DEFAULT_${name.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()}: Required<${name}> = {`);
|
|
545
592
|
for (const field of fields) {
|
|
546
593
|
const fp = p(field);
|
|
594
|
+
const fieldName = emitIdentifier(fp.name, 'field', field);
|
|
547
595
|
const ftype = fp.type;
|
|
548
596
|
let def = fp.default;
|
|
549
597
|
if (def === undefined) {
|
|
@@ -557,9 +605,9 @@ export function generateConfig(node) {
|
|
|
557
605
|
def = "''";
|
|
558
606
|
}
|
|
559
607
|
else if (ftype === 'string' || (!['number', 'boolean'].includes(ftype) && !ftype.endsWith('[]') && !def.startsWith("'") && !def.startsWith('"'))) {
|
|
560
|
-
def =
|
|
608
|
+
def = emitStringLiteral(def);
|
|
561
609
|
}
|
|
562
|
-
lines.push(` ${
|
|
610
|
+
lines.push(` ${fieldName}: ${def},`);
|
|
563
611
|
}
|
|
564
612
|
lines.push('};');
|
|
565
613
|
return lines;
|
|
@@ -569,16 +617,17 @@ export function generateConfig(node) {
|
|
|
569
617
|
// model Plan
|
|
570
618
|
export function generateStore(node) {
|
|
571
619
|
const props = p(node);
|
|
572
|
-
const name = props.name;
|
|
573
|
-
const
|
|
574
|
-
const key = props.key
|
|
575
|
-
const model = props.model
|
|
620
|
+
const name = emitIdentifier(props.name, 'Store', node);
|
|
621
|
+
const rawPath = props.path || '~/.data';
|
|
622
|
+
const key = emitIdentifier(props.key, 'id', node);
|
|
623
|
+
const model = emitIdentifier(props.model, 'unknown', node);
|
|
576
624
|
const exp = exportPrefix(node);
|
|
577
625
|
const lines = [];
|
|
578
626
|
const dirConst = `${name.toUpperCase()}_DIR`;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
627
|
+
// Validate path before interpolation — blocks injection + traversal via storePath
|
|
628
|
+
const resolvedPath = rawPath.startsWith('~/')
|
|
629
|
+
? `join(homedir(), ${emitPath(rawPath.slice(2), node)})`
|
|
630
|
+
: emitPath(rawPath, node);
|
|
582
631
|
lines.push(`import { readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs';`);
|
|
583
632
|
lines.push(`import { join, resolve } from 'node:path';`);
|
|
584
633
|
lines.push(`import { homedir } from 'node:os';`);
|
|
@@ -591,6 +640,7 @@ export function generateStore(node) {
|
|
|
591
640
|
lines.push('');
|
|
592
641
|
lines.push(`function safe${name}Path(id: string): string {`);
|
|
593
642
|
lines.push(` const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, '');`);
|
|
643
|
+
lines.push(` if (!sanitized) throw new Error(\`Invalid ID: \${id}\`);`);
|
|
594
644
|
lines.push(` const full = resolve(${dirConst}, \`\${sanitized}.json\`);`);
|
|
595
645
|
lines.push(` if (!full.startsWith(resolve(${dirConst}))) throw new Error(\`Invalid ID: \${id}\`);`);
|
|
596
646
|
lines.push(` return full;`);
|
|
@@ -603,17 +653,18 @@ export function generateStore(node) {
|
|
|
603
653
|
lines.push('');
|
|
604
654
|
lines.push(`${exp}function load${name}(id: string): ${model} | null {`);
|
|
605
655
|
lines.push(` try { return JSON.parse(readFileSync(safe${name}Path(id), 'utf-8')) as ${model}; }`);
|
|
606
|
-
lines.push(` catch { return null; }`);
|
|
656
|
+
lines.push(` catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null; throw e; }`);
|
|
607
657
|
lines.push('}');
|
|
608
658
|
lines.push('');
|
|
609
659
|
lines.push(`${exp}function list${name}s(limit = 20): ${model}[] {`);
|
|
610
660
|
lines.push(` ensure${name}Dir();`);
|
|
611
|
-
lines.push(`
|
|
612
|
-
lines.push(`
|
|
613
|
-
lines.push(`
|
|
614
|
-
lines.push(`
|
|
615
|
-
lines.push(`
|
|
616
|
-
lines.push(` }
|
|
661
|
+
lines.push(` const files = readdirSync(${dirConst}).filter(f => f.endsWith('.json'));`);
|
|
662
|
+
lines.push(` const items: ${model}[] = [];`);
|
|
663
|
+
lines.push(` for (const f of files) {`);
|
|
664
|
+
lines.push(` try { items.push(JSON.parse(readFileSync(join(${dirConst}, f), 'utf-8')) as ${model}); }`);
|
|
665
|
+
lines.push(` catch { /* skip corrupt files */ }`);
|
|
666
|
+
lines.push(` }`);
|
|
667
|
+
lines.push(` return items.sort((a: any, b: any) => (b.updatedAt || '').localeCompare(a.updatedAt || '')).slice(0, limit);`);
|
|
617
668
|
lines.push('}');
|
|
618
669
|
lines.push('');
|
|
619
670
|
lines.push(`${exp}function delete${name}(id: string): boolean {`);
|
|
@@ -631,7 +682,7 @@ export function generateStore(node) {
|
|
|
631
682
|
// >>>
|
|
632
683
|
export function generateTest(node) {
|
|
633
684
|
const props = p(node);
|
|
634
|
-
const name = props.name;
|
|
685
|
+
const name = emitTemplateSafe(props.name || 'UnknownTest');
|
|
635
686
|
const lines = [];
|
|
636
687
|
lines.push(`import { describe, it, expect } from 'vitest';`);
|
|
637
688
|
lines.push('');
|
|
@@ -644,10 +695,10 @@ export function generateTest(node) {
|
|
|
644
695
|
}
|
|
645
696
|
lines.push(`describe('${name}', () => {`);
|
|
646
697
|
for (const desc of kids(node, 'describe')) {
|
|
647
|
-
const dname = p(desc).name;
|
|
698
|
+
const dname = emitTemplateSafe(p(desc).name || 'describe');
|
|
648
699
|
lines.push(` describe('${dname}', () => {`);
|
|
649
700
|
for (const test of kids(desc, 'it')) {
|
|
650
|
-
const tname = p(test).name;
|
|
701
|
+
const tname = emitTemplateSafe(p(test).name || 'test');
|
|
651
702
|
const code = handlerCode(test);
|
|
652
703
|
lines.push(` it('${tname}', () => {`);
|
|
653
704
|
if (code) {
|
|
@@ -660,7 +711,7 @@ export function generateTest(node) {
|
|
|
660
711
|
}
|
|
661
712
|
// Top-level it blocks
|
|
662
713
|
for (const test of kids(node, 'it')) {
|
|
663
|
-
const tname = p(test).name;
|
|
714
|
+
const tname = emitTemplateSafe(p(test).name || 'test');
|
|
664
715
|
const code = handlerCode(test);
|
|
665
716
|
lines.push(` it('${tname}', () => {`);
|
|
666
717
|
if (code) {
|
|
@@ -679,12 +730,12 @@ export function generateTest(node) {
|
|
|
679
730
|
// type name="winner:determined" data="{ winner: string, bestScore: number }"
|
|
680
731
|
export function generateEvent(node) {
|
|
681
732
|
const props = p(node);
|
|
682
|
-
const name = props.name;
|
|
733
|
+
const name = emitIdentifier(props.name, 'UnknownEvent', node);
|
|
683
734
|
const exp = exportPrefix(node);
|
|
684
735
|
const types = kids(node, 'type');
|
|
685
736
|
const lines = [];
|
|
686
737
|
// Event type union
|
|
687
|
-
lines.push(`${exp}type ${name}Type = ${types.map(t => `'${(p(t).name || p(t).value)}'`).join(' | ')};`);
|
|
738
|
+
lines.push(`${exp}type ${name}Type = ${types.map(t => `'${emitTemplateSafe((p(t).name || p(t).value))}'`).join(' | ')};`);
|
|
688
739
|
lines.push('');
|
|
689
740
|
// Event interface
|
|
690
741
|
lines.push(`${exp}interface ${name} {`);
|
|
@@ -697,7 +748,7 @@ export function generateEvent(node) {
|
|
|
697
748
|
lines.push(`${exp}interface ${name}Map {`);
|
|
698
749
|
for (const t of types) {
|
|
699
750
|
const tp = p(t);
|
|
700
|
-
const tname = (tp.name || tp.value);
|
|
751
|
+
const tname = emitTemplateSafe((tp.name || tp.value));
|
|
701
752
|
const data = tp.data || 'Record<string, unknown>';
|
|
702
753
|
lines.push(` '${tname}': ${data};`);
|
|
703
754
|
}
|
|
@@ -853,7 +904,7 @@ export function generateWebSocket(node) {
|
|
|
853
904
|
// export from="./plan.js" names="createPlan,advanceStep"
|
|
854
905
|
export function generateModule(node) {
|
|
855
906
|
const props = p(node);
|
|
856
|
-
const name = props.name;
|
|
907
|
+
const name = emitTemplateSafe(props.name || 'unknown');
|
|
857
908
|
const lines = [];
|
|
858
909
|
lines.push(`// ── Module: ${name} ──`);
|
|
859
910
|
lines.push('');
|
|
@@ -949,7 +1000,7 @@ export function generateImport(node) {
|
|
|
949
1000
|
// → export const DEFAULT_WEIGHTS: ScoreWeights = { pass: 50 };
|
|
950
1001
|
export function generateConst(node) {
|
|
951
1002
|
const props = p(node);
|
|
952
|
-
const name = props.name;
|
|
1003
|
+
const name = emitIdentifier(props.name, 'unknownConst', node);
|
|
953
1004
|
const constType = props.type;
|
|
954
1005
|
const value = props.value;
|
|
955
1006
|
const exp = exportPrefix(node);
|
|
@@ -1033,157 +1084,7 @@ function findDefaultSeparator(rest) {
|
|
|
1033
1084
|
export function capitalize(s) {
|
|
1034
1085
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1035
1086
|
}
|
|
1036
|
-
//
|
|
1037
|
-
// hook name=useSearch params="initialState:SearchState" returns=UseSearchResult
|
|
1038
|
-
// state name=query type=string init="initialState.query"
|
|
1039
|
-
// ref name=abortCtrl type=AbortController init="new AbortController()"
|
|
1040
|
-
// context name=env type=EnvConfig source=EnvContext
|
|
1041
|
-
// handler <<<
|
|
1042
|
-
// const { data } = useSWR(cacheKey, fetcher);
|
|
1043
|
-
// >>>
|
|
1044
|
-
// memo name=cacheKey deps="query,filters"
|
|
1045
|
-
// handler <<<
|
|
1046
|
-
// return buildCacheKey(query, filters);
|
|
1047
|
-
// >>>
|
|
1048
|
-
// callback name=handleFilter params="field:string,value:string" deps="query"
|
|
1049
|
-
// handler <<<
|
|
1050
|
-
// setQuery(prev => updateFilter(prev, field, value));
|
|
1051
|
-
// >>>
|
|
1052
|
-
// effect deps="query"
|
|
1053
|
-
// handler <<<
|
|
1054
|
-
// trackSearch(query);
|
|
1055
|
-
// >>>
|
|
1056
|
-
// returns names="articles:data?.articles,isLoading,handleFilter,cacheKey"
|
|
1057
|
-
export function generateHook(node) {
|
|
1058
|
-
const props = p(node);
|
|
1059
|
-
const name = props.name;
|
|
1060
|
-
const params = props.params || '';
|
|
1061
|
-
const returnsType = props.returns;
|
|
1062
|
-
const exp = exportPrefix(node);
|
|
1063
|
-
const lines = [];
|
|
1064
|
-
const reactImports = new Set();
|
|
1065
|
-
// Parse params
|
|
1066
|
-
const paramList = parseParamList(params);
|
|
1067
|
-
const retClause = returnsType ? `: ${returnsType}` : '';
|
|
1068
|
-
lines.push(`${exp}function ${name}(${paramList})${retClause} {`);
|
|
1069
|
-
// Emit children in source order — returns is always last
|
|
1070
|
-
const children = kids(node);
|
|
1071
|
-
const returnsNode = children.find(c => c.type === 'returns');
|
|
1072
|
-
const ordered = children.filter(c => c.type !== 'returns');
|
|
1073
|
-
for (const child of ordered) {
|
|
1074
|
-
const cp = p(child);
|
|
1075
|
-
switch (child.type) {
|
|
1076
|
-
case 'state': {
|
|
1077
|
-
reactImports.add('useState');
|
|
1078
|
-
const sname = cp.name;
|
|
1079
|
-
const stype = cp.type || 'unknown';
|
|
1080
|
-
const sinit = cp.init || 'undefined';
|
|
1081
|
-
const setter = `set${capitalize(sname)}`;
|
|
1082
|
-
lines.push(` const [${sname}, ${setter}] = useState<${stype}>(${sinit});`);
|
|
1083
|
-
break;
|
|
1084
|
-
}
|
|
1085
|
-
case 'ref': {
|
|
1086
|
-
reactImports.add('useRef');
|
|
1087
|
-
const rname = cp.name;
|
|
1088
|
-
const rtype = cp.type || 'unknown';
|
|
1089
|
-
const rinit = cp.init || 'null';
|
|
1090
|
-
lines.push(` const ${rname} = useRef<${rtype}>(${rinit});`);
|
|
1091
|
-
break;
|
|
1092
|
-
}
|
|
1093
|
-
case 'context': {
|
|
1094
|
-
reactImports.add('useContext');
|
|
1095
|
-
const cname = cp.name;
|
|
1096
|
-
const csource = cp.source;
|
|
1097
|
-
lines.push(` const ${cname} = useContext(${csource});`);
|
|
1098
|
-
break;
|
|
1099
|
-
}
|
|
1100
|
-
case 'handler': {
|
|
1101
|
-
const code = cp.code || '';
|
|
1102
|
-
const dedented = dedent(code);
|
|
1103
|
-
for (const line of dedented.split('\n')) {
|
|
1104
|
-
lines.push(` ${line}`);
|
|
1105
|
-
}
|
|
1106
|
-
break;
|
|
1107
|
-
}
|
|
1108
|
-
case 'memo': {
|
|
1109
|
-
reactImports.add('useMemo');
|
|
1110
|
-
const mname = cp.name;
|
|
1111
|
-
const mdeps = cp.deps || '';
|
|
1112
|
-
const mcode = handlerCode(child);
|
|
1113
|
-
const depsArr = mdeps ? `[${mdeps}]` : '[]';
|
|
1114
|
-
lines.push(` const ${mname} = useMemo(() => {`);
|
|
1115
|
-
if (mcode) {
|
|
1116
|
-
for (const line of mcode.split('\n')) {
|
|
1117
|
-
lines.push(` ${line}`);
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
lines.push(` }, ${depsArr});`);
|
|
1121
|
-
break;
|
|
1122
|
-
}
|
|
1123
|
-
case 'callback': {
|
|
1124
|
-
reactImports.add('useCallback');
|
|
1125
|
-
const cbname = cp.name;
|
|
1126
|
-
const cbparams = cp.params || '';
|
|
1127
|
-
const cbdeps = cp.deps || '';
|
|
1128
|
-
const cbcode = handlerCode(child);
|
|
1129
|
-
const cbParamList = parseParamList(cbparams);
|
|
1130
|
-
const cbDepsArr = cbdeps ? `[${cbdeps}]` : '[]';
|
|
1131
|
-
lines.push(` const ${cbname} = useCallback((${cbParamList}) => {`);
|
|
1132
|
-
if (cbcode) {
|
|
1133
|
-
for (const line of cbcode.split('\n')) {
|
|
1134
|
-
lines.push(` ${line}`);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
lines.push(` }, ${cbDepsArr});`);
|
|
1138
|
-
break;
|
|
1139
|
-
}
|
|
1140
|
-
case 'effect': {
|
|
1141
|
-
reactImports.add('useEffect');
|
|
1142
|
-
const edeps = cp.deps || '';
|
|
1143
|
-
const ecode = handlerCode(child);
|
|
1144
|
-
const eDepsArr = edeps ? `[${edeps}]` : '[]';
|
|
1145
|
-
lines.push(` useEffect(() => {`);
|
|
1146
|
-
if (ecode) {
|
|
1147
|
-
for (const line of ecode.split('\n')) {
|
|
1148
|
-
lines.push(` ${line}`);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
// Check for cleanup block
|
|
1152
|
-
const cleanupNode = firstChild(child, 'cleanup');
|
|
1153
|
-
if (cleanupNode) {
|
|
1154
|
-
const cleanupCode = p(cleanupNode).code || '';
|
|
1155
|
-
const cleanupDedented = dedent(cleanupCode);
|
|
1156
|
-
lines.push(` return () => {`);
|
|
1157
|
-
for (const line of cleanupDedented.split('\n')) {
|
|
1158
|
-
lines.push(` ${line}`);
|
|
1159
|
-
}
|
|
1160
|
-
lines.push(` };`);
|
|
1161
|
-
}
|
|
1162
|
-
lines.push(` }, ${eDepsArr});`);
|
|
1163
|
-
break;
|
|
1164
|
-
}
|
|
1165
|
-
// Skip unknown child types silently
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
// Returns — always last
|
|
1169
|
-
if (returnsNode) {
|
|
1170
|
-
const rnames = p(returnsNode).names || '';
|
|
1171
|
-
const entries = rnames.split(',').map(e => {
|
|
1172
|
-
const [key, ...valueParts] = e.split(':');
|
|
1173
|
-
const value = valueParts.join(':').trim();
|
|
1174
|
-
return value ? `${key.trim()}: ${value}` : key.trim();
|
|
1175
|
-
});
|
|
1176
|
-
lines.push(` return { ${entries.join(', ')} };`);
|
|
1177
|
-
}
|
|
1178
|
-
lines.push('}');
|
|
1179
|
-
// Prepend React imports
|
|
1180
|
-
if (reactImports.size > 0) {
|
|
1181
|
-
const importLine = `import { ${[...reactImports].sort().join(', ')} } from 'react';`;
|
|
1182
|
-
lines.unshift('');
|
|
1183
|
-
lines.unshift(importLine);
|
|
1184
|
-
}
|
|
1185
|
-
return lines;
|
|
1186
|
-
}
|
|
1087
|
+
// Hook codegen moved to @kernlang/react (generateHook in codegen-react.ts)
|
|
1187
1088
|
// ── Reason & Confidence Annotations ──────────────────────────────────────
|
|
1188
1089
|
export function emitReasonAnnotations(node) {
|
|
1189
1090
|
const reasonNode = firstChild(node, 'reason');
|
|
@@ -1240,7 +1141,7 @@ export function generateDerive(node) {
|
|
|
1240
1141
|
const conf = p(node).confidence;
|
|
1241
1142
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1242
1143
|
const props = p(node);
|
|
1243
|
-
const name = props.name;
|
|
1144
|
+
const name = emitIdentifier(props.name, 'derived', node);
|
|
1244
1145
|
const expr = props.expr;
|
|
1245
1146
|
const constType = props.type;
|
|
1246
1147
|
const exp = exportPrefix(node);
|
|
@@ -1255,7 +1156,7 @@ export function generateTransform(node) {
|
|
|
1255
1156
|
const conf = p(node).confidence;
|
|
1256
1157
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1257
1158
|
const props = p(node);
|
|
1258
|
-
const name = props.name;
|
|
1159
|
+
const name = emitIdentifier(props.name, 'transform', node);
|
|
1259
1160
|
const target = props.target;
|
|
1260
1161
|
const via = props.via;
|
|
1261
1162
|
const constType = props.type;
|
|
@@ -1288,7 +1189,7 @@ export function generateAction(node) {
|
|
|
1288
1189
|
const conf = p(node).confidence;
|
|
1289
1190
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1290
1191
|
const props = p(node);
|
|
1291
|
-
const name = props.name;
|
|
1192
|
+
const name = emitIdentifier(props.name, 'action', node);
|
|
1292
1193
|
const idempotent = props.idempotent === 'true' || props.idempotent === true;
|
|
1293
1194
|
const reversible = props.reversible === 'true' || props.reversible === true;
|
|
1294
1195
|
const params = props.params || '';
|
|
@@ -1407,7 +1308,7 @@ export function generateCollect(node) {
|
|
|
1407
1308
|
const conf = p(node).confidence;
|
|
1408
1309
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1409
1310
|
const props = p(node);
|
|
1410
|
-
const name = props.name;
|
|
1311
|
+
const name = emitIdentifier(props.name, 'collected', node);
|
|
1411
1312
|
const from = props.from;
|
|
1412
1313
|
const where = props.where;
|
|
1413
1314
|
const limit = props.limit;
|
|
@@ -1464,7 +1365,7 @@ export function generateResolve(node) {
|
|
|
1464
1365
|
const conf = p(node).confidence;
|
|
1465
1366
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1466
1367
|
const props = p(node);
|
|
1467
|
-
const name = props.name;
|
|
1368
|
+
const name = emitIdentifier(props.name, 'resolver', node);
|
|
1468
1369
|
const candidates = kids(node, 'candidate');
|
|
1469
1370
|
const discriminator = firstChild(node, 'discriminator');
|
|
1470
1371
|
if (!discriminator)
|
|
@@ -1478,7 +1379,7 @@ export function generateResolve(node) {
|
|
|
1478
1379
|
lines.push(`const _${name}_candidates = [`);
|
|
1479
1380
|
for (const c of candidates) {
|
|
1480
1381
|
const cp = p(c);
|
|
1481
|
-
const cname = cp.name;
|
|
1382
|
+
const cname = emitIdentifier(cp.name, 'candidate', c);
|
|
1482
1383
|
const code = handlerCode(c);
|
|
1483
1384
|
lines.push(` { name: '${cname}', fn: (signal: unknown) => { ${code.trim()} } },`);
|
|
1484
1385
|
}
|
|
@@ -1542,7 +1443,7 @@ export function generateRecover(node) {
|
|
|
1542
1443
|
const conf = p(node).confidence;
|
|
1543
1444
|
const todo = emitLowConfidenceTodo(node, conf);
|
|
1544
1445
|
const props = p(node);
|
|
1545
|
-
const name = props.name;
|
|
1446
|
+
const name = emitIdentifier(props.name, 'recovery', node);
|
|
1546
1447
|
const strategies = kids(node, 'strategy');
|
|
1547
1448
|
const hasFallback = strategies.some(s => p(s).name === 'fallback');
|
|
1548
1449
|
if (!hasFallback)
|
|
@@ -1552,7 +1453,7 @@ export function generateRecover(node) {
|
|
|
1552
1453
|
lines.push(`async function ${name}WithRecovery<T>(fn: () => Promise<T>): Promise<T> {`);
|
|
1553
1454
|
for (const strategy of strategies) {
|
|
1554
1455
|
const sp = p(strategy);
|
|
1555
|
-
const sname = sp.name;
|
|
1456
|
+
const sname = emitIdentifier(sp.name, 'strategy', strategy);
|
|
1556
1457
|
const code = handlerCode(strategy);
|
|
1557
1458
|
if (sname === 'retry') {
|
|
1558
1459
|
const max = Number(sp.max) || 3;
|
|
@@ -1596,16 +1497,16 @@ export function generatePattern(node) {
|
|
|
1596
1497
|
// pattern nodes are registered as templates — no direct output
|
|
1597
1498
|
return [];
|
|
1598
1499
|
}
|
|
1599
|
-
export function generateApply(node) {
|
|
1500
|
+
export function generateApply(node, _depth = 0) {
|
|
1600
1501
|
// apply nodes expand the referenced pattern
|
|
1601
1502
|
const props = p(node);
|
|
1602
1503
|
const patternName = props.pattern;
|
|
1603
1504
|
if (!patternName)
|
|
1604
1505
|
return [];
|
|
1605
|
-
// Delegate to template expansion
|
|
1506
|
+
// Delegate to template expansion — propagate depth to prevent infinite recursion
|
|
1606
1507
|
const syntheticNode = { ...node, type: patternName };
|
|
1607
1508
|
if (isTemplateNode(patternName)) {
|
|
1608
|
-
return expandTemplateNode(syntheticNode);
|
|
1509
|
+
return expandTemplateNode(syntheticNode, _depth + 1);
|
|
1609
1510
|
}
|
|
1610
1511
|
return [`// apply: pattern '${patternName}' not found`];
|
|
1611
1512
|
}
|
|
@@ -1659,12 +1560,12 @@ export function generateSelect(node) {
|
|
|
1659
1560
|
const lines = onChange ? [`{/* kern:use-client */}`] : [];
|
|
1660
1561
|
lines.push(`<select ${attrs.join(' ')}>`);
|
|
1661
1562
|
if (placeholder) {
|
|
1662
|
-
lines.push(` <option value="" disabled>${placeholder}</option>`);
|
|
1563
|
+
lines.push(` <option value="" disabled>${emitTemplateSafe(placeholder)}</option>`);
|
|
1663
1564
|
}
|
|
1664
1565
|
for (const opt of kids(node, 'option')) {
|
|
1665
1566
|
const op = p(opt);
|
|
1666
|
-
const optValue = op.value || '';
|
|
1667
|
-
const optLabel = op.label ||
|
|
1567
|
+
const optValue = emitTemplateSafe(op.value || '');
|
|
1568
|
+
const optLabel = emitTemplateSafe(op.label || op.value || '');
|
|
1668
1569
|
lines.push(` <option value="${optValue}">${optLabel}</option>`);
|
|
1669
1570
|
}
|
|
1670
1571
|
lines.push(`</select>`);
|
|
@@ -1677,7 +1578,7 @@ export function generateSelect(node) {
|
|
|
1677
1578
|
// relation name=posts target=Post kind=one-to-many cascade=delete
|
|
1678
1579
|
export function generateModel(node) {
|
|
1679
1580
|
const props = p(node);
|
|
1680
|
-
const name = props.name;
|
|
1581
|
+
const name = emitIdentifier(props.name, 'UnknownModel', node);
|
|
1681
1582
|
const table = props.table;
|
|
1682
1583
|
const exp = exportPrefix(node);
|
|
1683
1584
|
const lines = [];
|
|
@@ -1685,14 +1586,14 @@ export function generateModel(node) {
|
|
|
1685
1586
|
lines.push(`${exp}interface ${name} {`);
|
|
1686
1587
|
for (const col of kids(node, 'column')) {
|
|
1687
1588
|
const cp = p(col);
|
|
1688
|
-
const colName = cp.name;
|
|
1589
|
+
const colName = emitIdentifier(cp.name, 'column', col);
|
|
1689
1590
|
const colType = mapColumnType(cp.type);
|
|
1690
1591
|
const opt = cp.optional === 'true' || cp.optional === true ? '?' : '';
|
|
1691
1592
|
lines.push(` ${colName}${opt}: ${colType};`);
|
|
1692
1593
|
}
|
|
1693
1594
|
for (const rel of kids(node, 'relation')) {
|
|
1694
1595
|
const rp = p(rel);
|
|
1695
|
-
const relName = rp.name;
|
|
1596
|
+
const relName = emitIdentifier(rp.name, 'relation', rel);
|
|
1696
1597
|
const target = rp.target;
|
|
1697
1598
|
const kind = rp.kind || 'one-to-many';
|
|
1698
1599
|
const relType = kind.includes('many') ? `${target}[]` : target;
|
|
@@ -1722,7 +1623,7 @@ function mapColumnType(kernType) {
|
|
|
1722
1623
|
// handler <<<return this.findOne({ email });>>>
|
|
1723
1624
|
export function generateRepository(node) {
|
|
1724
1625
|
const props = p(node);
|
|
1725
|
-
const name = props.name;
|
|
1626
|
+
const name = emitIdentifier(props.name, 'UnknownRepo', node);
|
|
1726
1627
|
const model = props.model;
|
|
1727
1628
|
const exp = exportPrefix(node);
|
|
1728
1629
|
const lines = [];
|
|
@@ -1733,7 +1634,7 @@ export function generateRepository(node) {
|
|
|
1733
1634
|
}
|
|
1734
1635
|
for (const method of kids(node, 'method')) {
|
|
1735
1636
|
const mp = p(method);
|
|
1736
|
-
const mname = mp.name;
|
|
1637
|
+
const mname = emitIdentifier(mp.name, 'method', method);
|
|
1737
1638
|
const mparams = mp.params ? parseParamList(mp.params) : '';
|
|
1738
1639
|
const isAsync = mp.async === 'true' || mp.async === true;
|
|
1739
1640
|
const asyncKw = isAsync ? 'async ' : '';
|
|
@@ -1758,7 +1659,7 @@ export function generateRepository(node) {
|
|
|
1758
1659
|
// returns AuthService with=repo
|
|
1759
1660
|
export function generateDependency(node) {
|
|
1760
1661
|
const props = p(node);
|
|
1761
|
-
const name = props.name;
|
|
1662
|
+
const name = emitIdentifier(props.name, 'unknownDep', node);
|
|
1762
1663
|
const scope = props.scope || 'transient';
|
|
1763
1664
|
const exp = exportPrefix(node);
|
|
1764
1665
|
const lines = [];
|
|
@@ -1775,7 +1676,7 @@ export function generateDependency(node) {
|
|
|
1775
1676
|
}
|
|
1776
1677
|
for (const inj of injects) {
|
|
1777
1678
|
const ip = p(inj);
|
|
1778
|
-
const injName = ip.name;
|
|
1679
|
+
const injName = emitIdentifier(ip.name, 'dep', inj);
|
|
1779
1680
|
const injType = ip.type;
|
|
1780
1681
|
const injFrom = ip.from;
|
|
1781
1682
|
const injWith = ip.with;
|
|
@@ -1810,7 +1711,7 @@ export function generateDependency(node) {
|
|
|
1810
1711
|
// invalidate on=userUpdate tags="user:{id}"
|
|
1811
1712
|
export function generateCache(node) {
|
|
1812
1713
|
const props = p(node);
|
|
1813
|
-
const name = props.name;
|
|
1714
|
+
const name = emitIdentifier(props.name, 'unknownCache', node);
|
|
1814
1715
|
const backend = props.backend || 'memory';
|
|
1815
1716
|
const prefix = props.prefix || '';
|
|
1816
1717
|
const ttl = props.ttl;
|
|
@@ -1825,7 +1726,7 @@ export function generateCache(node) {
|
|
|
1825
1726
|
// Entry methods
|
|
1826
1727
|
for (const entry of kids(node, 'entry')) {
|
|
1827
1728
|
const ep = p(entry);
|
|
1828
|
-
const entryName = ep.name;
|
|
1729
|
+
const entryName = emitIdentifier(ep.name, 'entry', entry);
|
|
1829
1730
|
const key = ep.key || entryName;
|
|
1830
1731
|
const strategyNode = firstChild(entry, 'strategy');
|
|
1831
1732
|
const strategy = strategyNode ? (p(strategyNode).name || 'cache-aside') : 'cache-aside';
|
|
@@ -1910,7 +1811,7 @@ export function generateCoreNode(node, target) {
|
|
|
1910
1811
|
case 'event': return generateEvent(node);
|
|
1911
1812
|
case 'import': return generateImport(node);
|
|
1912
1813
|
case 'const': return generateConst(node);
|
|
1913
|
-
case 'hook': return
|
|
1814
|
+
case 'hook': return []; // Handled by @kernlang/react
|
|
1914
1815
|
case 'on': return generateOn(node);
|
|
1915
1816
|
case 'websocket': return generateWebSocket(node);
|
|
1916
1817
|
// Ground layer
|