@kernlang/core 3.4.2 → 3.4.3-canary.11.1.8a8b6d69
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.d.ts +5 -2
- package/dist/codegen/body-ts.js +54 -7
- package/dist/codegen/body-ts.js.map +1 -1
- package/dist/codegen/kern-stdlib.d.ts +1 -1
- package/dist/codegen/kern-stdlib.js +12 -1
- package/dist/codegen/kern-stdlib.js.map +1 -1
- package/dist/codegen-expression.js +27 -0
- package/dist/codegen-expression.js.map +1 -1
- package/dist/concepts.d.ts +53 -0
- package/dist/concepts.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/native-eligibility-ast.d.ts +52 -0
- package/dist/native-eligibility-ast.js +189 -0
- package/dist/native-eligibility-ast.js.map +1 -0
- package/dist/native-eligibility.d.ts +29 -18
- package/dist/native-eligibility.js +24 -26
- package/dist/native-eligibility.js.map +1 -1
- package/dist/parser-expression.d.ts +4 -2
- package/dist/parser-expression.js +72 -11
- package/dist/parser-expression.js.map +1 -1
- package/dist/parser-validate-body-statements.d.ts +6 -6
- package/dist/parser-validate-body-statements.js +7 -6
- package/dist/parser-validate-body-statements.js.map +1 -1
- package/dist/parser-validate-native-eligible.d.ts +11 -6
- package/dist/parser-validate-native-eligible.js +12 -7
- package/dist/parser-validate-native-eligible.js.map +1 -1
- package/dist/schema.js +11 -4
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +1 -1
- package/dist/spec.js +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/value-ir.d.ts +6 -0
- package/dist/value-ir.js +2 -1
- package/dist/value-ir.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/** Native KERN handler-body AST eligibility — slice α-3.
|
|
2
|
+
*
|
|
3
|
+
* Replaces slice 5a's regex pre-screen (in `native-eligibility.ts`) with a
|
|
4
|
+
* TS-AST walk that mirrors the migrator's `mapStatement` rules in
|
|
5
|
+
* `packages/cli/src/commands/migrate-native-handlers.ts`. After this slice,
|
|
6
|
+
* the diagnostic and the migrator agree by construction:
|
|
7
|
+
*
|
|
8
|
+
* classifyHandlerBodyAst(body).eligible === true
|
|
9
|
+
* ⟺ kern migrate native-handlers will emit a `lang="kern"` rewrite for it.
|
|
10
|
+
*
|
|
11
|
+
* Why this matters: the slice 5a regex disagreed with the migrator's deeper
|
|
12
|
+
* TS-AST shape check on ~34% of "eligible" bodies (agon scan, 2026-05-04).
|
|
13
|
+
* Promoting the diagnostic from `info` to `warn` at that disagreement rate
|
|
14
|
+
* would surface fix-or-suppress noise on bodies the migrator silently bails
|
|
15
|
+
* on — exactly the no-unused-vars trust-collapse pattern. AST agreement is
|
|
16
|
+
* the prerequisite for the future warn promotion.
|
|
17
|
+
*
|
|
18
|
+
* The reason strings here are deliberately specific (e.g. `var-destructure`,
|
|
19
|
+
* `try-finally`, `expr-stmt-mutation`) so users running
|
|
20
|
+
* `kern migrate native-handlers` and `kern review` see actionable hints
|
|
21
|
+
* instead of a generic "ineligible". */
|
|
22
|
+
import ts from 'typescript';
|
|
23
|
+
import { parseExpression } from './parser-expression.js';
|
|
24
|
+
/** True when `exprText` parses cleanly under KERN's parser-expression. The
|
|
25
|
+
* multi-line guard catches body-statement attributes (`value="…"`) where
|
|
26
|
+
* raw newlines would break the line shape.
|
|
27
|
+
*
|
|
28
|
+
* Exported so the migrator (`migrate-native-handlers.ts`) shares the
|
|
29
|
+
* same predicate the classifier uses — slice α-3 gemini review pulled
|
|
30
|
+
* the formerly duplicated helper into core to prevent the migrator's
|
|
31
|
+
* bail conditions from drifting away from the classifier's pass
|
|
32
|
+
* conditions. */
|
|
33
|
+
export function isValidKernExpression(exprText) {
|
|
34
|
+
if (/\n/.test(exprText))
|
|
35
|
+
return false;
|
|
36
|
+
try {
|
|
37
|
+
parseExpression(exprText);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** True when `bodyText` contains any line or block comment. The migrator
|
|
45
|
+
* drops comments silently on rewrite, so a body containing them is
|
|
46
|
+
* ineligible — preserving the comment is the user's responsibility.
|
|
47
|
+
* Exported (slice α-3 gemini review) so the migrator imports the same
|
|
48
|
+
* scanner predicate and comment-detection cannot diverge between the
|
|
49
|
+
* two sides. */
|
|
50
|
+
export function hasComments(bodyText) {
|
|
51
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, /*skipTrivia*/ false);
|
|
52
|
+
scanner.setText(bodyText);
|
|
53
|
+
while (true) {
|
|
54
|
+
const kind = scanner.scan();
|
|
55
|
+
if (kind === ts.SyntaxKind.EndOfFileToken)
|
|
56
|
+
return false;
|
|
57
|
+
if (kind === ts.SyntaxKind.SingleLineCommentTrivia || kind === ts.SyntaxKind.MultiLineCommentTrivia)
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Classify a single statement. Returns null if the migrator can emit it,
|
|
62
|
+
* otherwise a kebab-case reason. Recurses through if/try branches. */
|
|
63
|
+
function classifyStmt(stmt, sf) {
|
|
64
|
+
if (ts.isVariableStatement(stmt)) {
|
|
65
|
+
const flags = stmt.declarationList.flags;
|
|
66
|
+
if (!(flags & ts.NodeFlags.Const))
|
|
67
|
+
return 'var-non-const';
|
|
68
|
+
const decls = stmt.declarationList.declarations;
|
|
69
|
+
if (decls.length !== 1)
|
|
70
|
+
return 'var-multi-decl';
|
|
71
|
+
const decl = decls[0];
|
|
72
|
+
if (!ts.isIdentifier(decl.name))
|
|
73
|
+
return 'var-destructure';
|
|
74
|
+
if (!decl.initializer)
|
|
75
|
+
return 'var-no-init';
|
|
76
|
+
if (decl.type)
|
|
77
|
+
return 'var-typed';
|
|
78
|
+
if (!isValidKernExpression(decl.initializer.getText(sf)))
|
|
79
|
+
return 'var-bad-expr';
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (ts.isReturnStatement(stmt)) {
|
|
83
|
+
if (!stmt.expression)
|
|
84
|
+
return null;
|
|
85
|
+
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
86
|
+
return 'return-bad-expr';
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (ts.isThrowStatement(stmt)) {
|
|
90
|
+
if (!stmt.expression)
|
|
91
|
+
return 'throw-no-expr';
|
|
92
|
+
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
93
|
+
return 'throw-bad-expr';
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (ts.isIfStatement(stmt)) {
|
|
97
|
+
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
98
|
+
return 'if-bad-cond';
|
|
99
|
+
const thenReason = classifyBranch(stmt.thenStatement, sf);
|
|
100
|
+
if (thenReason !== null)
|
|
101
|
+
return thenReason;
|
|
102
|
+
if (stmt.elseStatement) {
|
|
103
|
+
// `else if (…)` is a nested IfStatement here. The migrator's mapIf
|
|
104
|
+
// recurses for it, and the body emitters (TS + Python) collapse the
|
|
105
|
+
// resulting `else > if` shape back to `else if` / `elif` (commit
|
|
106
|
+
// 88c06dcc on dev). classifyBranch handles the nested IfStatement
|
|
107
|
+
// by re-entering classifyStmt, so the recursion is automatic.
|
|
108
|
+
const elseReason = classifyBranch(stmt.elseStatement, sf);
|
|
109
|
+
if (elseReason !== null)
|
|
110
|
+
return elseReason;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (ts.isTryStatement(stmt)) {
|
|
115
|
+
if (!stmt.catchClause)
|
|
116
|
+
return 'try-no-catch';
|
|
117
|
+
if (stmt.finallyBlock)
|
|
118
|
+
return 'try-finally';
|
|
119
|
+
const cc = stmt.catchClause;
|
|
120
|
+
if (cc.variableDeclaration && !ts.isIdentifier(cc.variableDeclaration.name))
|
|
121
|
+
return 'try-destruct-catch';
|
|
122
|
+
const tryReason = classifyBranch(stmt.tryBlock, sf);
|
|
123
|
+
if (tryReason !== null)
|
|
124
|
+
return tryReason;
|
|
125
|
+
return classifyBranch(cc.block, sf);
|
|
126
|
+
}
|
|
127
|
+
if (ts.isExpressionStatement(stmt)) {
|
|
128
|
+
// Slice α-1: ExpressionStatement → `do value="…"`. Reject mutation
|
|
129
|
+
// (assignments, ++, --) here so the classifier matches what the migrator
|
|
130
|
+
// emits — the migrator has the same defensive guards.
|
|
131
|
+
if (ts.isBinaryExpression(stmt.expression)) {
|
|
132
|
+
const op = stmt.expression.operatorToken.kind;
|
|
133
|
+
if (op >= ts.SyntaxKind.FirstAssignment && op <= ts.SyntaxKind.LastAssignment)
|
|
134
|
+
return 'expr-stmt-assignment';
|
|
135
|
+
}
|
|
136
|
+
if (ts.isPostfixUnaryExpression(stmt.expression) || ts.isPrefixUnaryExpression(stmt.expression)) {
|
|
137
|
+
const op = stmt.expression.operator;
|
|
138
|
+
if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken)
|
|
139
|
+
return 'expr-stmt-mutation';
|
|
140
|
+
}
|
|
141
|
+
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
142
|
+
return 'expr-stmt-bad-expr';
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (ts.isForStatement(stmt) || ts.isForOfStatement(stmt) || ts.isForInStatement(stmt))
|
|
146
|
+
return 'for-stmt';
|
|
147
|
+
if (ts.isWhileStatement(stmt) || ts.isDoStatement(stmt))
|
|
148
|
+
return 'while-do-stmt';
|
|
149
|
+
if (ts.isSwitchStatement(stmt))
|
|
150
|
+
return 'switch-stmt';
|
|
151
|
+
if (ts.isBlock(stmt))
|
|
152
|
+
return 'bare-block';
|
|
153
|
+
// Fallback — the TS SyntaxKind name surfaces in diagnostics so users have
|
|
154
|
+
// a starting point when they hit something exotic (label, with, debugger).
|
|
155
|
+
return `unsupported-stmt-${ts.SyntaxKind[stmt.kind]}`;
|
|
156
|
+
}
|
|
157
|
+
function classifyBranch(node, sf) {
|
|
158
|
+
const stmts = ts.isBlock(node) ? Array.from(node.statements) : [node];
|
|
159
|
+
for (const s of stmts) {
|
|
160
|
+
const r = classifyStmt(s, sf);
|
|
161
|
+
if (r !== null)
|
|
162
|
+
return r;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/** Classify a raw `<<<…>>>` handler body — the AST-aware replacement for
|
|
167
|
+
* the slice 5a regex pass. Returns `eligible: true` only if every top-level
|
|
168
|
+
* statement (and every nested if/try branch) maps to a body-statement form
|
|
169
|
+
* the migrator can emit. */
|
|
170
|
+
export function classifyHandlerBodyAst(rawBody) {
|
|
171
|
+
const trimmed = rawBody.trim();
|
|
172
|
+
if (trimmed === '')
|
|
173
|
+
return { eligible: true, reason: 'empty' };
|
|
174
|
+
if (hasComments(rawBody))
|
|
175
|
+
return { eligible: false, reason: 'comments-present' };
|
|
176
|
+
const sf = ts.createSourceFile('__handler.ts', rawBody, ts.ScriptTarget.Latest, true);
|
|
177
|
+
// ts.SourceFile carries `parseDiagnostics` despite not exposing it on the
|
|
178
|
+
// public type — the migrator reads it the same way.
|
|
179
|
+
const diags = sf.parseDiagnostics;
|
|
180
|
+
if (diags && diags.length > 0)
|
|
181
|
+
return { eligible: false, reason: 'ts-parse-error' };
|
|
182
|
+
for (const stmt of sf.statements) {
|
|
183
|
+
const r = classifyStmt(stmt, sf);
|
|
184
|
+
if (r !== null)
|
|
185
|
+
return { eligible: false, reason: r };
|
|
186
|
+
}
|
|
187
|
+
return { eligible: true, reason: 'ok' };
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=native-eligibility-ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-eligibility-ast.js","sourceRoot":"","sources":["../src/native-eligibility-ast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;yCAoByC;AAEzC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAYzD;;;;;;;;kBAQkB;AAClB,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,CAAC;QACH,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;iBAKiB;AACjB,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/E,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1B,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,cAAc;YAAE,OAAO,KAAK,CAAC;QACxD,IAAI,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,uBAAuB,IAAI,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,sBAAsB;YAAE,OAAO,IAAI,CAAC;IACnH,CAAC;AACH,CAAC;AAED;uEACuE;AACvE,SAAS,YAAY,CAAC,IAAkB,EAAE,EAAiB;IACzD,IAAI,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QACzC,IAAI,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,OAAO,eAAe,CAAC;QAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC;QAChD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,gBAAgB,CAAC;QAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,iBAAiB,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,aAAa,CAAC;QAC5C,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,WAAW,CAAC;QAClC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,cAAc,CAAC;QAChF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAClC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,iBAAiB,CAAC;QAClF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,eAAe,CAAC;QAC7C,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,gBAAgB,CAAC;QACjF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,aAAa,CAAC;QAC9E,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QAC1D,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,UAAU,CAAC;QAC3C,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,mEAAmE;YACnE,oEAAoE;YACpE,iEAAiE;YACjE,kEAAkE;YAClE,8DAA8D;YAC9D,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;YAC1D,IAAI,UAAU,KAAK,IAAI;gBAAE,OAAO,UAAU,CAAC;QAC7C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,cAAc,CAAC;QAC7C,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,aAAa,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;QAC5B,IAAI,EAAE,CAAC,mBAAmB,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAAE,OAAO,oBAAoB,CAAC;QACzG,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACpD,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,SAAS,CAAC;QACzC,OAAO,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,mEAAmE;QACnE,yEAAyE;QACzE,sDAAsD;QACtD,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC;YAC9C,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,eAAe,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc;gBAAE,OAAO,sBAAsB,CAAC;QAC/G,CAAC;QACD,IAAI,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAChG,MAAM,EAAE,GAAI,IAAI,CAAC,UAAmE,CAAC,QAAQ,CAAC;YAC9F,IAAI,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,aAAa,IAAI,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,eAAe;gBAAE,OAAO,oBAAoB,CAAC;QAC9G,CAAC;QACD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,oBAAoB,CAAC;QACrF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IACzG,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,eAAe,CAAC;IAChF,IAAI,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,CAAC;IACrD,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IAC1C,0EAA0E;IAC1E,2EAA2E;IAC3E,OAAO,oBAAoB,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACxD,CAAC;AAED,SAAS,cAAc,CAAC,IAAkB,EAAE,EAAiB;IAC3D,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;6BAG6B;AAC7B,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/D,IAAI,WAAW,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACjF,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtF,0EAA0E;IAC1E,oDAAoD;IACpD,MAAM,KAAK,GAAI,EAAwD,CAAC,gBAAgB,CAAC;IACzF,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACpF,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -1,33 +1,37 @@
|
|
|
1
|
-
/** Native KERN handler body eligibility classifier — slice 5a foundation
|
|
1
|
+
/** Native KERN handler body eligibility classifier — slice 5a foundation
|
|
2
|
+
* (slice α-3 update: delegates to the AST walker in
|
|
3
|
+
* `native-eligibility-ast.ts`; slice α-4: diagnostic surfaces at `warning`).
|
|
2
4
|
*
|
|
3
5
|
* Given a raw `<<<...>>>` handler body, determines whether it could compile
|
|
4
6
|
* under `lang="kern"` opt-in WITHOUT manual rewrite. Used by:
|
|
5
7
|
*
|
|
6
|
-
* 1. The compiler diagnostic layer (parser-validate-
|
|
7
|
-
* to surface `
|
|
8
|
-
* 2. The
|
|
8
|
+
* 1. The compiler diagnostic layer (`parser-validate-native-eligible.ts`)
|
|
9
|
+
* to surface `warning`-level `NATIVE_KERN_ELIGIBLE` hints suggesting opt-in.
|
|
10
|
+
* 2. The `kern migrate native-handlers` CLI (slice 5b) to bulk-convert.
|
|
9
11
|
* 3. Empirical scans of real-world repos (e.g. Agon-AI) to measure the
|
|
10
12
|
* practical adoption ceiling for native bodies.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Slice α-3: replaced the regex pre-screen with a TS-AST walk that mirrors
|
|
15
|
+
* the migrator's `mapStatement` rules. Eligibility now equals migrate-success
|
|
16
|
+
* by construction — the prerequisite for slice α-4's promotion of
|
|
17
|
+
* `NATIVE_KERN_ELIGIBLE` from `info` to `warning` without producing
|
|
18
|
+
* fix-or-suppress noise on bodies the migrator silently bails on.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* is still gated since slice 4d only lowered the object-literal form.
|
|
20
|
+
* The legacy regex disqualifier set lives at `LEGACY_NEG_PATTERNS` for
|
|
21
|
+
* consumers that need a fast pre-filter (no TS parse). The canonical
|
|
22
|
+
* classifier (`classifyHandlerBody`) uses the AST walker.
|
|
22
23
|
*/
|
|
23
24
|
/** Result of classifying a single handler body. */
|
|
24
25
|
export interface EligibilityResult {
|
|
25
26
|
/** True iff the body uses ONLY syntactic patterns that lang=kern supports. */
|
|
26
27
|
eligible: boolean;
|
|
27
|
-
/** When eligible: 'empty' (whitespace-only body) or '
|
|
28
|
-
* When ineligible:
|
|
29
|
-
* '
|
|
30
|
-
*
|
|
28
|
+
/** When eligible: `'empty'` (whitespace-only body) or `'ok'` (passed AST walk).
|
|
29
|
+
* When ineligible: a kebab-case slug naming the first blocking shape —
|
|
30
|
+
* e.g. `'var-destructure'`, `'try-finally'`, `'expr-stmt-mutation'`,
|
|
31
|
+
* `'comments-present'`, `'ts-parse-error'`. See
|
|
32
|
+
* `native-eligibility-ast.ts` for the full set. The legacy regex source
|
|
33
|
+
* (e.g. `'\\bfor\\s*\\('`) is no longer surfaced — older callers that
|
|
34
|
+
* switched on the regex string need to migrate to the new slugs. */
|
|
31
35
|
reason: string;
|
|
32
36
|
}
|
|
33
37
|
/** A raw `<<<…>>>` handler body extracted from a `.kern` source file,
|
|
@@ -48,7 +52,14 @@ export interface FileEligibilityReport {
|
|
|
48
52
|
* as the bodies returned by `extractRawBodies(content)`. */
|
|
49
53
|
bodies: Array<RawBody & EligibilityResult>;
|
|
50
54
|
}
|
|
51
|
-
/**
|
|
55
|
+
/** Slice α-3: legacy regex disqualifier set. Kept exported for fast
|
|
56
|
+
* pre-filtering (no TS parse) in tools that don't need precise reasons —
|
|
57
|
+
* e.g. histogram scanners that only want a coarse "ineligible" signal.
|
|
58
|
+
* The canonical classifier (`classifyHandlerBody`) no longer uses this set;
|
|
59
|
+
* it delegates to the AST walker in `native-eligibility-ast.ts`. */
|
|
60
|
+
export declare const LEGACY_NEG_PATTERNS: ReadonlyArray<RegExp>;
|
|
61
|
+
/** Classify a single raw body. Slice α-3: delegates to the AST walker so
|
|
62
|
+
* eligibility ≡ migrate-success by construction. */
|
|
52
63
|
export declare function classifyHandlerBody(rawBody: string): EligibilityResult;
|
|
53
64
|
/** Walk a `.kern` source file's text and pull out every `<<< … >>>` body,
|
|
54
65
|
* preserving line positions. Mirrors the behaviour of `parser-core.ts`
|
|
@@ -1,29 +1,33 @@
|
|
|
1
|
-
/** Native KERN handler body eligibility classifier — slice 5a foundation
|
|
1
|
+
/** Native KERN handler body eligibility classifier — slice 5a foundation
|
|
2
|
+
* (slice α-3 update: delegates to the AST walker in
|
|
3
|
+
* `native-eligibility-ast.ts`; slice α-4: diagnostic surfaces at `warning`).
|
|
2
4
|
*
|
|
3
5
|
* Given a raw `<<<...>>>` handler body, determines whether it could compile
|
|
4
6
|
* under `lang="kern"` opt-in WITHOUT manual rewrite. Used by:
|
|
5
7
|
*
|
|
6
|
-
* 1. The compiler diagnostic layer (parser-validate-
|
|
7
|
-
* to surface `
|
|
8
|
-
* 2. The
|
|
8
|
+
* 1. The compiler diagnostic layer (`parser-validate-native-eligible.ts`)
|
|
9
|
+
* to surface `warning`-level `NATIVE_KERN_ELIGIBLE` hints suggesting opt-in.
|
|
10
|
+
* 2. The `kern migrate native-handlers` CLI (slice 5b) to bulk-convert.
|
|
9
11
|
* 3. Empirical scans of real-world repos (e.g. Agon-AI) to measure the
|
|
10
12
|
* practical adoption ceiling for native bodies.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Slice α-3: replaced the regex pre-screen with a TS-AST walk that mirrors
|
|
15
|
+
* the migrator's `mapStatement` rules. Eligibility now equals migrate-success
|
|
16
|
+
* by construction — the prerequisite for slice α-4's promotion of
|
|
17
|
+
* `NATIVE_KERN_ELIGIBLE` from `info` to `warning` without producing
|
|
18
|
+
* fix-or-suppress noise on bodies the migrator silently bails on.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* is still gated since slice 4d only lowered the object-literal form.
|
|
20
|
+
* The legacy regex disqualifier set lives at `LEGACY_NEG_PATTERNS` for
|
|
21
|
+
* consumers that need a fast pre-filter (no TS parse). The canonical
|
|
22
|
+
* classifier (`classifyHandlerBody`) uses the AST walker.
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
|
|
24
|
+
import { classifyHandlerBodyAst } from './native-eligibility-ast.js';
|
|
25
|
+
/** Slice α-3: legacy regex disqualifier set. Kept exported for fast
|
|
26
|
+
* pre-filtering (no TS parse) in tools that don't need precise reasons —
|
|
27
|
+
* e.g. histogram scanners that only want a coarse "ineligible" signal.
|
|
28
|
+
* The canonical classifier (`classifyHandlerBody`) no longer uses this set;
|
|
29
|
+
* it delegates to the AST walker in `native-eligibility-ast.ts`. */
|
|
30
|
+
export const LEGACY_NEG_PATTERNS = [
|
|
27
31
|
/=>/,
|
|
28
32
|
/\bfunction\b/,
|
|
29
33
|
/\bclass\s+\w/,
|
|
@@ -73,16 +77,10 @@ const NEG_PATTERNS = [
|
|
|
73
77
|
/\(\s*\.{3}/,
|
|
74
78
|
/\/\w+\/[gimsy]*/,
|
|
75
79
|
];
|
|
76
|
-
/** Classify a single raw body.
|
|
80
|
+
/** Classify a single raw body. Slice α-3: delegates to the AST walker so
|
|
81
|
+
* eligibility ≡ migrate-success by construction. */
|
|
77
82
|
export function classifyHandlerBody(rawBody) {
|
|
78
|
-
|
|
79
|
-
if (trimmed === '')
|
|
80
|
-
return { eligible: true, reason: 'empty' };
|
|
81
|
-
for (const pat of NEG_PATTERNS) {
|
|
82
|
-
if (pat.test(rawBody))
|
|
83
|
-
return { eligible: false, reason: pat.source };
|
|
84
|
-
}
|
|
85
|
-
return { eligible: true, reason: 'no-disqualifier' };
|
|
83
|
+
return classifyHandlerBodyAst(rawBody);
|
|
86
84
|
}
|
|
87
85
|
/** Walk a `.kern` source file's text and pull out every `<<< … >>>` body,
|
|
88
86
|
* preserving line positions. Mirrors the behaviour of `parser-core.ts`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-eligibility.js","sourceRoot":"","sources":["../src/native-eligibility.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"native-eligibility.js","sourceRoot":"","sources":["../src/native-eligibility.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* spec, `none` is the canonical empty-value form in `lang=kern` bodies; `null` is
|
|
9
9
|
* retained for legacy/round-trip compatibility.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Slice 2c added arithmetic and comparisons; slice α-2 added ternary
|
|
12
|
+
* `a ? b : c`. Still NOT supported: indexing (`xs[0]`), bitwise ops,
|
|
13
|
+
* assignment — these would require shape changes the body emitter
|
|
14
|
+
* doesn't have, so the parser deliberately rejects them. */
|
|
13
15
|
import type { ValueIR } from './value-ir.js';
|
|
14
16
|
export type ExprTokenKind = 'ident' | 'num' | 'str' | 'tmplStart' | 'dot' | 'optDot' | 'nullish' | 'or' | 'and' | 'lparen' | 'rparen' | 'lbrace' | 'rbrace' | 'lbracket' | 'rbracket' | 'colon' | 'comma' | 'spread' | 'qmark' | 'eq' | 'neq' | 'strictEq' | 'strictNeq' | 'bang' | 'lt' | 'lte' | 'gt' | 'gte' | 'plus' | 'minus' | 'star' | 'slash' | 'percent' | 'kwNull' | 'kwUndef' | 'kwTrue' | 'kwFalse' | 'kwAwait' | 'kwNew' | 'eof';
|
|
15
17
|
export interface ExprToken {
|
|
@@ -8,8 +8,39 @@
|
|
|
8
8
|
* spec, `none` is the canonical empty-value form in `lang=kern` bodies; `null` is
|
|
9
9
|
* retained for legacy/round-trip compatibility.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Slice 2c added arithmetic and comparisons; slice α-2 added ternary
|
|
12
|
+
* `a ? b : c`. Still NOT supported: indexing (`xs[0]`), bitwise ops,
|
|
13
|
+
* assignment — these would require shape changes the body emitter
|
|
14
|
+
* doesn't have, so the parser deliberately rejects them. */
|
|
15
|
+
/** Slice α-2: token kinds that can start an expression. Used by parsePostfix
|
|
16
|
+
* and the await branch of parseUnary to disambiguate postfix `?`
|
|
17
|
+
* (propagation) from ternary `?` (which is always followed by an expression).
|
|
18
|
+
* If the token AFTER `?` is in this set, the `?` belongs to the outer
|
|
19
|
+
* ternary (parseConditional); otherwise, it's propagation.
|
|
20
|
+
*
|
|
21
|
+
* `plus` is not in the set — KERN's parseUnary doesn't accept unary `+`.
|
|
22
|
+
* `kwNew` isn't in the set — `new` is matched by parseUnary as an `ident`
|
|
23
|
+
* token with value `'new'`. */
|
|
24
|
+
const EXPR_START_KINDS = new Set([
|
|
25
|
+
'ident',
|
|
26
|
+
'num',
|
|
27
|
+
'str',
|
|
28
|
+
'tmplStart',
|
|
29
|
+
'kwTrue',
|
|
30
|
+
'kwFalse',
|
|
31
|
+
'kwNull',
|
|
32
|
+
'kwUndef',
|
|
33
|
+
'kwAwait',
|
|
34
|
+
'lparen',
|
|
35
|
+
'lbrace',
|
|
36
|
+
'lbracket',
|
|
37
|
+
'spread',
|
|
38
|
+
'bang',
|
|
39
|
+
'minus',
|
|
40
|
+
]);
|
|
41
|
+
function isExprStartKind(kind) {
|
|
42
|
+
return EXPR_START_KINDS.has(kind);
|
|
43
|
+
}
|
|
13
44
|
const KEYWORDS = {
|
|
14
45
|
null: 'kwNull',
|
|
15
46
|
none: 'kwNull',
|
|
@@ -356,13 +387,37 @@ class Parser {
|
|
|
356
387
|
return this.advance();
|
|
357
388
|
}
|
|
358
389
|
parse() {
|
|
359
|
-
const result = this.
|
|
390
|
+
const result = this.parseConditional();
|
|
360
391
|
if (this.peek().kind !== 'eof') {
|
|
361
392
|
const t = this.peek();
|
|
362
393
|
throw new Error(`Unexpected token ${t.kind} ('${t.value}') at column ${t.pos + 1}`);
|
|
363
394
|
}
|
|
364
395
|
return result;
|
|
365
396
|
}
|
|
397
|
+
// Slice α-2: ternary `test ? consequent : alternate`. Right-associative —
|
|
398
|
+
// `a ? b : c ? d : e` parses as `a ? b : (c ? d : e)`. Lower precedence
|
|
399
|
+
// than `??`/`||`/`&&`/binary ops, so it wraps `parseNullish`.
|
|
400
|
+
//
|
|
401
|
+
// Disambiguation with propagation `?`: parsePostfix and the await branch
|
|
402
|
+
// of parseUnary both consume a postfix `?` when the next token is NOT an
|
|
403
|
+
// expression-start. So when this method sees `?` at the top, it's
|
|
404
|
+
// unambiguously a ternary `?` (the next token MUST be an expression-start
|
|
405
|
+
// — otherwise parsePostfix would have consumed the `?` as propagation).
|
|
406
|
+
parseConditional() {
|
|
407
|
+
const test = this.parseNullish();
|
|
408
|
+
if (this.peek().kind !== 'qmark')
|
|
409
|
+
return test;
|
|
410
|
+
if (!isExprStartKind(this.peek(1).kind)) {
|
|
411
|
+
// Defensive: shouldn't happen given the parsePostfix lookahead rule.
|
|
412
|
+
const t = this.peek();
|
|
413
|
+
throw new Error(`Unexpected '?' at column ${t.pos + 1}. Postfix '?' (propagation) is recognized inside expressions; ternary '?' must be followed by an expression then ':'.`);
|
|
414
|
+
}
|
|
415
|
+
this.advance(); // consume `?`
|
|
416
|
+
const consequent = this.parseConditional();
|
|
417
|
+
this.expect('colon');
|
|
418
|
+
const alternate = this.parseConditional();
|
|
419
|
+
return { kind: 'conditional', test, consequent, alternate };
|
|
420
|
+
}
|
|
366
421
|
parseNullish() {
|
|
367
422
|
let left = this.parseOr();
|
|
368
423
|
while (this.peek().kind === 'nullish') {
|
|
@@ -463,7 +518,10 @@ class Parser {
|
|
|
463
518
|
// instead of the semantically-correct `propagate(await(call()))`.
|
|
464
519
|
const argument = this.parseCall();
|
|
465
520
|
const awaited = { kind: 'await', argument };
|
|
466
|
-
|
|
521
|
+
// Slice α-2: only consume postfix `?` as propagation if the token
|
|
522
|
+
// after it is NOT an expression-start. Otherwise leave it for the
|
|
523
|
+
// outer parseConditional (ternary).
|
|
524
|
+
if (this.peek().kind === 'qmark' && !isExprStartKind(this.peek(1).kind)) {
|
|
467
525
|
this.advance();
|
|
468
526
|
return { kind: 'propagate', argument: awaited, op: '?' };
|
|
469
527
|
}
|
|
@@ -481,7 +539,10 @@ class Parser {
|
|
|
481
539
|
}
|
|
482
540
|
parsePostfix() {
|
|
483
541
|
const node = this.parseCall();
|
|
484
|
-
if
|
|
542
|
+
// Slice α-2: only consume postfix `?` as propagation if the token after
|
|
543
|
+
// it is NOT an expression-start. Otherwise leave it for the outer
|
|
544
|
+
// parseConditional (ternary).
|
|
545
|
+
if (this.peek().kind === 'qmark' && !isExprStartKind(this.peek(1).kind)) {
|
|
485
546
|
this.advance();
|
|
486
547
|
return { kind: 'propagate', argument: node, op: '?' };
|
|
487
548
|
}
|
|
@@ -526,10 +587,10 @@ class Parser {
|
|
|
526
587
|
const args = [];
|
|
527
588
|
if (this.peek().kind === 'rparen')
|
|
528
589
|
return args;
|
|
529
|
-
args.push(this.
|
|
590
|
+
args.push(this.parseConditional());
|
|
530
591
|
while (this.peek().kind === 'comma') {
|
|
531
592
|
this.advance();
|
|
532
|
-
args.push(this.
|
|
593
|
+
args.push(this.parseConditional());
|
|
533
594
|
}
|
|
534
595
|
return args;
|
|
535
596
|
}
|
|
@@ -566,7 +627,7 @@ class Parser {
|
|
|
566
627
|
return { kind: 'undefLit' };
|
|
567
628
|
case 'lparen': {
|
|
568
629
|
this.advance();
|
|
569
|
-
const inner = this.
|
|
630
|
+
const inner = this.parseConditional();
|
|
570
631
|
this.expect('rparen');
|
|
571
632
|
return inner;
|
|
572
633
|
}
|
|
@@ -595,7 +656,7 @@ class Parser {
|
|
|
595
656
|
const keyTok = this.peek();
|
|
596
657
|
if (keyTok.kind === 'spread') {
|
|
597
658
|
this.advance();
|
|
598
|
-
const argument = this.
|
|
659
|
+
const argument = this.parseConditional();
|
|
599
660
|
entries.push({ kind: 'spread', argument });
|
|
600
661
|
}
|
|
601
662
|
else {
|
|
@@ -623,7 +684,7 @@ class Parser {
|
|
|
623
684
|
}
|
|
624
685
|
else {
|
|
625
686
|
this.expect('colon');
|
|
626
|
-
const value = this.
|
|
687
|
+
const value = this.parseConditional();
|
|
627
688
|
entries.push({ key, value });
|
|
628
689
|
}
|
|
629
690
|
}
|
|
@@ -647,7 +708,7 @@ class Parser {
|
|
|
647
708
|
return { kind: 'arrayLit', items };
|
|
648
709
|
}
|
|
649
710
|
while (true) {
|
|
650
|
-
items.push(this.
|
|
711
|
+
items.push(this.parseConditional());
|
|
651
712
|
if (this.peek().kind === 'comma') {
|
|
652
713
|
this.advance();
|
|
653
714
|
if (this.peek().kind === 'rbracket')
|