@kernlang/core 3.4.6 → 3.4.7-canary.101.1.5437ac62

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.
Files changed (49) hide show
  1. package/dist/codegen/body-ts.js +146 -3
  2. package/dist/codegen/body-ts.js.map +1 -1
  3. package/dist/codegen/functions.js +9 -1
  4. package/dist/codegen/functions.js.map +1 -1
  5. package/dist/codegen/modules.js +26 -3
  6. package/dist/codegen/modules.js.map +1 -1
  7. package/dist/codegen/react-hook-imports.d.ts +72 -0
  8. package/dist/codegen/react-hook-imports.js +162 -0
  9. package/dist/codegen/react-hook-imports.js.map +1 -0
  10. package/dist/codegen-expression.js +10 -3
  11. package/dist/codegen-expression.js.map +1 -1
  12. package/dist/importer.js +25 -0
  13. package/dist/importer.js.map +1 -1
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.js +3 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/native-eligibility-ast.d.ts +30 -3
  18. package/dist/native-eligibility-ast.js +338 -35
  19. package/dist/native-eligibility-ast.js.map +1 -1
  20. package/dist/native-eligibility.d.ts +7 -0
  21. package/dist/native-eligibility.js +81 -7
  22. package/dist/native-eligibility.js.map +1 -1
  23. package/dist/parser-core.js +264 -23
  24. package/dist/parser-core.js.map +1 -1
  25. package/dist/parser-diagnostics.js +2 -0
  26. package/dist/parser-diagnostics.js.map +1 -1
  27. package/dist/parser-expression.d.ts +2 -2
  28. package/dist/parser-expression.js +276 -11
  29. package/dist/parser-expression.js.map +1 -1
  30. package/dist/parser-keywords.js +429 -1
  31. package/dist/parser-keywords.js.map +1 -1
  32. package/dist/parser-validate-body-statements.js +16 -0
  33. package/dist/parser-validate-body-statements.js.map +1 -1
  34. package/dist/parser-validate-native-eligible.js +14 -5
  35. package/dist/parser-validate-native-eligible.js.map +1 -1
  36. package/dist/parser-validate-propagation.js +2 -0
  37. package/dist/parser-validate-propagation.js.map +1 -1
  38. package/dist/schema.js +120 -12
  39. package/dist/schema.js.map +1 -1
  40. package/dist/semantic-validator.js +20 -11
  41. package/dist/semantic-validator.js.map +1 -1
  42. package/dist/spec.d.ts +2 -2
  43. package/dist/spec.js +3 -1
  44. package/dist/spec.js.map +1 -1
  45. package/dist/types.d.ts +1 -1
  46. package/dist/value-ir.d.ts +7 -0
  47. package/dist/value-ir.js +1 -0
  48. package/dist/value-ir.js.map +1 -1
  49. package/package.json +1 -1
