@kernlang/core 3.4.6 → 3.4.7-canary.100.1.95f0eb11
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/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 +194 -12
- 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 +381 -0
- 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/schema.js +120 -12
- package/dist/schema.js.map +1 -1
- package/dist/semantic-validator.js +16 -10
- 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
package/dist/schema.js
CHANGED
|
@@ -30,6 +30,14 @@ export const NODE_SCHEMAS = {
|
|
|
30
30
|
code: { kind: 'rawBlock' },
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
|
+
comment: {
|
|
34
|
+
description: 'Native body comment preserved during raw-handler migration. `raw=` may keep a host-style single-line comment; `text=` emits a target-native line comment.',
|
|
35
|
+
example: 'comment text="explain the early return"',
|
|
36
|
+
props: {
|
|
37
|
+
raw: { kind: 'string' },
|
|
38
|
+
text: { kind: 'string' },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
33
41
|
type: {
|
|
34
42
|
description: 'TypeScript type alias — union of string literals, or alias to another type (including tuple types like [string, number]). Use generics="<T>" for parameterised aliases.',
|
|
35
43
|
example: 'type name=Status values="active|inactive|banned"',
|
|
@@ -211,7 +219,16 @@ export const NODE_SCHEMAS = {
|
|
|
211
219
|
// Validated by parser-validate-effects.ts; see docs/language/effects-pure-spec.md.
|
|
212
220
|
effects: { kind: 'string' },
|
|
213
221
|
},
|
|
214
|
-
allowedChildren: ['handler', 'signal', 'cleanup', 'overload', 'param'],
|
|
222
|
+
allowedChildren: ['decorator', 'handler', 'signal', 'cleanup', 'overload', 'param'],
|
|
223
|
+
},
|
|
224
|
+
decorator: {
|
|
225
|
+
description: 'KERN decorator metadata attached to a declaration',
|
|
226
|
+
example: '@http.get("/users/:id")\nfn getUser(id: string): User',
|
|
227
|
+
props: {
|
|
228
|
+
name: { required: true, kind: 'string' },
|
|
229
|
+
args: { kind: 'string' },
|
|
230
|
+
},
|
|
231
|
+
allowedChildren: [],
|
|
215
232
|
},
|
|
216
233
|
machine: {
|
|
217
234
|
description: 'State machine with states and guarded transitions — 12 lines of KERN generates 140+ lines of TypeScript',
|
|
@@ -234,6 +251,15 @@ export const NODE_SCHEMAS = {
|
|
|
234
251
|
debounce: { kind: 'number' },
|
|
235
252
|
},
|
|
236
253
|
},
|
|
254
|
+
cell: {
|
|
255
|
+
description: 'Body-statement reactive state cell — KERN-canonical state primitive inside a `handler lang="kern"` body. Read the cell via its `name`; update via `set name=X to=...` or `assign target=X value=...`. Target lowering: TS+React → `const [X, setX] = useState<T>(initial);` (writes auto-rewrite to setter calls). Python+FastAPI → plain mutable `X = initial` (FastAPI per-request handlers don\'t need reactivity; each request resets). Must be a direct child of `handler lang="kern"` — placing it inside `if`/`for`/`while`/`try` violates React\'s Rules of Hooks.',
|
|
256
|
+
example: 'fn name=Counter\n handler lang="kern"\n cell name=count initial=0 type=number\n set name=count to="count + 1"',
|
|
257
|
+
props: {
|
|
258
|
+
name: { required: true, kind: 'identifier' },
|
|
259
|
+
initial: { kind: 'expression' },
|
|
260
|
+
type: { kind: 'typeAnnotation' },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
237
263
|
animation: {
|
|
238
264
|
description: 'Interval-driven state update — generates useEffect with setInterval and auto-cleanup',
|
|
239
265
|
example: 'animation name=frame interval=100 update="(prev) => (prev + 1) % 4"',
|
|
@@ -509,10 +535,12 @@ export const NODE_SCHEMAS = {
|
|
|
509
535
|
'handler',
|
|
510
536
|
'catch',
|
|
511
537
|
'finally',
|
|
538
|
+
'comment',
|
|
512
539
|
'let',
|
|
513
540
|
'assign',
|
|
514
541
|
'destructure',
|
|
515
542
|
'do',
|
|
543
|
+
'fmt',
|
|
516
544
|
'return',
|
|
517
545
|
'if',
|
|
518
546
|
'else',
|
|
@@ -543,10 +571,12 @@ export const NODE_SCHEMAS = {
|
|
|
543
571
|
},
|
|
544
572
|
allowedChildren: [
|
|
545
573
|
'handler',
|
|
574
|
+
'comment',
|
|
546
575
|
'let',
|
|
547
576
|
'assign',
|
|
548
577
|
'destructure',
|
|
549
578
|
'do',
|
|
579
|
+
'fmt',
|
|
550
580
|
'return',
|
|
551
581
|
'if',
|
|
552
582
|
'else',
|
|
@@ -565,9 +595,11 @@ export const NODE_SCHEMAS = {
|
|
|
565
595
|
example: 'try\n let name=conn value="acquire()"\n do value="conn.use()"\nfinally\n do value="conn.release()"',
|
|
566
596
|
props: {},
|
|
567
597
|
allowedChildren: [
|
|
598
|
+
'comment',
|
|
568
599
|
'let',
|
|
569
600
|
'assign',
|
|
570
601
|
'do',
|
|
602
|
+
'fmt',
|
|
571
603
|
'return',
|
|
572
604
|
'if',
|
|
573
605
|
'else',
|
|
@@ -1159,14 +1191,14 @@ export const NODE_SCHEMAS = {
|
|
|
1159
1191
|
},
|
|
1160
1192
|
},
|
|
1161
1193
|
each: {
|
|
1162
|
-
description: 'Iteration — renders children for each item in a collection. Inside a render block emits `items.map(...)` with auto-key; elsewhere emits `for...of`. `let` children become iteration-scoped `const` bindings inside the callback (hook-safe, unlike `derive`).
|
|
1194
|
+
description: 'Iteration — renders children for each item in a collection. Inside a render block emits `items.map(...)` with auto-key; elsewhere emits `for...of`. `let` children become iteration-scoped `const` bindings inside the callback (hook-safe, unlike `derive`). Body-statement forms: (1) `each name=x in=xs` → `for (const x of xs)`; (2) `each name=x index=i in=xs` → `for (const [i, x] of xs.entries())`; (3) `each pairKey=k pairValue=v in=map` → `for (const [k, v] of map)` (TS) / `for k, v in map.items():` (Python). Use `entries=true` with keyed-entry modes for object/dict entries: `pairKey`/`pairValue` lowers to `Object.entries(obj)` / `obj.items()`, `entryKey` lowers to `Object.entries(obj)` / `obj.keys()`, and `entryValue` lowers to `Object.entries(obj)` / `obj.values()`. Add `type=` to preserve a TS item binding annotation in body-statement form (`each name=x type=User in=users` → `for (const x: User of users)`). Add `await=true` for async iterables (`for await` / `async for`); it cannot be combined with `index=` and is rejected inside render JSX. Async pair-mode expects an async iterable of pairs, not a mapping (`async for k, v in stream`). In keyed-entry modes `name` is optional. `key=` (render-only) is the React render key, distinct from `pairKey=` / `entryKey=`.',
|
|
1163
1195
|
example: 'each name=f in=files index=i key="f.path"\n let name=isSel expr="focused && i === selIdx"\n handler <<<\n <Text bold={isSel}>{f.path}</Text>\n >>>',
|
|
1164
1196
|
props: {
|
|
1165
1197
|
// `name` is required schema-side for the array-iteration forms; the
|
|
1166
|
-
//
|
|
1198
|
+
// keyed-entry (`pairKey` + `pairValue`, `entryKey`, `entryValue`) forms relax this via a
|
|
1167
1199
|
// conditional-required exemption inside `checkRequiredProps` in this
|
|
1168
|
-
// same file. (The `each-
|
|
1169
|
-
// semantic-validator.ts is unrelated — it just blocks
|
|
1200
|
+
// same file. (The `each-keyed-mode-body-stmt-only` semantic rule in
|
|
1201
|
+
// semantic-validator.ts is unrelated — it just blocks keyed modes in
|
|
1170
1202
|
// render/group ancestor scope.)
|
|
1171
1203
|
name: { required: true, kind: 'identifier' },
|
|
1172
1204
|
in: { required: true, kind: 'rawExpr' },
|
|
@@ -1175,6 +1207,9 @@ export const NODE_SCHEMAS = {
|
|
|
1175
1207
|
type: { kind: 'typeAnnotation' },
|
|
1176
1208
|
pairKey: { kind: 'identifier' },
|
|
1177
1209
|
pairValue: { kind: 'identifier' },
|
|
1210
|
+
entryKey: { kind: 'identifier' },
|
|
1211
|
+
entryValue: { kind: 'identifier' },
|
|
1212
|
+
entries: { kind: 'boolean' },
|
|
1178
1213
|
await: { kind: 'boolean' },
|
|
1179
1214
|
},
|
|
1180
1215
|
// Intentionally unrestricted — statement-form `each` composes with `derive`,
|
|
@@ -1435,16 +1470,22 @@ export const NODE_SCHEMAS = {
|
|
|
1435
1470
|
props: {
|
|
1436
1471
|
code: { kind: 'rawBlock' },
|
|
1437
1472
|
lang: { kind: 'string' },
|
|
1473
|
+
reason: { kind: 'string' },
|
|
1474
|
+
review: { kind: 'identifier' },
|
|
1438
1475
|
},
|
|
1439
1476
|
// Body-statement children apply when `lang="kern"`. Outside that opt-in,
|
|
1440
1477
|
// body statements are rejected by validateBodyStatements (the schema list
|
|
1441
1478
|
// is intentionally permissive so the validator can produce a clearer
|
|
1442
1479
|
// context-aware error).
|
|
1443
1480
|
allowedChildren: [
|
|
1481
|
+
'cell',
|
|
1482
|
+
'set',
|
|
1483
|
+
'comment',
|
|
1444
1484
|
'let',
|
|
1445
1485
|
'assign',
|
|
1446
1486
|
'destructure',
|
|
1447
1487
|
'do',
|
|
1488
|
+
'fmt',
|
|
1448
1489
|
'return',
|
|
1449
1490
|
'if',
|
|
1450
1491
|
'else',
|
|
@@ -1514,10 +1555,12 @@ export const NODE_SCHEMAS = {
|
|
|
1514
1555
|
cond: { required: true, kind: 'expression' },
|
|
1515
1556
|
},
|
|
1516
1557
|
allowedChildren: [
|
|
1558
|
+
'comment',
|
|
1517
1559
|
'let',
|
|
1518
1560
|
'assign',
|
|
1519
1561
|
'destructure',
|
|
1520
1562
|
'do',
|
|
1563
|
+
'fmt',
|
|
1521
1564
|
'return',
|
|
1522
1565
|
'if',
|
|
1523
1566
|
'else',
|
|
@@ -1542,10 +1585,12 @@ export const NODE_SCHEMAS = {
|
|
|
1542
1585
|
step: { kind: 'expression' },
|
|
1543
1586
|
},
|
|
1544
1587
|
allowedChildren: [
|
|
1588
|
+
'comment',
|
|
1545
1589
|
'let',
|
|
1546
1590
|
'assign',
|
|
1547
1591
|
'destructure',
|
|
1548
1592
|
'do',
|
|
1593
|
+
'fmt',
|
|
1549
1594
|
'return',
|
|
1550
1595
|
'if',
|
|
1551
1596
|
'else',
|
|
@@ -2626,8 +2671,9 @@ function checkRequiredProps(node, schema, violations, parent) {
|
|
|
2626
2671
|
if (node.type === 'import' && propName === 'from' && parent?.type === 'extern') {
|
|
2627
2672
|
continue;
|
|
2628
2673
|
}
|
|
2629
|
-
//
|
|
2630
|
-
// `pairKey`
|
|
2674
|
+
// keyed-entry modes (2026-05-12): `name` becomes optional when either
|
|
2675
|
+
// pair-mode (`pairKey` + `pairValue`) or one-binding entry-mode
|
|
2676
|
+
// (`entryKey` / `entryValue`) is present.
|
|
2631
2677
|
// The schema can't express conditional-required, so suppress the
|
|
2632
2678
|
// `name`-required violation here under that exact shape. Other props
|
|
2633
2679
|
// (notably `in=`) still error if missing.
|
|
@@ -2643,6 +2689,12 @@ function checkRequiredProps(node, schema, violations, parent) {
|
|
|
2643
2689
|
props.pairValue.length > 0) {
|
|
2644
2690
|
continue;
|
|
2645
2691
|
}
|
|
2692
|
+
if (node.type === 'each' &&
|
|
2693
|
+
propName === 'name' &&
|
|
2694
|
+
((typeof props.entryKey === 'string' && props.entryKey.length > 0) ||
|
|
2695
|
+
(typeof props.entryValue === 'string' && props.entryValue.length > 0))) {
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2646
2698
|
violations.push({
|
|
2647
2699
|
nodeType: node.type,
|
|
2648
2700
|
message: `'${node.type}' requires prop '${propName}'`,
|
|
@@ -2684,6 +2736,17 @@ function checkCrossProps(node, violations, parent) {
|
|
|
2684
2736
|
});
|
|
2685
2737
|
}
|
|
2686
2738
|
}
|
|
2739
|
+
if (node.type === 'handler' && ('reason' in props || 'review' in props)) {
|
|
2740
|
+
const lang = typeof props.lang === 'string' ? props.lang.trim().toLowerCase() : '';
|
|
2741
|
+
if (lang === '' || lang === 'kern') {
|
|
2742
|
+
violations.push({
|
|
2743
|
+
nodeType: 'handler',
|
|
2744
|
+
message: '`handler reason=`/`review=` metadata requires an explicit non-kern `lang=` foreign boundary',
|
|
2745
|
+
line: node.loc?.line,
|
|
2746
|
+
col: node.loc?.col,
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2687
2750
|
if (node.type === 'import' && parent?.type === 'extern') {
|
|
2688
2751
|
for (const boundaryProp of ['package', 'registry', 'target']) {
|
|
2689
2752
|
if (boundaryProp in props) {
|
|
@@ -3007,7 +3070,7 @@ function checkCrossProps(node, violations, parent) {
|
|
|
3007
3070
|
}
|
|
3008
3071
|
}
|
|
3009
3072
|
if (node.type === 'each') {
|
|
3010
|
-
//
|
|
3073
|
+
// keyed-entry modes (2026-05-12): the Map / iterable-of-pairs form uses
|
|
3011
3074
|
// `pairKey=` + `pairValue=` for `for (const [k, v] of m)`. Three rules:
|
|
3012
3075
|
// 1. pairKey and pairValue come as a pair — neither alone is meaningful.
|
|
3013
3076
|
// 2. pair-mode is incompatible with `index=` (entries-with-index form).
|
|
@@ -3024,9 +3087,14 @@ function checkCrossProps(node, violations, parent) {
|
|
|
3024
3087
|
const isPairProp = (raw) => typeof raw === 'string' && raw.length > 0;
|
|
3025
3088
|
const hasPairKey = isPairProp(props.pairKey);
|
|
3026
3089
|
const hasPairValue = isPairProp(props.pairValue);
|
|
3090
|
+
const hasEntryKey = isPairProp(props.entryKey);
|
|
3091
|
+
const hasEntryValue = isPairProp(props.entryValue);
|
|
3092
|
+
const hasFullPair = hasPairKey && hasPairValue;
|
|
3093
|
+
const entryBindingCount = Number(hasFullPair) + Number(hasEntryKey) + Number(hasEntryValue);
|
|
3027
3094
|
const hasIndex = isPairProp(props.index);
|
|
3028
3095
|
const hasType = isPairProp(props.type);
|
|
3029
3096
|
const hasAwait = props.await === true || props.await === 'true';
|
|
3097
|
+
const hasEntries = props.entries === true || props.entries === 'true';
|
|
3030
3098
|
if (hasPairKey !== hasPairValue) {
|
|
3031
3099
|
violations.push({
|
|
3032
3100
|
nodeType: 'each',
|
|
@@ -3035,18 +3103,26 @@ function checkCrossProps(node, violations, parent) {
|
|
|
3035
3103
|
col: node.loc?.col,
|
|
3036
3104
|
});
|
|
3037
3105
|
}
|
|
3038
|
-
if (
|
|
3106
|
+
if (entryBindingCount > 1) {
|
|
3039
3107
|
violations.push({
|
|
3040
3108
|
nodeType: 'each',
|
|
3041
|
-
message: "'each'
|
|
3109
|
+
message: "'each' keyed-entry modes are mutually exclusive: use pairKey+pairValue, entryKey, or entryValue",
|
|
3042
3110
|
line: node.loc?.line,
|
|
3043
3111
|
col: node.loc?.col,
|
|
3044
3112
|
});
|
|
3045
3113
|
}
|
|
3046
|
-
if (
|
|
3114
|
+
if ((hasFullPair || hasEntryKey || hasEntryValue) && hasIndex) {
|
|
3047
3115
|
violations.push({
|
|
3048
3116
|
nodeType: 'each',
|
|
3049
|
-
message: "'each'
|
|
3117
|
+
message: "'each' keyed-entry modes are mutually exclusive with 'index='",
|
|
3118
|
+
line: node.loc?.line,
|
|
3119
|
+
col: node.loc?.col,
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
if ((hasFullPair || hasEntryKey || hasEntryValue) && hasType) {
|
|
3123
|
+
violations.push({
|
|
3124
|
+
nodeType: 'each',
|
|
3125
|
+
message: "'each' keyed-entry modes are mutually exclusive with 'type='",
|
|
3050
3126
|
line: node.loc?.line,
|
|
3051
3127
|
col: node.loc?.col,
|
|
3052
3128
|
});
|
|
@@ -3059,6 +3135,38 @@ function checkCrossProps(node, violations, parent) {
|
|
|
3059
3135
|
col: node.loc?.col,
|
|
3060
3136
|
});
|
|
3061
3137
|
}
|
|
3138
|
+
if (hasAwait && (hasEntryKey || hasEntryValue)) {
|
|
3139
|
+
violations.push({
|
|
3140
|
+
nodeType: 'each',
|
|
3141
|
+
message: "'each await=true' is mutually exclusive with one-binding entry modes ('entryKey='/'entryValue=')",
|
|
3142
|
+
line: node.loc?.line,
|
|
3143
|
+
col: node.loc?.col,
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
if (hasEntries && !hasFullPair && !hasEntryKey && !hasEntryValue) {
|
|
3147
|
+
violations.push({
|
|
3148
|
+
nodeType: 'each',
|
|
3149
|
+
message: "'each entries=true' requires a keyed-entry mode ('pairKey'+'pairValue', 'entryKey', or 'entryValue')",
|
|
3150
|
+
line: node.loc?.line,
|
|
3151
|
+
col: node.loc?.col,
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
if (!hasEntries && (hasEntryKey || hasEntryValue)) {
|
|
3155
|
+
violations.push({
|
|
3156
|
+
nodeType: 'each',
|
|
3157
|
+
message: "'each entryKey='/entryValue=' requires entries=true for object/dict entry semantics",
|
|
3158
|
+
line: node.loc?.line,
|
|
3159
|
+
col: node.loc?.col,
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
if (hasEntries && hasAwait) {
|
|
3163
|
+
violations.push({
|
|
3164
|
+
nodeType: 'each',
|
|
3165
|
+
message: "'each entries=true' is mutually exclusive with 'await=true'",
|
|
3166
|
+
line: node.loc?.line,
|
|
3167
|
+
col: node.loc?.col,
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3062
3170
|
// Pair-mode relaxes `name` requirement — handled by checkRequiredProps
|
|
3063
3171
|
// (suppresses the `name`-required violation when pairKey+pairValue are
|
|
3064
3172
|
// both present).
|