@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.
- package/dist/codegen/body-ts.js +146 -3
- package/dist/codegen/body-ts.js.map +1 -1
- package/dist/codegen/functions.js +9 -1
- package/dist/codegen/functions.js.map +1 -1
- package/dist/codegen/modules.js +26 -3
- package/dist/codegen/modules.js.map +1 -1
- package/dist/codegen/react-hook-imports.d.ts +72 -0
- package/dist/codegen/react-hook-imports.js +162 -0
- package/dist/codegen/react-hook-imports.js.map +1 -0
- package/dist/codegen-expression.js +10 -3
- package/dist/codegen-expression.js.map +1 -1
- package/dist/importer.js +25 -0
- package/dist/importer.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/native-eligibility-ast.d.ts +30 -3
- package/dist/native-eligibility-ast.js +338 -35
- package/dist/native-eligibility-ast.js.map +1 -1
- package/dist/native-eligibility.d.ts +7 -0
- package/dist/native-eligibility.js +81 -7
- package/dist/native-eligibility.js.map +1 -1
- package/dist/parser-core.js +264 -23
- package/dist/parser-core.js.map +1 -1
- package/dist/parser-diagnostics.js +2 -0
- package/dist/parser-diagnostics.js.map +1 -1
- package/dist/parser-expression.d.ts +2 -2
- package/dist/parser-expression.js +276 -11
- package/dist/parser-expression.js.map +1 -1
- package/dist/parser-keywords.js +429 -1
- package/dist/parser-keywords.js.map +1 -1
- package/dist/parser-validate-body-statements.js +16 -0
- package/dist/parser-validate-body-statements.js.map +1 -1
- package/dist/parser-validate-native-eligible.js +14 -5
- package/dist/parser-validate-native-eligible.js.map +1 -1
- package/dist/parser-validate-propagation.js +2 -0
- package/dist/parser-validate-propagation.js.map +1 -1
- package/dist/schema.js +120 -12
- package/dist/schema.js.map +1 -1
- package/dist/semantic-validator.js +20 -11
- package/dist/semantic-validator.js.map +1 -1
- package/dist/spec.d.ts +2 -2
- package/dist/spec.js +3 -1
- package/dist/spec.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/value-ir.d.ts +7 -0
- package/dist/value-ir.js +1 -0
- package/dist/value-ir.js.map +1 -1
- 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
|
|
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
|
|
172
|
+
const closeIdx = indexOfFenceOutsideQuotes(afterOpen, '>>>');
|
|
105
173
|
if (closeIdx !== -1) {
|
|
106
174
|
// Shape 1: inline single-line `handler <<< body >>>`.
|
|
107
|
-
bodies.push(
|
|
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
|
|
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(
|
|
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 =
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"native-eligibility.js","sourceRoot":"","sources":["../src/native-eligibility.ts"],"names":[],"mappings":"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"}
|
package/dist/parser-core.js
CHANGED
|
@@ -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(
|
|
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 +=
|
|
145
|
-
state.diagnostics[d].endCol +=
|
|
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,
|
|
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,
|
|
176
|
-
endCol:
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
386
|
-
|
|
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
|
|
391
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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 (
|
|
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
|
-
|
|
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);
|