@@ -35,7 +35,6 @@ export const LEGACY_NEG_PATTERNS = [
35
35
  /\bfor\s*\(/,
36
36
  /\bdo\s*\{/,
37
37
  /\bswitch\s*\(/,
38
- /\btypeof\b/,
39
38
  /\binstanceof\b/,
40
39
  /^\s*import\b/m,
41
40
  /\brequire\(/,
@@ -71,6 +70,73 @@ export const LEGACY_NEG_PATTERNS = [
71
70
  /\(\s*\.{3}/,
72
71
  /\/\w+\/[gimsy]*/,
73
72
  ];
73
+ const FOREIGN_HANDLER_LANGS = new Set(['ts', 'typescript', 'js', 'javascript', 'python', 'py']);
74
+ function createFenceScanState() {
75
+ return { inQuote: false, quoteChar: null, exprDepth: 0 };
76
+ }
77
+ function indexOfFenceOutsideQuotes(content, fence, state = createFenceScanState()) {
78
+ for (let i = 0; i < content.length; i++) {
79
+ const ch = content[i];
80
+ const next = content[i + 1];
81
+ if (ch === '\\' && state.inQuote) {
82
+ i++;
83
+ continue;
84
+ }
85
+ if ((ch === '"' || ch === "'" || ch === '`') && (!state.inQuote || ch === state.quoteChar)) {
86
+ if (state.inQuote) {
87
+ state.inQuote = false;
88
+ state.quoteChar = null;
89
+ }
90
+ else {
91
+ state.inQuote = true;
92
+ state.quoteChar = ch;
93
+ }
94
+ continue;
95
+ }
96
+ if (state.inQuote)
97
+ continue;
98
+ if (ch === '{' && next === '{') {
99
+ state.exprDepth++;
100
+ i++;
101
+ continue;
102
+ }
103
+ if (ch === '}' && next === '}' && state.exprDepth > 0) {
104
+ state.exprDepth--;
105
+ i++;
106
+ continue;
107
+ }
108
+ if (state.exprDepth > 0)
109
+ continue;
110
+ if (content.startsWith(fence, i))
111
+ return i;
112
+ }
113
+ return -1;
114
+ }
115
+ function parseBoundaryProp(opener, propName) {
116
+ const quoted = new RegExp(`(?:^|\\s)${propName}="([^"]*)"`).exec(opener);
117
+ if (quoted)
118
+ return quoted[1];
119
+ const bare = new RegExp(`(?:^|\\s)${propName}=([^\\s]+)`).exec(opener);
120
+ return bare?.[1];
121
+ }
122
+ function annotateRawBody(text, startLine, endLine, opener) {
123
+ return {
124
+ text,
125
+ startLine,
126
+ endLine,
127
+ opener: opener.trim(),
128
+ declaredLang: parseBoundaryProp(opener, 'lang'),
129
+ declaredReason: parseBoundaryProp(opener, 'reason'),
130
+ };
131
+ }
132
+ export function isExplicitForeignRawBody(body) {
133
+ const opener = body.opener?.trim();
134
+ if (opener && !/^handler\b/.test(opener))
135
+ return false;
136
+ const lang = body.declaredLang?.trim().toLowerCase();
137
+ const reason = body.declaredReason?.trim();
138
+ return Boolean(lang && reason && FOREIGN_HANDLER_LANGS.has(lang));
139
+ }
74
140
  /** Classify a single raw body. Slice α-3: delegates to the AST walker so
75
141
  * eligibility ≡ migrate-success by construction. */
76
142
  export function classifyHandlerBody(rawBody) {
@@ -94,17 +160,19 @@ export function extractRawBodies(content) {
94
160
  let inBody = false;
95
161
  let buf = [];
96
162
  let startLine = 0;
163
+ let opener = '';
164
+ let closeScanState = createFenceScanState();
97
165
  for (let i = 0; i < lines.length; i++) {
98
166
  const line = lines[i];
99
167
  if (!inBody) {
100
- const openIdx = line.indexOf('<<<');
168
+ const openIdx = indexOfFenceOutsideQuotes(line, '<<<');
101
169
  if (openIdx === -1)
102
170
  continue;
103
171
  const afterOpen = line.slice(openIdx + 3);
104
- const closeIdx = afterOpen.indexOf('>>>');
172
+ const closeIdx = indexOfFenceOutsideQuotes(afterOpen, '>>>');
105
173
  if (closeIdx !== -1) {
106
174
  // Shape 1: inline single-line `handler <<< body >>>`.
107
- bodies.push({ text: afterOpen.slice(0, closeIdx).trim(), startLine: i + 1, endLine: i + 1 });
175
+ bodies.push(annotateRawBody(afterOpen.slice(0, closeIdx).trim(), i + 1, i + 1, line.slice(0, openIdx)));
108
176
  continue;
109
177
  }
110
178
  // Shape 2/3: multi-line block. parser-core.ts `parseLines` discards
@@ -114,9 +182,11 @@ export function extractRawBodies(content) {
114
182
  inBody = true;
115
183
  buf = [];
116
184
  startLine = i + 1;
185
+ opener = line.slice(0, openIdx);
186
+ closeScanState = createFenceScanState();
117
187
  }
118
188
  else {
119
- const closeIdx = line.indexOf('>>>');
189
+ const closeIdx = indexOfFenceOutsideQuotes(line, '>>>', closeScanState);
120
190
  if (closeIdx === -1) {
121
191
  buf.push(line);
122
192
  continue;
@@ -124,8 +194,10 @@ export function extractRawBodies(content) {
124
194
  const before = line.slice(0, closeIdx).trim();
125
195
  if (before.length > 0)
126
196
  buf.push(before);
127
- bodies.push({ text: buf.join('\n'), startLine, endLine: i + 1 });
197
+ bodies.push(annotateRawBody(buf.join('\n'), startLine, i + 1, opener));
128
198
  inBody = false;
199
+ opener = '';
200
+ closeScanState = createFenceScanState();
129
201
  }
130
202
  }
131
203
  return bodies;
@@ -137,7 +209,9 @@ export function scanFileForEligibility(content) {
137
209
  const raw = extractRawBodies(content);
138
210
  let eligibleBodies = 0;
139
211
  const bodies = raw.map((body) => {
140
- const result = classifyHandlerBody(body.text);
212
+ const result = isExplicitForeignRawBody(body)
213
+ ? { eligible: false, reason: 'explicit-foreign' }
214
+ : classifyHandlerBody(body.text);
141
215
  if (result.eligible)
142
216
  eligibleBodies++;
143
217
  return { ...body, ...result };
@@ -1 +1 @@
1
- {"version":3,"file":"native-eligibility.js","sourceRoot":"","sources":["../src/native-eligibility.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-eligibility.js","sourceRoot":"","sources":["../src/native-eligibility.ts"],"names":[],"mappings":"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"}
@@ -70,6 +70,84 @@ function stripInlineComment(content) {
70
70
  }
71
71
  return content;
72
72
  }
73
+ function indexOfFenceOutsideQuotes(content, fence) {
74
+ let inQuote = false;
75
+ let quoteChar = null;
76
+ let exprDepth = 0;
77
+ for (let i = 0; i < content.length; i++) {
78
+ const ch = content[i];
79
+ const next = content[i + 1];
80
+ if (ch === '\\' && inQuote) {
81
+ i++;
82
+ continue;
83
+ }
84
+ if ((ch === '"' || ch === "'") && (!inQuote || ch === quoteChar)) {
85
+ if (inQuote) {
86
+ inQuote = false;
87
+ quoteChar = null;
88
+ }
89
+ else {
90
+ inQuote = true;
91
+ quoteChar = ch;
92
+ }
93
+ continue;
94
+ }
95
+ if (inQuote)
96
+ continue;
97
+ if (ch === '{' && next === '{') {
98
+ exprDepth++;
99
+ i++;
100
+ continue;
101
+ }
102
+ if (ch === '}' && next === '}' && exprDepth > 0) {
103
+ exprDepth--;
104
+ i++;
105
+ continue;
106
+ }
107
+ if (exprDepth > 0)
108
+ continue;
109
+ if (content.startsWith(fence, i))
110
+ return i;
111
+ }
112
+ return -1;
113
+ }
114
+ function findMultilineBlockOpen(trimmed, runtime) {
115
+ for (const type of runtime.multilineBlockTypes) {
116
+ if (!trimmed.startsWith(type))
117
+ continue;
118
+ const afterType = trimmed.slice(type.length);
119
+ if (!/^\s/.test(afterType))
120
+ continue;
121
+ const openIdx = indexOfFenceOutsideQuotes(trimmed, '<<<');
122
+ if (openIdx !== -1)
123
+ return { type, openIdx };
124
+ }
125
+ return undefined;
126
+ }
127
+ function parseDecoratorLine(trimmed, indent, lineNum) {
128
+ const exportMatch = /^export\s+(@[\s\S]+)$/u.exec(trimmed);
129
+ const exported = Boolean(exportMatch);
130
+ const decorator = exportMatch ? exportMatch[1].trimStart() : trimmed;
131
+ const decoratorCol = indent + 1 + (exported ? trimmed.indexOf('@') : 0);
132
+ const m = /^@([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)(?:\(([\s\S]*)\))?\s*$/.exec(decorator);
133
+ if (!m)
134
+ return null;
135
+ const [, name, args] = m;
136
+ return {
137
+ indent,
138
+ rawLength: trimmed.length,
139
+ type: 'decorator',
140
+ props: {
141
+ name,
142
+ ...(args !== undefined ? { args: args.trim() } : {}),
143
+ ...(exported ? { __exportNextFn: true } : {}),
144
+ },
145
+ styles: {},
146
+ pseudoStyles: {},
147
+ themeRefs: [],
148
+ loc: { line: lineNum, col: decoratorCol, endLine: lineNum, endCol: indent + 1 + trimmed.length },
149
+ };
150
+ }
73
151
  // ── Prop parsing ─────────────────────────────────────────────────────────
74
152
  /** Map a value token to its JS representation. */
75
153
  function tokenValue(tok) {
@@ -131,18 +209,21 @@ function parseLine(state, raw, lineNum, runtime = defaultRuntime) {
131
209
  if (content.trim() === '')
132
210
  return null;
133
211
  const col = indent + 1;
212
+ const exportMatch = /^export\s+(fn\b[\s\S]*)$/u.exec(content);
213
+ const contentForParse = exportMatch ? exportMatch[1] : content;
214
+ const parseCol = col + content.length - contentForParse.length;
134
215
  if (indentText.includes('\t')) {
135
216
  emitDiagnostic(state, 'INVALID_INDENT', 'warning', `Tab indentation at line ${lineNum}`, lineNum, 1, {
136
217
  endCol: indent + 1,
137
218
  });
138
219
  }
139
220
  const diagBefore = state.diagnostics.length;
140
- const tokens = tokenizeLineInternal(content, state);
221
+ const tokens = tokenizeLineInternal(contentForParse, state);
141
222
  for (let d = diagBefore; d < state.diagnostics.length; d++) {
142
223
  if (state.diagnostics[d].line === 0)
143
224
  state.diagnostics[d].line = lineNum;
144
- state.diagnostics[d].col += indent;
145
- state.diagnostics[d].endCol += indent;
225
+ state.diagnostics[d].col += parseCol - 1;
226
+ state.diagnostics[d].endCol += parseCol - 1;
146
227
  }
147
228
  const s = new TokenStream(tokens);
148
229
  // First token must be an identifier (the node type)
@@ -150,7 +231,7 @@ function parseLine(state, raw, lineNum, runtime = defaultRuntime) {
150
231
  if (!typeToken) {
151
232
  const firstToken = tokens.find((tok) => tok.kind !== 'whitespace');
152
233
  if (firstToken) {
153
- emitDiagnostic(state, 'DROPPED_LINE', 'error', `Dropped line ${lineNum}: expected a node type at the start of the line`, lineNum, col + firstToken.pos, {
234
+ emitDiagnostic(state, 'DROPPED_LINE', 'error', `Dropped line ${lineNum}: expected a node type at the start of the line`, lineNum, parseCol + firstToken.pos, {
154
235
  endCol: col + content.length,
155
236
  });
156
237
  }
@@ -167,16 +248,18 @@ function parseLine(state, raw, lineNum, runtime = defaultRuntime) {
167
248
  styles: {},
168
249
  pseudoStyles: {},
169
250
  themeRefs: [],
170
- loc: { line: lineNum, col, endLine: lineNum, endCol: col + content.length },
251
+ loc: { line: lineNum, col: parseCol, endLine: lineNum, endCol: col + content.length },
171
252
  };
172
253
  }
173
254
  const type = typeToken;
174
255
  if (!isKnownNodeType(type, runtime) && !runtime.multilineBlockTypes.has(type) && !runtime.isTemplateNode(type)) {
175
- emitDiagnostic(state, 'UNKNOWN_NODE_TYPE', 'warning', `Unknown node type '${type}' at line ${lineNum}`, lineNum, col, {
176
- endCol: col + type.length,
256
+ emitDiagnostic(state, 'UNKNOWN_NODE_TYPE', 'warning', `Unknown node type '${type}' at line ${lineNum}`, lineNum, parseCol, {
257
+ endCol: parseCol + type.length,
177
258
  });
178
259
  }
179
260
  const props = {};
261
+ if (exportMatch)
262
+ props.export = true;
180
263
  const quotedProps = new Set();
181
264
  const styles = {};
182
265
  const pseudoStyles = {};
@@ -203,7 +286,7 @@ function parseLine(state, raw, lineNum, runtime = defaultRuntime) {
203
286
  // ── Keyword-specific handling ──────────────────────────────────────
204
287
  const handler = KEYWORD_HANDLERS.get(type);
205
288
  if (handler)
206
- handler(s, props, content);
289
+ handler(s, props, contentForParse);
207
290
  // ── Generic prop/style/theme parsing ───────────────────────────────
208
291
  while (!s.done()) {
209
292
  s.skipWS();
@@ -223,11 +306,11 @@ function parseLine(state, raw, lineNum, runtime = defaultRuntime) {
223
306
  continue;
224
307
  }
225
308
  // Key=value prop (extracted helper from Codex)
226
- if (parseProp(state, s, props, quotedProps, lineNum, col))
309
+ if (parseProp(state, s, props, quotedProps, lineNum, parseCol))
227
310
  continue;
228
311
  // Unknown token — skip with warning
229
312
  const skipped = s.next();
230
- const errCol = col + skipped.pos;
313
+ const errCol = parseCol + skipped.pos;
231
314
  emitDiagnostic(state, 'UNEXPECTED_TOKEN', 'warning', `Unexpected token "${skipped.value}" at line ${lineNum}:${errCol}`, lineNum, errCol, {
232
315
  endCol: errCol + skipped.value.length,
233
316
  });
@@ -241,13 +324,15 @@ function parseLine(state, raw, lineNum, runtime = defaultRuntime) {
241
324
  styles,
242
325
  pseudoStyles,
243
326
  themeRefs,
244
- loc: { line: lineNum, col, endLine: lineNum, endCol: col + content.length },
327
+ loc: { line: lineNum, col: parseCol, endLine: lineNum, endCol: col + content.length },
245
328
  };
246
329
  }
247
330
  // ── Minified source expander ─────────────────────────────────────────────
248
331
  function expandMinified(source) {
249
332
  if (!source.includes('(') || source.split('\n').length > 1)
250
333
  return source;
334
+ if (/^\s*(?:export\s+)?fn\s+[A-Za-z_$][\w$]*/u.test(source))
335
+ return source;
251
336
  const result = [];
252
337
  let depth = 0;
253
338
  let current = '';
@@ -377,18 +462,57 @@ function scanLineState(s, prev) {
377
462
  function parseLines(state, source, runtime = defaultRuntime) {
378
463
  const lines = expandMinified(source).split('\n');
379
464
  const parsed = [];
465
+ const pendingDecorators = [];
466
+ const warnDroppedDecorators = (message) => {
467
+ for (const dec of pendingDecorators) {
468
+ emitDiagnostic(state, 'DROPPED_DECORATOR', 'warning', message(dec), dec.loc.line, dec.loc.col, {
469
+ endCol: dec.loc.endCol,
470
+ });
471
+ }
472
+ pendingDecorators.length = 0;
473
+ };
474
+ const pushParsed = (line) => {
475
+ parsed.push(line);
476
+ if (line.type !== 'fn') {
477
+ warnDroppedDecorators((dec) => `Decorator '${String(dec.props.name)}' at line ${dec.loc.line} must be followed by a fn declaration`);
478
+ return;
479
+ }
480
+ for (const dec of pendingDecorators) {
481
+ if (dec.indent !== line.indent) {
482
+ emitDiagnostic(state, 'DROPPED_DECORATOR', 'warning', `Decorator '${String(dec.props.name)}' at line ${dec.loc.line} must use the same indentation as its fn declaration`, dec.loc.line, dec.loc.col, { endCol: dec.loc.endCol });
483
+ continue;
484
+ }
485
+ if (dec.props.__exportNextFn === true) {
486
+ line.props.export = true;
487
+ }
488
+ delete dec.props.__exportNextFn;
489
+ parsed.push({ ...dec, indent: line.indent + 2 });
490
+ }
491
+ pendingDecorators.length = 0;
492
+ };
380
493
  for (let i = 0; i < lines.length; i++) {
381
494
  const trimmed = lines[i].trimStart();
382
495
  // Skip comment lines (// or #)
383
496
  if (trimmed.startsWith('//') || trimmed.startsWith('#'))
384
497
  continue;
385
- const multilineType = [...runtime.multilineBlockTypes].find((type) => trimmed.startsWith(`${type} <<<`));
386
- if (multilineType) {
498
+ if (trimmed.startsWith('@') || /^export\s+@/u.test(trimmed)) {
499
+ const indent = lines[i].search(/\S/);
500
+ const dec = parseDecoratorLine(stripInlineComment(lines[i].slice(indent)).trim(), indent, i + 1);
501
+ if (dec) {
502
+ pendingDecorators.push(dec);
503
+ continue;
504
+ }
505
+ }
506
+ const multilineOpen = findMultilineBlockOpen(trimmed, runtime);
507
+ if (multilineOpen) {
508
+ const multilineType = multilineOpen.type;
387
509
  const indent = lines[i].search(/\S/);
388
510
  const codeLines = [];
389
511
  const startLine = i + 1;
390
- const blockOpen = `${multilineType} <<<`;
391
- const afterOpen = trimmed.slice(blockOpen.length);
512
+ const openIdx = multilineOpen.openIdx;
513
+ const opener = trimmed.slice(0, openIdx).trimEnd();
514
+ const blockOpen = `${opener} <<<`;
515
+ const afterOpen = trimmed.slice(openIdx + 3);
392
516
  let closed = false;
393
517
  if (afterOpen.includes('>>>')) {
394
518
  closed = true;
@@ -417,14 +541,16 @@ function parseLines(state, source, runtime = defaultRuntime) {
417
541
  suggestion: `Close the '${multilineType} <<<' block with a matching '>>>' marker before the file ends.`,
418
542
  });
419
543
  }
420
- parsed.push({
544
+ const openerParsed = parseLine(state, `${' '.repeat(indent)}${opener}`, startLine, runtime);
545
+ pushParsed({
421
546
  indent,
422
547
  rawLength: lines[startLine - 1].slice(indent).length,
423
- type: multilineType,
424
- props: { code: codeLines.join('\n').replace(/^\n+|\n+$/g, '') },
425
- styles: {},
426
- pseudoStyles: {},
427
- themeRefs: [],
548
+ type: openerParsed?.type ?? multilineType,
549
+ props: { ...(openerParsed?.props ?? {}), code: codeLines.join('\n').replace(/^\n+|\n+$/g, '') },
550
+ quotedProps: openerParsed?.quotedProps,
551
+ styles: openerParsed?.styles ?? {},
552
+ pseudoStyles: openerParsed?.pseudoStyles ?? {},
553
+ themeRefs: openerParsed?.themeRefs ?? [],
428
554
  loc: {
429
555
  line: startLine,
430
556
  col: indent + 1,
@@ -446,8 +572,9 @@ function parseLines(state, source, runtime = defaultRuntime) {
446
572
  const nextTrimmed = lines[i + 1].trimStart();
447
573
  if (nextTrimmed.startsWith('//') || nextTrimmed.startsWith('#'))
448
574
  break;
449
- if ([...runtime.multilineBlockTypes].some((t) => nextTrimmed.startsWith(`${t} <<<`)))
575
+ if (findMultilineBlockOpen(nextTrimmed, runtime)) {
450
576
  break;
577
+ }
451
578
  i++;
452
579
  joinedParts.push(lines[i]);
453
580
  lineState = scanLineState(lines[i], lineState);
@@ -455,8 +582,9 @@ function parseLines(state, source, runtime = defaultRuntime) {
455
582
  const joined = joinedParts.length === 1 ? joinedParts[0] : joinedParts.join('\n');
456
583
  const p = parseLine(state, joined, startLine, runtime);
457
584
  if (p)
458
- parsed.push(p); // null only for blank/comment lines; __error nodes are always pushed
585
+ pushParsed(p); // null only for blank/comment lines; __error nodes are always pushed
459
586
  }
587
+ warnDroppedDecorators((dec) => `Decorator '${String(dec.props.name)}' at line ${dec.loc.line} must be followed by a fn declaration`);
460
588
  return parsed;
461
589
  }
462
590
  // ── Tree building ────────────────────────────────────────────────────────
@@ -501,6 +629,117 @@ function buildTree(state, parsed, root, rootIndent) {
501
629
  stack.push({ node, indent: p.indent });
502
630
  }
503
631
  }
632
+ function canonicalizeFirstClassFunctionBodies(state, node) {
633
+ if (node.children) {
634
+ for (const child of node.children)
635
+ canonicalizeFirstClassFunctionBodies(state, child);
636
+ }
637
+ if (node.type !== 'fn')
638
+ return;
639
+ const isFirstClassSyntax = node.props?.__firstClassSyntax === true;
640
+ if (node.props)
641
+ delete node.props.__firstClassSyntax;
642
+ if (!isFirstClassSyntax || !node.children?.some(isNativeBodyStatementChild))
643
+ return;
644
+ const existingHandler = node.children.find((child) => child.type === 'handler');
645
+ if (existingHandler) {
646
+ const firstBody = node.children.find(isNativeBodyStatementChild);
647
+ const loc = firstBody?.loc ?? existingHandler.loc ?? node.loc ?? { line: 1, col: 1, endCol: 2 };
648
+ emitDiagnostic(state, 'BODY_STATEMENT_OUTSIDE_NATIVE_HANDLER', 'error', 'First-class fn bodies cannot mix direct KERN body statements with an explicit `handler` child. Move the direct statements into the handler or remove the explicit handler.', loc.line, loc.col, { endCol: loc.endCol ?? loc.col + 1 });
649
+ return;
650
+ }
651
+ const nextChildren = [];
652
+ let implicitHandler = null;
653
+ for (const child of node.children) {
654
+ if (!isNativeBodyStatementChild(child)) {
655
+ nextChildren.push(child);
656
+ continue;
657
+ }
658
+ if (!implicitHandler) {
659
+ implicitHandler = {
660
+ type: 'handler',
661
+ props: { lang: 'kern' },
662
+ children: [],
663
+ loc: { ...(child.loc ?? node.loc ?? { line: 1, col: 1 }) },
664
+ };
665
+ nextChildren.push(implicitHandler);
666
+ }
667
+ implicitHandler.children.push(child);
668
+ }
669
+ node.children = nextChildren;
670
+ }
671
+ function isKernSourcePath(path) {
672
+ return typeof path === 'string' && path.endsWith('.kern');
673
+ }
674
+ function canonicalizeFirstClassModuleImports(node) {
675
+ if (!node.children)
676
+ return;
677
+ const nextChildren = [];
678
+ for (const child of node.children) {
679
+ canonicalizeFirstClassModuleImports(child);
680
+ const isFirstClassImport = child.type === 'import' && child.props?.__firstClassImport === true;
681
+ if (!isFirstClassImport) {
682
+ nextChildren.push(child);
683
+ continue;
684
+ }
685
+ const props = child.props ?? {};
686
+ const bindings = Array.isArray(props.__firstClassBindings)
687
+ ? props.__firstClassBindings
688
+ : [];
689
+ const from = props.from;
690
+ delete props.__firstClassImport;
691
+ delete props.__firstClassBindings;
692
+ if (!isKernSourcePath(from)) {
693
+ nextChildren.push(child);
694
+ continue;
695
+ }
696
+ const isTypeOnly = props.types === true || props.types === 'true';
697
+ nextChildren.push({
698
+ type: 'use',
699
+ props: { path: from },
700
+ children: bindings.map((binding) => ({
701
+ type: 'from',
702
+ props: {
703
+ name: String(binding.name),
704
+ ...(typeof binding.as === 'string' ? { as: binding.as } : {}),
705
+ ...(isTypeOnly ? { kind: 'type' } : {}),
706
+ },
707
+ children: [],
708
+ loc: child.loc,
709
+ })),
710
+ loc: child.loc,
711
+ });
712
+ }
713
+ node.children = nextChildren;
714
+ }
715
+ function isNativeBodyStatementChild(node) {
716
+ switch (node.type) {
717
+ case 'cell':
718
+ case 'set':
719
+ case 'comment':
720
+ case 'let':
721
+ case 'assign':
722
+ case 'destructure':
723
+ case 'do':
724
+ case 'fmt':
725
+ case 'return':
726
+ case 'if':
727
+ case 'else':
728
+ case 'while':
729
+ case 'for':
730
+ case 'each':
731
+ case 'try':
732
+ case 'catch':
733
+ case 'finally':
734
+ case 'throw':
735
+ case 'continue':
736
+ case 'break':
737
+ case 'branch':
738
+ return true;
739
+ default:
740
+ return false;
741
+ }
742
+ }
504
743
  /** Recursively compute endLine/endCol for each node based on its last child. */
505
744
  function computeEndSpans(node) {
506
745
  if (node.children && node.children.length > 0) {
@@ -539,6 +778,8 @@ export function parseInternal(source, asDocument, runtime, options) {
539
778
  root = toNode(parsed[0]);
540
779
  buildTree(state, parsed.slice(1), root, parsed[0].indent);
541
780
  }
781
+ canonicalizeFirstClassFunctionBodies(state, root);
782
+ canonicalizeFirstClassModuleImports(root);
542
783
  computeEndSpans(root);
543
784
  validateExpressions(state, root);
544
785
  validateEffects(state, root);