0x-lang 0.1.12 → 0.1.14
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/ast.d.ts +9 -1
- package/dist/compiler.d.ts +2 -0
- package/dist/compiler.js +21 -3
- package/dist/compiler.js.map +1 -1
- package/dist/generators/react.js +114 -22
- package/dist/generators/react.js.map +1 -1
- package/dist/generators/source-map.d.ts +18 -0
- package/dist/generators/source-map.js +45 -0
- package/dist/generators/source-map.js.map +1 -0
- package/dist/generators/svelte.js +36 -8
- package/dist/generators/svelte.js.map +1 -1
- package/dist/generators/vue.js +36 -6
- package/dist/generators/vue.js.map +1 -1
- package/dist/parser.js +25 -2
- package/dist/parser.js.map +1 -1
- package/dist/tokenizer.js +1 -1
- package/dist/tokenizer.js.map +1 -1
- package/package.json +2 -1
package/dist/ast.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export interface OnDestroy extends BaseNode {
|
|
|
79
79
|
export interface WatchBlock extends BaseNode {
|
|
80
80
|
type: 'WatchBlock';
|
|
81
81
|
variable: string;
|
|
82
|
+
variables: string[];
|
|
82
83
|
body: Statement[];
|
|
83
84
|
}
|
|
84
85
|
export interface CheckDecl extends BaseNode {
|
|
@@ -188,6 +189,12 @@ export interface JsBlock extends BaseNode {
|
|
|
188
189
|
type: 'JsBlock';
|
|
189
190
|
code: string;
|
|
190
191
|
}
|
|
192
|
+
export interface TopLevelVarDecl extends BaseNode {
|
|
193
|
+
type: 'TopLevelVarDecl';
|
|
194
|
+
keyword: 'const' | 'let';
|
|
195
|
+
name: string;
|
|
196
|
+
value: Expression;
|
|
197
|
+
}
|
|
191
198
|
export interface CommentNode extends BaseNode {
|
|
192
199
|
type: 'Comment';
|
|
193
200
|
text: string;
|
|
@@ -903,7 +910,7 @@ export interface RtlNode extends BaseNode {
|
|
|
903
910
|
props: Record<string, Expression>;
|
|
904
911
|
}
|
|
905
912
|
export type UINode = LayoutNode | TextNode | ButtonNode | InputNode | ImageNode | LinkNode | ToggleNode | SelectNode | IfBlock | ForBlock | ShowBlock | HideBlock | ComponentCall | CommentNode | TableNode | ChartNode | StatNode | NavNode | UploadNode | ModalNode | ToastNode | CrudNode | ListNode | LayoutShellNode | SlideOverNode | DrawerNode | CommandNode | ConfirmNode | PayNode | CartNode | MediaNode | NotificationNode | SearchNode | FilterNode | SocialNode | ProfileNode | HeroNode | FeaturesNode | PricingNode | FaqNode | TestimonialNode | FooterNode | AdminNode | SeoNode | A11yNode | AnimateNode | GestureNode | AiNode | AutomationNode | DevNode | EmitNode | ResponsiveNode | BreadcrumbNode | StatsGridNode | DividerNode | ProgressNode | ErrorNode | LoadingNode | OfflineNode | RetryNode | LogNode;
|
|
906
|
-
export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | ModelNode | DataDecl | FormDecl | AuthDecl | RealtimeDecl | RouteDecl | RoleDecl | AutomationNode | DevNode | DeployNode | EnvNode | DockerNode | CiNode | DomainNode | CdnNode | MonitorNode | BackupNode | EndpointNode | MiddlewareNode | QueueNode | CronNode | CacheNode | MigrateNode | SeedNode | WebhookNode | StorageNode | TestNode | E2eNode | MockNode | FixtureNode | I18nNode | LocaleNode | RtlNode | UINode;
|
|
913
|
+
export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | TopLevelVarDecl | ModelNode | DataDecl | FormDecl | AuthDecl | RealtimeDecl | RouteDecl | RoleDecl | AutomationNode | DevNode | DeployNode | EnvNode | DockerNode | CiNode | DomainNode | CdnNode | MonitorNode | BackupNode | EndpointNode | MiddlewareNode | QueueNode | CronNode | CacheNode | MigrateNode | SeedNode | WebhookNode | StorageNode | TestNode | E2eNode | MockNode | FixtureNode | I18nNode | LocaleNode | RtlNode | UINode;
|
|
907
914
|
export interface CodeGenerator {
|
|
908
915
|
target: string;
|
|
909
916
|
generate(ast: PageNode | ComponentNode | AppNode): GeneratedCode;
|
|
@@ -914,4 +921,5 @@ export interface GeneratedCode {
|
|
|
914
921
|
imports: string[];
|
|
915
922
|
lineCount: number;
|
|
916
923
|
tokenCount: number;
|
|
924
|
+
sourceMap?: string;
|
|
917
925
|
}
|
package/dist/compiler.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ import type { GeneratedCode } from './ast.js';
|
|
|
2
2
|
export interface CompileOptions {
|
|
3
3
|
target: 'react' | 'vue' | 'svelte';
|
|
4
4
|
validate?: boolean;
|
|
5
|
+
sourceMap?: boolean;
|
|
6
|
+
useClient?: boolean;
|
|
5
7
|
}
|
|
6
8
|
export declare function compile(source: string, options: CompileOptions): GeneratedCode;
|
package/dist/compiler.js
CHANGED
|
@@ -14,15 +14,33 @@ export function compile(source, options) {
|
|
|
14
14
|
throw new Error(`Validation errors:\n${errMsg}`);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
let result;
|
|
17
18
|
switch (options.target) {
|
|
18
19
|
case 'react':
|
|
19
|
-
|
|
20
|
+
result = generateReact(ast);
|
|
21
|
+
break;
|
|
20
22
|
case 'vue':
|
|
21
|
-
|
|
23
|
+
result = generateVue(ast);
|
|
24
|
+
break;
|
|
22
25
|
case 'svelte':
|
|
23
|
-
|
|
26
|
+
result = generateSvelte(ast);
|
|
27
|
+
break;
|
|
24
28
|
default:
|
|
25
29
|
throw new Error(`Unknown target: ${options.target}`);
|
|
26
30
|
}
|
|
31
|
+
// Post-process: strip source map comments and V3 source map if disabled
|
|
32
|
+
if (options.sourceMap === false) {
|
|
33
|
+
result = { ...result, code: result.code.replace(/\{\/\* 0x:L\d+ \*\/\}/g, '').replace(/<!-- 0x:L\d+ -->/g, ''), sourceMap: undefined };
|
|
34
|
+
}
|
|
35
|
+
else if (result.sourceMap) {
|
|
36
|
+
// Append inline sourceMappingURL as base64 data URL
|
|
37
|
+
const b64 = Buffer.from(result.sourceMap).toString('base64');
|
|
38
|
+
result = { ...result, code: result.code + `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${b64}\n` };
|
|
39
|
+
}
|
|
40
|
+
// Post-process: strip 'use client' if explicitly disabled
|
|
41
|
+
if (options.useClient === false && options.target === 'react') {
|
|
42
|
+
result = { ...result, code: result.code.replace(/^'use client';\n\n/gm, '') };
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
27
45
|
}
|
|
28
46
|
//# sourceMappingURL=compiler.js.map
|
package/dist/compiler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAEvB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAEvB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAY1C,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,OAAuB;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAE1B,8BAA8B;IAC9B,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClG,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,IAAI,MAAqB,CAAC;IAC1B,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,OAAO;YACV,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM;QACR,KAAK,KAAK;YACR,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM;QACR;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,wEAAwE;IACxE,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QAChC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IACzI,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QAC5B,oDAAoD;QACpD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC7D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,qEAAqE,GAAG,IAAI,EAAE,CAAC;IAC3H,CAAC;IAED,0DAA0D;IAC1D,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC9D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,EAAE,CAAC;IAChF,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/generators/react.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// 0x → React Code Generator
|
|
2
2
|
import { SIZE_MAP, unquote, capitalize, parseGradient, addPx } from './shared.js';
|
|
3
|
+
import { SourceMapBuilder } from './source-map.js';
|
|
3
4
|
function ctx() {
|
|
4
5
|
return {
|
|
5
6
|
imports: new Set(),
|
|
6
7
|
states: new Map(),
|
|
7
8
|
derivedNames: new Set(),
|
|
9
|
+
propNames: new Set(),
|
|
8
10
|
styles: new Map(),
|
|
9
11
|
indent: 1,
|
|
10
12
|
readOnly: false,
|
|
13
|
+
smb: null,
|
|
11
14
|
};
|
|
12
15
|
}
|
|
13
16
|
function ind(c) {
|
|
@@ -116,12 +119,25 @@ export function generateReact(ast) {
|
|
|
116
119
|
}
|
|
117
120
|
const code = parts.join('\n\n');
|
|
118
121
|
const importLines = code.match(/^import .+$/gm) || [];
|
|
122
|
+
// Build V3 source map from 0x:L### comments in generated code
|
|
123
|
+
const sourceFile = ast.find(n => n.type === 'Page' || n.type === 'Component' || n.type === 'App');
|
|
124
|
+
const srcName = sourceFile?.name ? `${sourceFile.name}.0x` : 'source.0x';
|
|
125
|
+
const smb = new SourceMapBuilder(srcName, 'Component.jsx');
|
|
126
|
+
const lines = code.split('\n');
|
|
127
|
+
for (let i = 0; i < lines.length; i++) {
|
|
128
|
+
const match = lines[i].match(/\{\/\* 0x:L(\d+) \*\/\}/);
|
|
129
|
+
if (match) {
|
|
130
|
+
smb.addMapping(parseInt(match[1], 10), 0);
|
|
131
|
+
}
|
|
132
|
+
smb.advance(lines[i] + (i < lines.length - 1 ? '\n' : ''));
|
|
133
|
+
}
|
|
119
134
|
return {
|
|
120
135
|
code,
|
|
121
136
|
filename: 'Component.jsx',
|
|
122
137
|
imports: importLines,
|
|
123
|
-
lineCount:
|
|
138
|
+
lineCount: lines.length,
|
|
124
139
|
tokenCount: code.split(/\s+/).length,
|
|
140
|
+
sourceMap: smb.toJSON(),
|
|
125
141
|
};
|
|
126
142
|
}
|
|
127
143
|
/** Generate framework-agnostic backend/infrastructure code for a non-UI node. Returns null if unrecognized. */
|
|
@@ -162,12 +178,14 @@ export function generateBackendCode(node) {
|
|
|
162
178
|
}
|
|
163
179
|
function generateTopLevel(node) {
|
|
164
180
|
const c = ctx();
|
|
165
|
-
// First pass: collect states, derived, styles
|
|
181
|
+
// First pass: collect states, derived, props, styles
|
|
166
182
|
for (const child of node.body) {
|
|
167
183
|
if (child.type === 'StateDecl')
|
|
168
184
|
c.states.set(child.name, child);
|
|
169
185
|
if (child.type === 'DerivedDecl')
|
|
170
186
|
c.derivedNames.add(child.name);
|
|
187
|
+
if (child.type === 'PropDecl')
|
|
188
|
+
c.propNames.add(child.name);
|
|
171
189
|
if (child.type === 'StyleDecl')
|
|
172
190
|
c.styles.set(child.name, child);
|
|
173
191
|
}
|
|
@@ -259,6 +277,11 @@ function generateTopLevel(node) {
|
|
|
259
277
|
case 'JsBlock':
|
|
260
278
|
hookLines.push(child.code);
|
|
261
279
|
break;
|
|
280
|
+
case 'TopLevelVarDecl': {
|
|
281
|
+
const tlv = child;
|
|
282
|
+
hookLines.push(`${tlv.keyword} ${tlv.name} = ${genExpr(tlv.value, c)};`);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
262
285
|
case 'StyleDecl':
|
|
263
286
|
// Collected already, used by reference
|
|
264
287
|
break;
|
|
@@ -276,13 +299,17 @@ function generateTopLevel(node) {
|
|
|
276
299
|
const propsArg = props.length > 0
|
|
277
300
|
? `{ ${props.map(p => p.defaultValue ? `${p.name} = ${genExpr(p.defaultValue, c)}` : p.name).join(', ')} }`
|
|
278
301
|
: '';
|
|
279
|
-
// Build import line
|
|
280
|
-
const reactImports = ['React'];
|
|
302
|
+
// Build import line — only import hooks that are actually used
|
|
281
303
|
const hookNames = Array.from(c.imports).sort();
|
|
282
|
-
if
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
304
|
+
// Check if React.Fragment is used in JSX (for loops, show/hide with multiple children)
|
|
305
|
+
const needsReact = jsxParts.some(p => p.includes('React.Fragment'));
|
|
306
|
+
const importLine = needsReact && hookNames.length > 0
|
|
307
|
+
? `import React, { ${hookNames.join(', ')} } from 'react';`
|
|
308
|
+
: needsReact
|
|
309
|
+
? `import React from 'react';`
|
|
310
|
+
: hookNames.length > 0
|
|
311
|
+
? `import { ${hookNames.join(', ')} } from 'react';`
|
|
312
|
+
: `import React from 'react';`;
|
|
286
313
|
// Collect user imports (js import / import / use)
|
|
287
314
|
const userImports = [];
|
|
288
315
|
for (const child of node.body) {
|
|
@@ -303,9 +330,12 @@ function generateTopLevel(node) {
|
|
|
303
330
|
// Build component
|
|
304
331
|
const isComponent = node.type === 'Component';
|
|
305
332
|
const exportKw = isComponent ? '' : 'export default ';
|
|
333
|
+
// Add "use client" for Next.js SSR when client-side hooks are used
|
|
334
|
+
const needsClient = c.imports.has('useState') || c.imports.has('useEffect') || c.imports.has('useRef') || c.imports.has('useCallback');
|
|
306
335
|
const lines = [
|
|
336
|
+
...(needsClient ? [`'use client';`, ''] : []),
|
|
307
337
|
`// Generated by 0x`,
|
|
308
|
-
|
|
338
|
+
importLine,
|
|
309
339
|
...userImports,
|
|
310
340
|
'',
|
|
311
341
|
`${exportKw}function ${node.name}(${propsArg}) {`,
|
|
@@ -337,8 +367,9 @@ function genDerived(node, c) {
|
|
|
337
367
|
c.readOnly = true;
|
|
338
368
|
const expr = genExpr(node.expression, c);
|
|
339
369
|
c.readOnly = prevReadOnly;
|
|
340
|
-
const deps =
|
|
341
|
-
|
|
370
|
+
const { deps, warning } = extractDepsWithWarning(node.expression, c);
|
|
371
|
+
const warnComment = warning ? ` ${warning}` : '';
|
|
372
|
+
return `${warnComment ? warnComment + '\n ' : ''}const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
|
|
342
373
|
}
|
|
343
374
|
function genCheck(node, c) {
|
|
344
375
|
const cond = genExpr(node.condition, c);
|
|
@@ -389,13 +420,17 @@ function genOnDestroy(node, c) {
|
|
|
389
420
|
function genWatch(node, c) {
|
|
390
421
|
c.imports.add('useEffect');
|
|
391
422
|
const body = node.body.map(s => genStatement(s, c)).join('\n ');
|
|
423
|
+
const vars = (node.variables || [node.variable]).join(', ');
|
|
392
424
|
const hasAwait = bodyContainsAwait(node.body);
|
|
393
425
|
if (hasAwait) {
|
|
394
|
-
return `useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${
|
|
426
|
+
return `useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${vars}]);`;
|
|
395
427
|
}
|
|
396
|
-
return `useEffect(() => {\n ${body}\n }, [${
|
|
428
|
+
return `useEffect(() => {\n ${body}\n }, [${vars}]);`;
|
|
397
429
|
}
|
|
398
430
|
// ── UI Nodes ────────────────────────────────────────
|
|
431
|
+
function srcComment(node) {
|
|
432
|
+
return node.loc?.line ? `{/* 0x:L${node.loc.line} */}` : '';
|
|
433
|
+
}
|
|
399
434
|
function genUINode(node, c) {
|
|
400
435
|
switch (node.type) {
|
|
401
436
|
case 'Layout': return genLayout(node, c);
|
|
@@ -566,7 +601,8 @@ function genLayout(node, c) {
|
|
|
566
601
|
}
|
|
567
602
|
const styleStr = genStyleObj(style, dynamicKeys);
|
|
568
603
|
const children = node.children.map(ch => genUINode(ch, c)).join('\n');
|
|
569
|
-
|
|
604
|
+
const sc = srcComment(node);
|
|
605
|
+
return `${sc}<div style={${styleStr}}>\n${children}\n</div>`;
|
|
570
606
|
}
|
|
571
607
|
function genText(node, c) {
|
|
572
608
|
const style = {};
|
|
@@ -626,7 +662,8 @@ function genText(node, c) {
|
|
|
626
662
|
// Badge prop: render a badge indicator next to content
|
|
627
663
|
const badgeExpr = node.props['badge'];
|
|
628
664
|
const tooltipExpr = node.props['tooltip'];
|
|
629
|
-
|
|
665
|
+
const sc = srcComment(node);
|
|
666
|
+
let result = `${sc}<span${styleStr}>${content}</span>`;
|
|
630
667
|
if (badgeExpr) {
|
|
631
668
|
const badge = genExpr(badgeExpr, c);
|
|
632
669
|
result = `<span style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>\n<span${styleStr}>${content}</span>\n<span style={{ marginLeft: '6px', padding: '2px 6px', fontSize: '12px', fontWeight: 'bold', borderRadius: '9999px', backgroundColor: '#ef4444', color: '#fff', minWidth: '20px', textAlign: 'center' }}>{${badge}}</span>\n</span>`;
|
|
@@ -654,7 +691,8 @@ function genButton(node, c) {
|
|
|
654
691
|
}
|
|
655
692
|
}
|
|
656
693
|
const propsStr = styleProps.join(' ');
|
|
657
|
-
|
|
694
|
+
const sc = srcComment(node);
|
|
695
|
+
return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}>${label}</button>`;
|
|
658
696
|
}
|
|
659
697
|
function genInput(node, c) {
|
|
660
698
|
const setter = 'set' + capitalize(node.binding);
|
|
@@ -680,7 +718,8 @@ function genInput(node, c) {
|
|
|
680
718
|
props.push(`onKeyPress={e => ${handler}(e.key)}`);
|
|
681
719
|
}
|
|
682
720
|
}
|
|
683
|
-
|
|
721
|
+
const sc = srcComment(node);
|
|
722
|
+
return `${sc}<input ${props.join(' ')} />`;
|
|
684
723
|
}
|
|
685
724
|
function genImage(node, c) {
|
|
686
725
|
const src = genExpr(node.src, c);
|
|
@@ -2445,14 +2484,32 @@ function buildSpreadUpdate(prevVar, path, value) {
|
|
|
2445
2484
|
const inner = buildSpreadUpdate(`${prevVar}.${head}`, rest, value);
|
|
2446
2485
|
return `({...${prevVar}, ${head}: ${inner}})`;
|
|
2447
2486
|
}
|
|
2448
|
-
|
|
2487
|
+
const KNOWN_GLOBALS = new Set([
|
|
2488
|
+
'console', 'Math', 'JSON', 'parseInt', 'parseFloat', 'Number', 'String', 'Boolean',
|
|
2489
|
+
'Array', 'Object', 'Date', 'RegExp', 'Error', 'Promise', 'Map', 'Set', 'Symbol',
|
|
2490
|
+
'window', 'document', 'navigator', 'localStorage', 'sessionStorage', 'fetch',
|
|
2491
|
+
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'requestAnimationFrame',
|
|
2492
|
+
'undefined', 'NaN', 'Infinity', 'globalThis', 'alert', 'confirm', 'prompt',
|
|
2493
|
+
'encodeURIComponent', 'decodeURIComponent', 'encodeURI', 'decodeURI', 'btoa', 'atob',
|
|
2494
|
+
'e', 'event', 'prev', 'item', 'index', 'key', 'value', 'i', 'j', 'k', 'v', 'x', 'el',
|
|
2495
|
+
]);
|
|
2496
|
+
function extractDepsWithWarning(expr, c) {
|
|
2449
2497
|
const deps = new Set();
|
|
2498
|
+
const unknowns = new Set();
|
|
2450
2499
|
walkExpr(expr, e => {
|
|
2451
|
-
if (e.kind === 'identifier'
|
|
2452
|
-
|
|
2500
|
+
if (e.kind === 'identifier') {
|
|
2501
|
+
if (c.states.has(e.name) || c.derivedNames.has(e.name) || c.propNames.has(e.name)) {
|
|
2502
|
+
deps.add(e.name);
|
|
2503
|
+
}
|
|
2504
|
+
else if (!KNOWN_GLOBALS.has(e.name)) {
|
|
2505
|
+
unknowns.add(e.name);
|
|
2506
|
+
}
|
|
2453
2507
|
}
|
|
2454
2508
|
});
|
|
2455
|
-
|
|
2509
|
+
const warning = unknowns.size > 0
|
|
2510
|
+
? `/* 0x-warn: untracked deps: ${Array.from(unknowns).join(', ')} */`
|
|
2511
|
+
: null;
|
|
2512
|
+
return { deps: Array.from(deps), warning };
|
|
2456
2513
|
}
|
|
2457
2514
|
function walkExpr(expr, fn) {
|
|
2458
2515
|
fn(expr);
|
|
@@ -2487,8 +2544,12 @@ function walkExpr(expr, fn) {
|
|
|
2487
2544
|
expr.properties.forEach(p => walkExpr(p.value, fn));
|
|
2488
2545
|
break;
|
|
2489
2546
|
case 'arrow':
|
|
2490
|
-
if (
|
|
2547
|
+
if (Array.isArray(expr.body)) {
|
|
2548
|
+
walkStmts(expr.body, fn);
|
|
2549
|
+
}
|
|
2550
|
+
else {
|
|
2491
2551
|
walkExpr(expr.body, fn);
|
|
2552
|
+
}
|
|
2492
2553
|
break;
|
|
2493
2554
|
case 'template':
|
|
2494
2555
|
expr.parts.forEach(p => { if (typeof p !== 'string')
|
|
@@ -2503,6 +2564,37 @@ function walkExpr(expr, fn) {
|
|
|
2503
2564
|
break;
|
|
2504
2565
|
}
|
|
2505
2566
|
}
|
|
2567
|
+
function walkStmts(stmts, fn) {
|
|
2568
|
+
for (const stmt of stmts) {
|
|
2569
|
+
switch (stmt.kind) {
|
|
2570
|
+
case 'expr_stmt':
|
|
2571
|
+
walkExpr(stmt.expression, fn);
|
|
2572
|
+
break;
|
|
2573
|
+
case 'return':
|
|
2574
|
+
if (stmt.value)
|
|
2575
|
+
walkExpr(stmt.value, fn);
|
|
2576
|
+
break;
|
|
2577
|
+
case 'var_decl':
|
|
2578
|
+
walkExpr(stmt.value, fn);
|
|
2579
|
+
break;
|
|
2580
|
+
case 'assignment_stmt':
|
|
2581
|
+
walkExpr(stmt.target, fn);
|
|
2582
|
+
walkExpr(stmt.value, fn);
|
|
2583
|
+
break;
|
|
2584
|
+
case 'if_stmt':
|
|
2585
|
+
walkExpr(stmt.condition, fn);
|
|
2586
|
+
walkStmts(stmt.body, fn);
|
|
2587
|
+
stmt.elifs.forEach(ei => { walkExpr(ei.condition, fn); walkStmts(ei.body, fn); });
|
|
2588
|
+
if (stmt.elseBody)
|
|
2589
|
+
walkStmts(stmt.elseBody, fn);
|
|
2590
|
+
break;
|
|
2591
|
+
case 'for_stmt':
|
|
2592
|
+
walkExpr(stmt.iterable, fn);
|
|
2593
|
+
walkStmts(stmt.body, fn);
|
|
2594
|
+
break;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2506
2598
|
function genStyleObj(style, dynamicKeys) {
|
|
2507
2599
|
if (Object.keys(style).length === 0)
|
|
2508
2600
|
return '{}';
|