@vesk/compiler 0.2.55 → 0.2.56
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/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +150 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAsBP,MAAM,uBAAuB,CAAC;AA2T/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;
|
|
1
|
+
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAsBP,MAAM,uBAAuB,CAAC;AA2T/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AA4lDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAqV9E"}
|
package/dist/ir-generator.js
CHANGED
|
@@ -1959,6 +1959,154 @@ function idxOfQuote(s, from) {
|
|
|
1959
1959
|
}
|
|
1960
1960
|
return -1;
|
|
1961
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Warn when a body-level `const` that READS a tracked value is then used in a
|
|
1964
|
+
* reactive binding.
|
|
1965
|
+
*
|
|
1966
|
+
* A body-level `const x = <expr>` is evaluated ONCE per render. When `<expr>`
|
|
1967
|
+
* reads a cell, the binding captures a snapshot, so any later re-evaluation of
|
|
1968
|
+
* that binding re-reads the SAME stale value and the UI silently stops
|
|
1969
|
+
* updating — the effect runs, the class/attribute effect runs, and nothing
|
|
1970
|
+
* changes. This has bitten real code twice in one app:
|
|
1971
|
+
*
|
|
1972
|
+
* const isSelected = selectedProgram.id === prog.id; // never re-evaluates
|
|
1973
|
+
* class={`... ${isSelected ? 'a' : 'b'}`} // stuck on 'b'
|
|
1974
|
+
*
|
|
1975
|
+
* The framework cannot fix this silently — hoisting is legitimate when the
|
|
1976
|
+
* value really is constant, and auto-inlining every such const would change
|
|
1977
|
+
* semantics. So it is reported at build time, naming the binding and the
|
|
1978
|
+
* tracked value it snapshotted.
|
|
1979
|
+
*
|
|
1980
|
+
* AST-only (no regex over source), and gated so a component with no such const
|
|
1981
|
+
* costs nothing: the binding scan only runs when a stale candidate exists.
|
|
1982
|
+
*/
|
|
1983
|
+
function warnStaleConstBindings(comp, file) {
|
|
1984
|
+
// Pass 1: every tracked/derived name, at ANY depth.
|
|
1985
|
+
const reactive = new Set();
|
|
1986
|
+
const eachNode = (nodes, fn) => {
|
|
1987
|
+
for (const n of nodes) {
|
|
1988
|
+
fn(n);
|
|
1989
|
+
if (n instanceof ComponentCall)
|
|
1990
|
+
eachNode(n.children, fn);
|
|
1991
|
+
if (n instanceof OpaqueDynamicRegion) {
|
|
1992
|
+
eachNode(n.consequentNodes, fn);
|
|
1993
|
+
eachNode(n.alternateNodes, fn);
|
|
1994
|
+
}
|
|
1995
|
+
if (n instanceof MapRegion)
|
|
1996
|
+
eachNode(n.bodyTemplate, fn);
|
|
1997
|
+
if (n instanceof ForLoop)
|
|
1998
|
+
eachNode(n.bodyTemplate, fn);
|
|
1999
|
+
if (n instanceof WhileLoop)
|
|
2000
|
+
eachNode(n.bodyTemplate, fn);
|
|
2001
|
+
if (n instanceof SwitchBlock)
|
|
2002
|
+
for (const c of n.cases)
|
|
2003
|
+
eachNode(c.body, fn);
|
|
2004
|
+
if (n instanceof TryCatch) {
|
|
2005
|
+
eachNode(n.bodyTemplate, fn);
|
|
2006
|
+
eachNode(n.catchBody, fn);
|
|
2007
|
+
}
|
|
2008
|
+
if (n instanceof StaticNode)
|
|
2009
|
+
eachNode(n.children, fn);
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
eachNode(comp.body, (n) => {
|
|
2013
|
+
if (!(n instanceof TrackDecl))
|
|
2014
|
+
return;
|
|
2015
|
+
reactive.add(n.name);
|
|
2016
|
+
if (n.rawName)
|
|
2017
|
+
reactive.add(n.rawName);
|
|
2018
|
+
});
|
|
2019
|
+
if (reactive.size === 0)
|
|
2020
|
+
return;
|
|
2021
|
+
// Pass 2: any `const X = <expr>` (at any depth) whose expr reads a tracked value.
|
|
2022
|
+
// Skips aliases of the cell itself (`const &[v, c] = track()` then `const v2 = v`
|
|
2023
|
+
// is still a snapshot, but `const c2 = c` is not) and anything named like a cell.
|
|
2024
|
+
const stale = new Map();
|
|
2025
|
+
eachNode(comp.body, (n) => {
|
|
2026
|
+
if (!(n instanceof RuntimeStatement))
|
|
2027
|
+
return;
|
|
2028
|
+
const st = n.ast;
|
|
2029
|
+
if (st?.type !== 'VariableDeclaration')
|
|
2030
|
+
return;
|
|
2031
|
+
for (const d of st.declarations || []) {
|
|
2032
|
+
if (d.id?.type !== 'Identifier' || !d.init)
|
|
2033
|
+
continue;
|
|
2034
|
+
if (reactive.has(d.id.name))
|
|
2035
|
+
continue;
|
|
2036
|
+
// A bare identifier initializer is an ALIAS, not a read: `const copy =
|
|
2037
|
+
// selCell` re-binds the cell and stays live. Only a read snapshots.
|
|
2038
|
+
if (d.init.type === 'Identifier' && reactive.has(d.init.name))
|
|
2039
|
+
continue;
|
|
2040
|
+
const seen = new Set();
|
|
2041
|
+
collectIdentifiers(d.init, seen);
|
|
2042
|
+
for (const name of seen) {
|
|
2043
|
+
if (!reactive.has(name))
|
|
2044
|
+
continue;
|
|
2045
|
+
stale.set(d.id.name, name);
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
if (stale.size === 0)
|
|
2051
|
+
return;
|
|
2052
|
+
// Pass 3: report only when such a name reaches a REACTIVE BINDING. Event
|
|
2053
|
+
// handler attributes are deliberately excluded: `onClick={handler}` where the
|
|
2054
|
+
// handler reads a cell is correct — the read happens when the handler runs,
|
|
2055
|
+
// and the binding is not expected to re-run.
|
|
2056
|
+
const reported = new Set();
|
|
2057
|
+
const report = (name) => {
|
|
2058
|
+
if (reported.has(name))
|
|
2059
|
+
return;
|
|
2060
|
+
const src = stale.get(name);
|
|
2061
|
+
if (!src)
|
|
2062
|
+
return;
|
|
2063
|
+
reported.add(name);
|
|
2064
|
+
console.warn(`[vesk] ${comp.name}: \`const ${name} = ...\` is computed once and reads the tracked value ` +
|
|
2065
|
+
`\`${src}\`, so bindings that use \`${name}\` never update. ` +
|
|
2066
|
+
`Inline the expression in the binding, or wrap the value in derived().`);
|
|
2067
|
+
};
|
|
2068
|
+
const isEventAttr = (target) => !!target && /^on./.test(target);
|
|
2069
|
+
eachNode(comp.body, (n) => {
|
|
2070
|
+
if (n instanceof DynamicBinding) {
|
|
2071
|
+
if (!isEventAttr(n.target)) {
|
|
2072
|
+
const seen = new Set();
|
|
2073
|
+
collectIdentifiers(n.expression.ast, seen);
|
|
2074
|
+
for (const name of seen)
|
|
2075
|
+
if (stale.has(name))
|
|
2076
|
+
report(name);
|
|
2077
|
+
}
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
if (n instanceof ComponentCall) {
|
|
2081
|
+
for (const pr of n.props) {
|
|
2082
|
+
const seen = new Set();
|
|
2083
|
+
collectIdentifiers(pr.value.ast, seen);
|
|
2084
|
+
for (const name of seen)
|
|
2085
|
+
if (stale.has(name))
|
|
2086
|
+
report(name);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
/** Every identifier name appearing anywhere in an expression (AST walk). */
|
|
2092
|
+
function collectIdentifiers(node, into) {
|
|
2093
|
+
if (!node || typeof node !== 'object')
|
|
2094
|
+
return;
|
|
2095
|
+
if (Array.isArray(node)) {
|
|
2096
|
+
for (const child of node)
|
|
2097
|
+
collectIdentifiers(child, into);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (typeof node.type !== 'string')
|
|
2101
|
+
return;
|
|
2102
|
+
if (node.type === 'Identifier' && typeof node.name === 'string')
|
|
2103
|
+
into.add(node.name);
|
|
2104
|
+
for (const key of Object.keys(node)) {
|
|
2105
|
+
if (key === 'loc' || key === 'range' || key === 'start' || key === 'end')
|
|
2106
|
+
continue;
|
|
2107
|
+
collectIdentifiers(node[key], into);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
1962
2110
|
export function generateIR(ast, source, filename) {
|
|
1963
2111
|
__vskAnnotations = ast.__vskAnnotations ?? [];
|
|
1964
2112
|
__slotProps = null;
|
|
@@ -2291,6 +2439,8 @@ export function generateIR(ast, source, filename) {
|
|
|
2291
2439
|
imports.push(`import { ${missing.join(', ')} } from '@vesk/runtime';`);
|
|
2292
2440
|
}
|
|
2293
2441
|
}
|
|
2442
|
+
for (const comp of components)
|
|
2443
|
+
warnStaleConstBindings(comp, file);
|
|
2294
2444
|
const irRoot = new IRRoot(components, imports, importedNames, staticProps, loadFn, topLevelCode, exportAliases, reexportSources);
|
|
2295
2445
|
irRoot.metadataSource = metadataSource;
|
|
2296
2446
|
return irRoot;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/compiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@sveltejs/acorn-typescript": "^1.0.0",
|
|
23
|
-
"@vesk/types": "^0.2.
|
|
23
|
+
"@vesk/types": "^0.2.56",
|
|
24
24
|
"acorn": "^8.15.0",
|
|
25
25
|
"esrap": "^2.3.0",
|
|
26
26
|
"is-reference": "^3.0.0",
|