@pyreon/compiler 0.12.4 → 0.12.6
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +241 -15
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/jsx.ts +296 -22
- package/src/tests/jsx.test.ts +192 -1
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"4ebf1530-1","name":"jsx.ts"},{"uid":"4ebf1530-3","name":"project-scanner.ts"},{"uid":"4ebf1530-5","name":"react-intercept.ts"},{"uid":"4ebf1530-7","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"4ebf1530-1":{"renderedLength":34297,"gzipLength":9205,"brotliLength":0,"metaUid":"4ebf1530-0"},"4ebf1530-3":{"renderedLength":4762,"gzipLength":1730,"brotliLength":0,"metaUid":"4ebf1530-2"},"4ebf1530-5":{"renderedLength":27692,"gzipLength":6920,"brotliLength":0,"metaUid":"4ebf1530-4"},"4ebf1530-7":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"4ebf1530-6"}},"nodeMetas":{"4ebf1530-0":{"id":"/src/jsx.ts","moduleParts":{"index.js":"4ebf1530-1"},"imported":[{"uid":"4ebf1530-8"}],"importedBy":[{"uid":"4ebf1530-6"}]},"4ebf1530-2":{"id":"/src/project-scanner.ts","moduleParts":{"index.js":"4ebf1530-3"},"imported":[{"uid":"4ebf1530-9"},{"uid":"4ebf1530-10"}],"importedBy":[{"uid":"4ebf1530-6"}]},"4ebf1530-4":{"id":"/src/react-intercept.ts","moduleParts":{"index.js":"4ebf1530-5"},"imported":[{"uid":"4ebf1530-8"}],"importedBy":[{"uid":"4ebf1530-6"}]},"4ebf1530-6":{"id":"/src/index.ts","moduleParts":{"index.js":"4ebf1530-7"},"imported":[{"uid":"4ebf1530-0"},{"uid":"4ebf1530-2"},{"uid":"4ebf1530-4"}],"importedBy":[],"isEntry":true},"4ebf1530-8":{"id":"typescript","moduleParts":{},"imported":[],"importedBy":[{"uid":"4ebf1530-0"},{"uid":"4ebf1530-4"}]},"4ebf1530-9":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"4ebf1530-2"}]},"4ebf1530-10":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"4ebf1530-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -85,6 +85,7 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
85
85
|
let needsBindTextImportGlobal = false;
|
|
86
86
|
let needsBindDirectImportGlobal = false;
|
|
87
87
|
let needsBindImportGlobal = false;
|
|
88
|
+
let needsApplyPropsImportGlobal = false;
|
|
88
89
|
/**
|
|
89
90
|
* If `node` is a fully-static JSX element/fragment, register a module-scope
|
|
90
91
|
* hoist for it and return the generated variable name. Otherwise return null.
|
|
@@ -104,10 +105,11 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
104
105
|
function wrap(expr) {
|
|
105
106
|
const start = expr.getStart(sf);
|
|
106
107
|
const end = expr.getEnd();
|
|
108
|
+
const exprText = inlineVarsInText(code.slice(start, end));
|
|
107
109
|
replacements.push({
|
|
108
110
|
start,
|
|
109
111
|
end,
|
|
110
|
-
text: `() => ${
|
|
112
|
+
text: `() => ${exprText}`
|
|
111
113
|
});
|
|
112
114
|
}
|
|
113
115
|
/** Try to hoist or wrap an expression, pushing a replacement if needed. */
|
|
@@ -122,7 +124,7 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
122
124
|
}
|
|
123
125
|
/** Try to emit a template for a JsxElement. Returns true if handled. */
|
|
124
126
|
function tryTemplateEmit(node) {
|
|
125
|
-
if (templateElementCount(node) < 1) return false;
|
|
127
|
+
if (templateElementCount(node, true) < 1) return false;
|
|
126
128
|
const tplCall = buildTemplateCall(node);
|
|
127
129
|
if (!tplCall) return false;
|
|
128
130
|
const start = node.getStart(sf);
|
|
@@ -203,6 +205,161 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
203
205
|
}
|
|
204
206
|
ts.forEachChild(expr, walk);
|
|
205
207
|
}
|
|
208
|
+
/** Names that refer to the props object or splitProps results. */
|
|
209
|
+
const propsNames = /* @__PURE__ */ new Set();
|
|
210
|
+
/** Map of variable name → source text of the original expression. */
|
|
211
|
+
const propDerivedVars = /* @__PURE__ */ new Map();
|
|
212
|
+
/** Check if an expression reads from a tracked props-like object. */
|
|
213
|
+
function readsFromProps(node) {
|
|
214
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) return propsNames.has(node.expression.text);
|
|
215
|
+
if (ts.isElementAccessExpression(node) && ts.isIdentifier(node.expression)) return propsNames.has(node.expression.text);
|
|
216
|
+
let found = false;
|
|
217
|
+
ts.forEachChild(node, (child) => {
|
|
218
|
+
if (found) return;
|
|
219
|
+
if (readsFromProps(child)) found = true;
|
|
220
|
+
});
|
|
221
|
+
return found;
|
|
222
|
+
}
|
|
223
|
+
/** Pre-pass: scan a function body for prop-derived variable declarations.
|
|
224
|
+
* callbackDepth tracks nesting inside callback arguments (map, filter, etc.)
|
|
225
|
+
* to avoid tracking variables declared inside callbacks as prop-derived. */
|
|
226
|
+
let _callbackDepth = 0;
|
|
227
|
+
function scanForPropDerivedVars(node) {
|
|
228
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
229
|
+
const parent = node.parent;
|
|
230
|
+
if (parent && ts.isCallExpression(parent) && parent.arguments.includes(node)) {
|
|
231
|
+
_callbackDepth++;
|
|
232
|
+
ts.forEachChild(node, scanForPropDerivedVars);
|
|
233
|
+
_callbackDepth--;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if ((ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.parameters.length > 0) {
|
|
238
|
+
const parent = node.parent;
|
|
239
|
+
if (parent && ts.isCallExpression(parent) && parent.arguments.includes(node)) {
|
|
240
|
+
ts.forEachChild(node, scanForPropDerivedVars);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const firstParam = node.parameters[0];
|
|
244
|
+
if (ts.isIdentifier(firstParam.name)) {
|
|
245
|
+
let hasJSX = false;
|
|
246
|
+
ts.forEachChild(node, function checkJSX(n) {
|
|
247
|
+
if (hasJSX) return;
|
|
248
|
+
if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) {
|
|
249
|
+
hasJSX = true;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
ts.forEachChild(n, checkJSX);
|
|
253
|
+
});
|
|
254
|
+
if (hasJSX) propsNames.add(firstParam.name.text);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (ts.isVariableStatement(node)) for (const decl of node.declarationList.declarations) {
|
|
258
|
+
if (ts.isArrayBindingPattern(decl.name) && decl.initializer && ts.isCallExpression(decl.initializer)) {
|
|
259
|
+
const callee = decl.initializer.expression;
|
|
260
|
+
if (ts.isIdentifier(callee) && callee.text === "splitProps") {
|
|
261
|
+
for (const el of decl.name.elements) if (ts.isBindingElement(el) && ts.isIdentifier(el.name)) propsNames.add(el.name.text);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!(node.declarationList.flags & ts.NodeFlags.Const)) continue;
|
|
265
|
+
if (_callbackDepth > 0) continue;
|
|
266
|
+
if (ts.isIdentifier(decl.name) && decl.initializer) {
|
|
267
|
+
if (readsFromProps(decl.initializer)) {
|
|
268
|
+
const varName = decl.name.text;
|
|
269
|
+
const exprText = code.slice(decl.initializer.getStart(sf), decl.initializer.getEnd());
|
|
270
|
+
propDerivedVars.set(varName, exprText);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
ts.forEachChild(node, scanForPropDerivedVars);
|
|
275
|
+
}
|
|
276
|
+
scanForPropDerivedVars(sf);
|
|
277
|
+
let changed = true;
|
|
278
|
+
while (changed) {
|
|
279
|
+
changed = false;
|
|
280
|
+
sf.forEachChild(function scanTransitive(node) {
|
|
281
|
+
if (!ts.isVariableStatement(node)) {
|
|
282
|
+
ts.forEachChild(node, scanTransitive);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
for (const decl of node.declarationList.declarations) {
|
|
286
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
|
|
287
|
+
const varName = decl.name.text;
|
|
288
|
+
if (propDerivedVars.has(varName)) continue;
|
|
289
|
+
if (node.declarationList.flags & ts.NodeFlags.Let) continue;
|
|
290
|
+
let usesPropVar = false;
|
|
291
|
+
ts.forEachChild(decl.initializer, function check(n) {
|
|
292
|
+
if (usesPropVar) return;
|
|
293
|
+
if (ts.isIdentifier(n) && propDerivedVars.has(n.text)) {
|
|
294
|
+
const parent = n.parent;
|
|
295
|
+
if (parent && ts.isPropertyAccessExpression(parent) && parent.name === n) return;
|
|
296
|
+
usesPropVar = true;
|
|
297
|
+
}
|
|
298
|
+
ts.forEachChild(n, check);
|
|
299
|
+
});
|
|
300
|
+
if (usesPropVar) {
|
|
301
|
+
const exprText = code.slice(decl.initializer.getStart(sf), decl.initializer.getEnd());
|
|
302
|
+
propDerivedVars.set(varName, exprText);
|
|
303
|
+
changed = true;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
for (const [varName, expr] of propDerivedVars) {
|
|
309
|
+
let resolved = expr;
|
|
310
|
+
for (const [depName, depExpr] of propDerivedVars) {
|
|
311
|
+
if (depName === varName) continue;
|
|
312
|
+
const re = new RegExp(`(?<![.\\w])${depName}(?![\\w:])`, "g");
|
|
313
|
+
if (re.test(resolved)) resolved = resolved.replace(re, `(${depExpr})`);
|
|
314
|
+
}
|
|
315
|
+
if (resolved !== expr) propDerivedVars.set(varName, resolved);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Enhanced dynamic check — combines containsCall with props awareness.
|
|
319
|
+
* Returns true if an expression is reactive (contains signal calls,
|
|
320
|
+
* accesses props members, or references prop-derived variables).
|
|
321
|
+
*/
|
|
322
|
+
/**
|
|
323
|
+
* Replace prop-derived variable names in a source text with their original expressions.
|
|
324
|
+
* Simple regex-based replacement — safe because variable names are identifiers.
|
|
325
|
+
*/
|
|
326
|
+
function inlineVarsInText(text) {
|
|
327
|
+
if (propDerivedVars.size === 0) return text;
|
|
328
|
+
let result = text;
|
|
329
|
+
for (const [varName, expr] of propDerivedVars) {
|
|
330
|
+
const re = new RegExp(`(?<![.\\w])${varName}(?![\\w:])`, "g");
|
|
331
|
+
result = result.replace(re, `(${expr})`);
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
function isDynamic(node) {
|
|
336
|
+
if (containsCall(node)) return true;
|
|
337
|
+
return accessesProps(node);
|
|
338
|
+
}
|
|
339
|
+
/** Check if an expression accesses a tracked props object or a prop-derived variable. */
|
|
340
|
+
function accessesProps(node) {
|
|
341
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
|
342
|
+
if (propsNames.has(node.expression.text)) return true;
|
|
343
|
+
}
|
|
344
|
+
if (ts.isIdentifier(node) && propDerivedVars.has(node.text)) {
|
|
345
|
+
const parent = node.parent;
|
|
346
|
+
if (parent && ts.isPropertyAccessExpression(parent) && parent.name === node) return false;
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
let found = false;
|
|
350
|
+
ts.forEachChild(node, (child) => {
|
|
351
|
+
if (found) return;
|
|
352
|
+
if (ts.isArrowFunction(child) || ts.isFunctionExpression(child)) return;
|
|
353
|
+
if (accessesProps(child)) found = true;
|
|
354
|
+
});
|
|
355
|
+
return found;
|
|
356
|
+
}
|
|
357
|
+
function shouldWrap(node) {
|
|
358
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) return false;
|
|
359
|
+
if (isStatic(node)) return false;
|
|
360
|
+
if (ts.isCallExpression(node) && isPureStaticCall(node)) return false;
|
|
361
|
+
return isDynamic(node);
|
|
362
|
+
}
|
|
206
363
|
function walk(node) {
|
|
207
364
|
if (ts.isJsxElement(node) && tryTemplateEmit(node)) return;
|
|
208
365
|
if (ts.isJsxSelfClosingElement(node) || ts.isJsxElement(node)) checkForWarnings(node);
|
|
@@ -236,6 +393,7 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
236
393
|
const runtimeDomImports = ["_tpl"];
|
|
237
394
|
if (needsBindDirectImportGlobal) runtimeDomImports.push("_bindDirect");
|
|
238
395
|
if (needsBindTextImportGlobal) runtimeDomImports.push("_bindText");
|
|
396
|
+
if (needsApplyPropsImportGlobal) runtimeDomImports.push("_applyProps");
|
|
239
397
|
const reactivityImports = needsBindImportGlobal ? `\nimport { _bind } from "@pyreon/reactivity";` : "";
|
|
240
398
|
result = `import { ${runtimeDomImports.join(", ")} } from "@pyreon/runtime-dom";${reactivityImports}\n` + result;
|
|
241
399
|
}
|
|
@@ -245,10 +403,18 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
245
403
|
usesTemplates: needsTplImport,
|
|
246
404
|
warnings
|
|
247
405
|
};
|
|
248
|
-
/**
|
|
249
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Check if attributes prevent template emission.
|
|
408
|
+
* - `key` always bails (VNode reconciliation prop)
|
|
409
|
+
* - Spread on inner elements bails (too complex to merge in _bind)
|
|
410
|
+
* - Spread on root element is allowed — applied via applyProps in _bind
|
|
411
|
+
*/
|
|
412
|
+
function hasBailAttr(node, isRoot = false) {
|
|
250
413
|
for (const attr of jsxAttrs(node)) {
|
|
251
|
-
if (ts.isJsxSpreadAttribute(attr))
|
|
414
|
+
if (ts.isJsxSpreadAttribute(attr)) {
|
|
415
|
+
if (isRoot) continue;
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
252
418
|
if (ts.isJsxAttribute(attr) && ts.isIdentifier(attr.name) && attr.name.text === "key") return true;
|
|
253
419
|
}
|
|
254
420
|
return false;
|
|
@@ -271,10 +437,10 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
271
437
|
* Count DOM elements in a JSX subtree. Returns -1 if the tree is not
|
|
272
438
|
* eligible for template emission.
|
|
273
439
|
*/
|
|
274
|
-
function templateElementCount(node) {
|
|
440
|
+
function templateElementCount(node, isRoot = false) {
|
|
275
441
|
const tag = jsxTagName(node);
|
|
276
442
|
if (!tag || !isLowerCase(tag)) return -1;
|
|
277
|
-
if (hasBailAttr(node)) return -1;
|
|
443
|
+
if (hasBailAttr(node, isRoot)) return -1;
|
|
278
444
|
if (!ts.isJsxElement(node)) return 1;
|
|
279
445
|
let count = 1;
|
|
280
446
|
for (const child of node.children) {
|
|
@@ -306,6 +472,7 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
306
472
|
const reactiveBindExprs = [];
|
|
307
473
|
let needsBindTextImport = false;
|
|
308
474
|
let needsBindDirectImport = false;
|
|
475
|
+
let needsApplyPropsImport = false;
|
|
309
476
|
function nextVar() {
|
|
310
477
|
return `__e${varIdx++}`;
|
|
311
478
|
}
|
|
@@ -376,7 +543,7 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
376
543
|
};
|
|
377
544
|
return {
|
|
378
545
|
expr: sliceExpr(exprNode),
|
|
379
|
-
isReactive:
|
|
546
|
+
isReactive: isDynamic(exprNode)
|
|
380
547
|
};
|
|
381
548
|
}
|
|
382
549
|
/** Build a setter expression for an attribute. */
|
|
@@ -434,6 +601,13 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
434
601
|
}
|
|
435
602
|
/** Process a single attribute, returning HTML to append. */
|
|
436
603
|
function processOneAttr(attr, varName) {
|
|
604
|
+
if (ts.isJsxSpreadAttribute(attr)) {
|
|
605
|
+
const expr = sliceExpr(attr.expression);
|
|
606
|
+
needsApplyPropsImport = true;
|
|
607
|
+
if (isDynamic(attr.expression)) reactiveBindExprs.push(`_applyProps(${varName}, ${expr})`);
|
|
608
|
+
else bindLines.push(`_applyProps(${varName}, ${expr})`);
|
|
609
|
+
return "";
|
|
610
|
+
}
|
|
437
611
|
if (!ts.isJsxAttribute(attr)) return "";
|
|
438
612
|
const attrName = ts.isIdentifier(attr.name) ? attr.name.text : "";
|
|
439
613
|
if (attrName === "key") return "";
|
|
@@ -457,7 +631,11 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
457
631
|
needsBindTextImport = true;
|
|
458
632
|
const d = nextDisp();
|
|
459
633
|
bindLines.push(`const ${d} = _bindText(${directRef}, ${tVar})`);
|
|
460
|
-
} else
|
|
634
|
+
} else {
|
|
635
|
+
needsBindImportGlobal = true;
|
|
636
|
+
const d = nextDisp();
|
|
637
|
+
bindLines.push(`const ${d} = _bind(() => { ${tVar}.data = ${inlineVarsInText(expr)} })`);
|
|
638
|
+
}
|
|
461
639
|
return needsPlaceholder ? "<!>" : "";
|
|
462
640
|
}
|
|
463
641
|
/** Emit bind lines for a static text expression child. */
|
|
@@ -516,11 +694,12 @@ function transformJSX(code, filename = "input.tsx") {
|
|
|
516
694
|
if (html === null) return null;
|
|
517
695
|
if (needsBindTextImport) needsBindTextImportGlobal = true;
|
|
518
696
|
if (needsBindDirectImport) needsBindDirectImportGlobal = true;
|
|
697
|
+
if (needsApplyPropsImport) needsApplyPropsImportGlobal = true;
|
|
519
698
|
const escaped = html.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
520
699
|
if (reactiveBindExprs.length > 0) {
|
|
521
700
|
needsBindImportGlobal = true;
|
|
522
701
|
const combinedName = nextDisp();
|
|
523
|
-
const combinedBody = reactiveBindExprs.join("; ");
|
|
702
|
+
const combinedBody = reactiveBindExprs.map(inlineVarsInText).join("; ");
|
|
524
703
|
bindLines.push(`const ${combinedName} = _bind(() => { ${combinedBody} })`);
|
|
525
704
|
}
|
|
526
705
|
if (bindLines.length === 0 && disposerNames.length === 0) return `_tpl("${escaped}", () => null)`;
|
|
@@ -668,13 +847,60 @@ function isStaticChild(child) {
|
|
|
668
847
|
function isStatic(node) {
|
|
669
848
|
return ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword || node.kind === ts.SyntaxKind.UndefinedKeyword;
|
|
670
849
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
850
|
+
/** Known pure global functions that don't read signals. */
|
|
851
|
+
const PURE_CALLS = new Set([
|
|
852
|
+
"Math.max",
|
|
853
|
+
"Math.min",
|
|
854
|
+
"Math.abs",
|
|
855
|
+
"Math.floor",
|
|
856
|
+
"Math.ceil",
|
|
857
|
+
"Math.round",
|
|
858
|
+
"Math.pow",
|
|
859
|
+
"Math.sqrt",
|
|
860
|
+
"Math.random",
|
|
861
|
+
"Math.trunc",
|
|
862
|
+
"Math.sign",
|
|
863
|
+
"Number.parseInt",
|
|
864
|
+
"Number.parseFloat",
|
|
865
|
+
"Number.isNaN",
|
|
866
|
+
"Number.isFinite",
|
|
867
|
+
"parseInt",
|
|
868
|
+
"parseFloat",
|
|
869
|
+
"isNaN",
|
|
870
|
+
"isFinite",
|
|
871
|
+
"String.fromCharCode",
|
|
872
|
+
"String.fromCodePoint",
|
|
873
|
+
"Object.keys",
|
|
874
|
+
"Object.values",
|
|
875
|
+
"Object.entries",
|
|
876
|
+
"Object.assign",
|
|
877
|
+
"Object.freeze",
|
|
878
|
+
"Object.create",
|
|
879
|
+
"Array.from",
|
|
880
|
+
"Array.isArray",
|
|
881
|
+
"Array.of",
|
|
882
|
+
"JSON.stringify",
|
|
883
|
+
"JSON.parse",
|
|
884
|
+
"encodeURIComponent",
|
|
885
|
+
"decodeURIComponent",
|
|
886
|
+
"encodeURI",
|
|
887
|
+
"decodeURI",
|
|
888
|
+
"Date.now"
|
|
889
|
+
]);
|
|
890
|
+
/** Check if a call expression calls a known pure function with static args. */
|
|
891
|
+
function isPureStaticCall(node) {
|
|
892
|
+
const callee = node.expression;
|
|
893
|
+
let name = "";
|
|
894
|
+
if (ts.isIdentifier(callee)) name = callee.text;
|
|
895
|
+
else if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression)) name = `${callee.expression.text}.${callee.name.text}`;
|
|
896
|
+
if (!PURE_CALLS.has(name)) return false;
|
|
897
|
+
return node.arguments.every((arg) => !ts.isSpreadElement(arg) && isStatic(arg));
|
|
675
898
|
}
|
|
676
899
|
function containsCall(node) {
|
|
677
|
-
if (ts.isCallExpression(node))
|
|
900
|
+
if (ts.isCallExpression(node)) {
|
|
901
|
+
if (isPureStaticCall(node)) return false;
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
678
904
|
if (ts.isTaggedTemplateExpression(node)) return true;
|
|
679
905
|
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) return false;
|
|
680
906
|
return ts.forEachChild(node, containsCall) ?? false;
|