oxlint-plugin-react-doctor 0.6.1 → 0.6.2-dev.072d37e
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/index.js +138 -115
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -212,8 +212,16 @@ const sliceBelowSourceRoot = (filename) => {
|
|
|
212
212
|
if (cutAt < 0) return filename;
|
|
213
213
|
return filename.slice(cutAt);
|
|
214
214
|
};
|
|
215
|
+
let lastFilename;
|
|
216
|
+
let lastResult = false;
|
|
215
217
|
const isTestlikeFilename = (rawFilename) => {
|
|
216
218
|
if (!rawFilename) return false;
|
|
219
|
+
if (rawFilename === lastFilename) return lastResult;
|
|
220
|
+
lastFilename = rawFilename;
|
|
221
|
+
lastResult = computeIsTestlikeFilename(rawFilename);
|
|
222
|
+
return lastResult;
|
|
223
|
+
};
|
|
224
|
+
const computeIsTestlikeFilename = (rawFilename) => {
|
|
217
225
|
const filename = rawFilename.replaceAll("\\", "/");
|
|
218
226
|
const lastSlash = filename.lastIndexOf("/");
|
|
219
227
|
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
@@ -983,16 +991,8 @@ const buildBindingIndex = (root) => {
|
|
|
983
991
|
}
|
|
984
992
|
}
|
|
985
993
|
}
|
|
986
|
-
const nodeRecord = node;
|
|
987
|
-
for (const key of Object.keys(nodeRecord)) {
|
|
988
|
-
if (key === "parent") continue;
|
|
989
|
-
const child = nodeRecord[key];
|
|
990
|
-
if (Array.isArray(child)) {
|
|
991
|
-
for (const item of child) if (isAstNode(item)) visit(item);
|
|
992
|
-
} else if (isAstNode(child)) visit(child);
|
|
993
|
-
}
|
|
994
994
|
};
|
|
995
|
-
|
|
995
|
+
walkAst(root, visit);
|
|
996
996
|
return out;
|
|
997
997
|
};
|
|
998
998
|
const programRootCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -1423,49 +1423,40 @@ const getElementType = (openingElement, settings) => {
|
|
|
1423
1423
|
//#region src/plugin/utils/find-import-source-for-name.ts
|
|
1424
1424
|
const collectFromProgram = (programRoot) => {
|
|
1425
1425
|
const lookup = /* @__PURE__ */ new Map();
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1426
|
+
const bodyStatements = programRoot.body ?? [];
|
|
1427
|
+
for (const node of bodyStatements) {
|
|
1428
|
+
if (node.type !== "ImportDeclaration" || !("source" in node) || !node.source) continue;
|
|
1429
|
+
const source = node.source.value;
|
|
1430
|
+
if (typeof source !== "string") continue;
|
|
1431
|
+
if (!("specifiers" in node) || !Array.isArray(node.specifiers)) continue;
|
|
1432
|
+
for (const specifier of node.specifiers) {
|
|
1433
|
+
if (!("local" in specifier) || !specifier.local) continue;
|
|
1434
|
+
const local = specifier.local;
|
|
1435
|
+
if (typeof local.name !== "string") continue;
|
|
1436
|
+
if (specifier.type === "ImportDefaultSpecifier") lookup.set(local.name, {
|
|
1437
|
+
source,
|
|
1438
|
+
imported: null,
|
|
1439
|
+
isDefault: true,
|
|
1440
|
+
isNamespace: false
|
|
1441
|
+
});
|
|
1442
|
+
else if (specifier.type === "ImportNamespaceSpecifier") lookup.set(local.name, {
|
|
1443
|
+
source,
|
|
1444
|
+
imported: null,
|
|
1445
|
+
isDefault: false,
|
|
1446
|
+
isNamespace: true
|
|
1447
|
+
});
|
|
1448
|
+
else if (specifier.type === "ImportSpecifier") {
|
|
1449
|
+
const importedNode = specifier.imported;
|
|
1450
|
+
const importedName = importedNode?.name ?? (typeof importedNode?.value === "string" ? importedNode.value : null);
|
|
1451
|
+
lookup.set(local.name, {
|
|
1441
1452
|
source,
|
|
1442
|
-
imported:
|
|
1453
|
+
imported: importedName,
|
|
1443
1454
|
isDefault: false,
|
|
1444
|
-
isNamespace:
|
|
1455
|
+
isNamespace: false
|
|
1445
1456
|
});
|
|
1446
|
-
else if (specifier.type === "ImportSpecifier") {
|
|
1447
|
-
const importedNode = specifier.imported;
|
|
1448
|
-
const importedName = importedNode?.name ?? (typeof importedNode?.value === "string" ? importedNode.value : null);
|
|
1449
|
-
lookup.set(local.name, {
|
|
1450
|
-
source,
|
|
1451
|
-
imported: importedName,
|
|
1452
|
-
isDefault: false,
|
|
1453
|
-
isNamespace: false
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
1457
|
}
|
|
1457
|
-
return;
|
|
1458
1458
|
}
|
|
1459
|
-
|
|
1460
|
-
for (const key of Object.keys(nodeRecord)) {
|
|
1461
|
-
if (key === "parent") continue;
|
|
1462
|
-
const child = nodeRecord[key];
|
|
1463
|
-
if (Array.isArray(child)) {
|
|
1464
|
-
for (const item of child) if (isAstNode(item)) visit(item);
|
|
1465
|
-
} else if (isAstNode(child)) visit(child);
|
|
1466
|
-
}
|
|
1467
|
-
};
|
|
1468
|
-
visit(programRoot);
|
|
1459
|
+
}
|
|
1469
1460
|
return lookup;
|
|
1470
1461
|
};
|
|
1471
1462
|
const importLookupCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -1479,6 +1470,12 @@ const getImportLookup = (node) => {
|
|
|
1479
1470
|
}
|
|
1480
1471
|
return cached;
|
|
1481
1472
|
};
|
|
1473
|
+
const hasImportFromModules = (contextNode, moduleSources) => {
|
|
1474
|
+
const lookup = getImportLookup(contextNode);
|
|
1475
|
+
if (!lookup) return false;
|
|
1476
|
+
for (const info of lookup.values()) if (moduleSources.includes(info.source)) return true;
|
|
1477
|
+
return false;
|
|
1478
|
+
};
|
|
1482
1479
|
const isImportedFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
1483
1480
|
const lookup = getImportLookup(contextNode);
|
|
1484
1481
|
if (!lookup) return false;
|
|
@@ -1631,6 +1628,7 @@ const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
|
1631
1628
|
//#region src/plugin/utils/is-generated-image-render-context.ts
|
|
1632
1629
|
const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
|
|
1633
1630
|
const SATORI_MODULE = "satori";
|
|
1631
|
+
const GENERATED_IMAGE_MODULES = [...IMAGE_RESPONSE_MODULES, SATORI_MODULE];
|
|
1634
1632
|
const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
|
|
1635
1633
|
const isGeneratedImageRenderFilename = (rawFilename) => {
|
|
1636
1634
|
if (!rawFilename) return false;
|
|
@@ -1774,7 +1772,7 @@ const collectGeneratedImageJsxNodes = (programRoot) => {
|
|
|
1774
1772
|
const cached = generatedImageJsxCache.get(programRoot);
|
|
1775
1773
|
if (cached) return cached;
|
|
1776
1774
|
const generatedImageJsxNodes = /* @__PURE__ */ new WeakSet();
|
|
1777
|
-
walkAst(programRoot, (descendantNode) => {
|
|
1775
|
+
if (hasImportFromModules(programRoot, GENERATED_IMAGE_MODULES)) walkAst(programRoot, (descendantNode) => {
|
|
1778
1776
|
if (!isGeneratedImageRendererCall(descendantNode)) return;
|
|
1779
1777
|
for (const argument of descendantNode.arguments) markGeneratedImageExpression(argument, programRoot, generatedImageJsxNodes, /* @__PURE__ */ new Set());
|
|
1780
1778
|
});
|
|
@@ -3460,7 +3458,7 @@ const SECRET_UNAMBIGUOUS_PLACEHOLDER_VALUE_PATTERNS = [
|
|
|
3460
3458
|
];
|
|
3461
3459
|
const SECRET_CONTEXTUAL_PLACEHOLDER_VALUE_PATTERNS = [/(?:^|[_\-\s])(?:example|sample|dummy)(?:$|[_\-\s])/i];
|
|
3462
3460
|
const SECRET_PLACEHOLDER_CONTEXT_PATTERN = /(?:placeholder|example|sample|dummy|masked|redacted|mask)/i;
|
|
3463
|
-
const SECRET_VARIABLE_PATTERN = /(?:api_?key|secret|token|password|credential|auth)/i;
|
|
3461
|
+
const SECRET_VARIABLE_PATTERN = /(?:api_?key|secret|token|password|credential|auth(?!or(?!i[sz])))/i;
|
|
3464
3462
|
const SECRET_TOOLING_FILE_PATTERN = /(?:^|\/)[^/]+\.config\.[cm]?[jt]s$/;
|
|
3465
3463
|
const SECRET_TOOLING_RC_FILE_PATTERN = /(?:^|\/)(?:\.[a-z-]+rc|[a-z-]+\.rc)\.[cm]?[jt]s$/;
|
|
3466
3464
|
const SECRET_TEST_FILE_PATTERN = /(?:^|\/)[^/]+\.(?:test|spec|stories|story|fixture|fixtures)\.[cm]?[jt]sx?$/;
|
|
@@ -3967,9 +3965,11 @@ const collectScopedReferenceIdentifierNames = (node, into, shadowed) => {
|
|
|
3967
3965
|
return;
|
|
3968
3966
|
}
|
|
3969
3967
|
if (typeof node.type === "string" && node.type.startsWith("TS")) return;
|
|
3970
|
-
|
|
3968
|
+
const nodeRecord = node;
|
|
3969
|
+
for (const key of Object.keys(nodeRecord)) {
|
|
3971
3970
|
if (key === "parent") continue;
|
|
3972
3971
|
if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
|
|
3972
|
+
const child = nodeRecord[key];
|
|
3973
3973
|
if (Array.isArray(child)) {
|
|
3974
3974
|
for (const item of child) if (isAstNode(item)) collectScopedReferenceIdentifierNames(item, into, shadowed);
|
|
3975
3975
|
} else if (isAstNode(child)) collectScopedReferenceIdentifierNames(child, into, shadowed);
|
|
@@ -7183,27 +7183,8 @@ const isDescendantScope = (inner, outer) => {
|
|
|
7183
7183
|
return false;
|
|
7184
7184
|
};
|
|
7185
7185
|
//#endregion
|
|
7186
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
7187
|
-
/**
|
|
7188
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
7189
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
7190
|
-
* match (`true`) or the chain's root (`false`).
|
|
7191
|
-
*
|
|
7192
|
-
* Was duplicated byte-identical in:
|
|
7193
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
7194
|
-
* - semantic/closure-captures.ts
|
|
7195
|
-
*/
|
|
7196
|
-
const isAstDescendant = (inner, outer) => {
|
|
7197
|
-
let current = inner;
|
|
7198
|
-
while (current) {
|
|
7199
|
-
if (current === outer) return true;
|
|
7200
|
-
current = current.parent ?? null;
|
|
7201
|
-
}
|
|
7202
|
-
return false;
|
|
7203
|
-
};
|
|
7204
|
-
//#endregion
|
|
7205
7186
|
//#region src/plugin/semantic/closure-captures.ts
|
|
7206
|
-
const
|
|
7187
|
+
const computeClosureCaptures = (functionNode, scopes) => {
|
|
7207
7188
|
const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
|
|
7208
7189
|
const out = [];
|
|
7209
7190
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7238,7 +7219,20 @@ const closureCaptures = (functionNode, scopes) => {
|
|
|
7238
7219
|
}
|
|
7239
7220
|
};
|
|
7240
7221
|
visit(functionNode);
|
|
7241
|
-
return out
|
|
7222
|
+
return out;
|
|
7223
|
+
};
|
|
7224
|
+
const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
7225
|
+
const closureCaptures = (functionNode, scopes) => {
|
|
7226
|
+
let capturesByFunction = capturesByAnalysis.get(scopes);
|
|
7227
|
+
if (!capturesByFunction) {
|
|
7228
|
+
capturesByFunction = /* @__PURE__ */ new WeakMap();
|
|
7229
|
+
capturesByAnalysis.set(scopes, capturesByFunction);
|
|
7230
|
+
}
|
|
7231
|
+
const memoizedCaptures = capturesByFunction.get(functionNode);
|
|
7232
|
+
if (memoizedCaptures) return memoizedCaptures;
|
|
7233
|
+
const computedCaptures = computeClosureCaptures(functionNode, scopes);
|
|
7234
|
+
capturesByFunction.set(functionNode, computedCaptures);
|
|
7235
|
+
return computedCaptures;
|
|
7242
7236
|
};
|
|
7243
7237
|
//#endregion
|
|
7244
7238
|
//#region src/plugin/utils/is-react-hook-name.ts
|
|
@@ -7367,6 +7361,25 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
7367
7361
|
};
|
|
7368
7362
|
};
|
|
7369
7363
|
//#endregion
|
|
7364
|
+
//#region src/plugin/utils/is-ast-descendant.ts
|
|
7365
|
+
/**
|
|
7366
|
+
* True when `inner` is `outer` itself or any descendant in the AST
|
|
7367
|
+
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
7368
|
+
* match (`true`) or the chain's root (`false`).
|
|
7369
|
+
*
|
|
7370
|
+
* Was duplicated byte-identical in:
|
|
7371
|
+
* - exhaustive-deps-symbol-stability.ts
|
|
7372
|
+
* - semantic/closure-captures.ts
|
|
7373
|
+
*/
|
|
7374
|
+
const isAstDescendant = (inner, outer) => {
|
|
7375
|
+
let current = inner;
|
|
7376
|
+
while (current) {
|
|
7377
|
+
if (current === outer) return true;
|
|
7378
|
+
current = current.parent ?? null;
|
|
7379
|
+
}
|
|
7380
|
+
return false;
|
|
7381
|
+
};
|
|
7382
|
+
//#endregion
|
|
7370
7383
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
7371
7384
|
/**
|
|
7372
7385
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -10439,10 +10452,15 @@ const jsCombineIterations = defineRule({
|
|
|
10439
10452
|
severity: "warn",
|
|
10440
10453
|
recommendation: "Combine `.map().filter()` style chains into one pass with `.reduce()` or a `for...of` loop, so you only loop over the list once",
|
|
10441
10454
|
create: (context) => {
|
|
10442
|
-
let
|
|
10455
|
+
let programNode = null;
|
|
10456
|
+
let generatorNamesInFile = null;
|
|
10457
|
+
const getGeneratorNamesInFile = () => {
|
|
10458
|
+
generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
|
|
10459
|
+
return generatorNamesInFile;
|
|
10460
|
+
};
|
|
10443
10461
|
return {
|
|
10444
|
-
Program(
|
|
10445
|
-
|
|
10462
|
+
Program(node) {
|
|
10463
|
+
programNode = node;
|
|
10446
10464
|
},
|
|
10447
10465
|
CallExpression(node) {
|
|
10448
10466
|
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
|
|
@@ -10464,7 +10482,7 @@ const jsCombineIterations = defineRule({
|
|
|
10464
10482
|
if (isNullFilteringPredicate(filterArgument)) return;
|
|
10465
10483
|
if (isTypePredicateArrow(filterArgument)) return;
|
|
10466
10484
|
}
|
|
10467
|
-
if (isReceiverChainIteratorRooted(innerCall.callee.object,
|
|
10485
|
+
if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
|
|
10468
10486
|
if (isSmallLiteralArrayRootedChain(innerCall.callee.object)) return;
|
|
10469
10487
|
if (isStringSplitRootedChain(innerCall.callee.object)) return;
|
|
10470
10488
|
context.report({
|
|
@@ -12524,6 +12542,7 @@ const KNOWN_SLOT_PROP_NAMES = new Set([
|
|
|
12524
12542
|
"bottomContent",
|
|
12525
12543
|
"leftContent",
|
|
12526
12544
|
"rightContent",
|
|
12545
|
+
"config",
|
|
12527
12546
|
"value",
|
|
12528
12547
|
"currentValue",
|
|
12529
12548
|
"form",
|
|
@@ -12662,6 +12681,10 @@ const SLOT_PROP_SUFFIXES = [
|
|
|
12662
12681
|
"Panel",
|
|
12663
12682
|
"Overlay",
|
|
12664
12683
|
"Shape",
|
|
12684
|
+
"Avatar",
|
|
12685
|
+
"Text",
|
|
12686
|
+
"State",
|
|
12687
|
+
"Zone",
|
|
12665
12688
|
"Override",
|
|
12666
12689
|
"Overrides",
|
|
12667
12690
|
"Items",
|
|
@@ -12678,6 +12701,8 @@ const SLOT_PROP_SUFFIXES = [
|
|
|
12678
12701
|
];
|
|
12679
12702
|
const isSlotPropName = (propName) => {
|
|
12680
12703
|
if (KNOWN_SLOT_PROP_NAMES.has(propName)) return true;
|
|
12704
|
+
const decapitalized = propName.charAt(0).toLowerCase() + propName.slice(1);
|
|
12705
|
+
if (decapitalized !== propName && KNOWN_SLOT_PROP_NAMES.has(decapitalized)) return true;
|
|
12681
12706
|
for (const suffix of SLOT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
12682
12707
|
return false;
|
|
12683
12708
|
};
|
|
@@ -29521,21 +29546,18 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
29521
29546
|
reportNode
|
|
29522
29547
|
};
|
|
29523
29548
|
};
|
|
29524
|
-
const
|
|
29525
|
-
const
|
|
29526
|
-
const
|
|
29527
|
-
|
|
29528
|
-
const
|
|
29529
|
-
|
|
29530
|
-
|
|
29531
|
-
|
|
29532
|
-
|
|
29533
|
-
|
|
29534
|
-
|
|
29535
|
-
}
|
|
29549
|
+
const collectRelevantNodes = (programRoot) => {
|
|
29550
|
+
const exportNodes = [];
|
|
29551
|
+
const componentCandidates = [];
|
|
29552
|
+
walkAst(programRoot, (child) => {
|
|
29553
|
+
const childType = child.type;
|
|
29554
|
+
if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
|
|
29555
|
+
else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator") componentCandidates.push(child);
|
|
29556
|
+
});
|
|
29557
|
+
return {
|
|
29558
|
+
exportNodes,
|
|
29559
|
+
componentCandidates
|
|
29536
29560
|
};
|
|
29537
|
-
visit(programRoot);
|
|
29538
|
-
return out;
|
|
29539
29561
|
};
|
|
29540
29562
|
const isEntryPointFile = (filename) => {
|
|
29541
29563
|
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
@@ -29582,13 +29604,13 @@ const onlyExportComponents = defineRule({
|
|
|
29582
29604
|
};
|
|
29583
29605
|
return { Program(node) {
|
|
29584
29606
|
if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
|
|
29585
|
-
const
|
|
29607
|
+
const { exportNodes, componentCandidates } = collectRelevantNodes(node);
|
|
29586
29608
|
const exports = [];
|
|
29587
29609
|
let hasReactExport = false;
|
|
29588
29610
|
let hasAnyExports = false;
|
|
29589
29611
|
const localComponents = [];
|
|
29590
29612
|
const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
|
|
29591
|
-
for (const child of
|
|
29613
|
+
for (const child of exportNodes) {
|
|
29592
29614
|
if (isNodeOfType(child, "ExportAllDeclaration")) {
|
|
29593
29615
|
if (child.exportKind === "type") continue;
|
|
29594
29616
|
hasAnyExports = true;
|
|
@@ -29724,12 +29746,12 @@ const onlyExportComponents = defineRule({
|
|
|
29724
29746
|
}
|
|
29725
29747
|
return false;
|
|
29726
29748
|
};
|
|
29727
|
-
for (const child of
|
|
29749
|
+
for (const child of componentCandidates) {
|
|
29728
29750
|
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
29729
|
-
if (isReactComponentName(child.id.name) && !isInsideExport(child)) localComponents.push(child.id);
|
|
29751
|
+
if (isReactComponentName(child.id.name) && !isInsideExport(child) && !isInsideFunctionScope(child)) localComponents.push(child.id);
|
|
29730
29752
|
}
|
|
29731
29753
|
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
29732
|
-
if (isReactComponentName(child.id.name) && canBeReactFunctionComponent(child.init, state) && !isInsideExport(child)) localComponents.push(child.id);
|
|
29754
|
+
if (isReactComponentName(child.id.name) && canBeReactFunctionComponent(child.init, state) && !isInsideExport(child) && !isInsideFunctionScope(child)) localComponents.push(child.id);
|
|
29733
29755
|
}
|
|
29734
29756
|
}
|
|
29735
29757
|
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
@@ -46841,23 +46863,29 @@ const FALLBACK_CFG = {
|
|
|
46841
46863
|
enclosingFunction: () => null,
|
|
46842
46864
|
isUnconditionalFromEntry: () => false
|
|
46843
46865
|
};
|
|
46866
|
+
const scopesByProgram = /* @__PURE__ */ new WeakMap();
|
|
46867
|
+
const cfgByProgram = /* @__PURE__ */ new WeakMap();
|
|
46844
46868
|
const wrapWithSemanticContext = (rule) => ({
|
|
46845
46869
|
...rule,
|
|
46846
46870
|
create: (baseContext) => {
|
|
46847
46871
|
let programRoot = null;
|
|
46848
|
-
let cachedScopes = null;
|
|
46849
|
-
let cachedCfg = null;
|
|
46850
46872
|
const getScopes = () => {
|
|
46851
|
-
if (cachedScopes) return cachedScopes;
|
|
46852
46873
|
if (!programRoot) return buildFallbackScopes();
|
|
46853
|
-
|
|
46854
|
-
|
|
46874
|
+
let scopes = scopesByProgram.get(programRoot);
|
|
46875
|
+
if (!scopes) {
|
|
46876
|
+
scopes = analyzeScopes(programRoot);
|
|
46877
|
+
scopesByProgram.set(programRoot, scopes);
|
|
46878
|
+
}
|
|
46879
|
+
return scopes;
|
|
46855
46880
|
};
|
|
46856
46881
|
const getCfg = () => {
|
|
46857
|
-
if (cachedCfg) return cachedCfg;
|
|
46858
46882
|
if (!programRoot) return FALLBACK_CFG;
|
|
46859
|
-
|
|
46860
|
-
|
|
46883
|
+
let cfg = cfgByProgram.get(programRoot);
|
|
46884
|
+
if (!cfg) {
|
|
46885
|
+
cfg = analyzeControlFlow(programRoot);
|
|
46886
|
+
cfgByProgram.set(programRoot, cfg);
|
|
46887
|
+
}
|
|
46888
|
+
return cfg;
|
|
46861
46889
|
};
|
|
46862
46890
|
const enrichedContext = {
|
|
46863
46891
|
report: baseContext.report,
|
|
@@ -46872,23 +46900,18 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
46872
46900
|
return getCfg();
|
|
46873
46901
|
}
|
|
46874
46902
|
};
|
|
46875
|
-
const captureRootIfNeeded = (node) => {
|
|
46876
|
-
if (programRoot) return;
|
|
46877
|
-
programRoot = findProgramRoot(node);
|
|
46878
|
-
};
|
|
46879
46903
|
const visitors = rule.create(enrichedContext);
|
|
46880
|
-
const
|
|
46904
|
+
const passthroughVisitors = {};
|
|
46881
46905
|
for (const [nodeType, handler] of Object.entries(visitors)) {
|
|
46882
46906
|
if (typeof handler !== "function") continue;
|
|
46883
|
-
|
|
46884
|
-
captureRootIfNeeded(node);
|
|
46885
|
-
handler(node);
|
|
46886
|
-
});
|
|
46907
|
+
passthroughVisitors[nodeType] = handler;
|
|
46887
46908
|
}
|
|
46888
|
-
|
|
46889
|
-
|
|
46890
|
-
|
|
46891
|
-
|
|
46909
|
+
const innerProgramHandler = passthroughVisitors.Program;
|
|
46910
|
+
passthroughVisitors.Program = ((node) => {
|
|
46911
|
+
programRoot = node;
|
|
46912
|
+
if (innerProgramHandler) innerProgramHandler(node);
|
|
46913
|
+
});
|
|
46914
|
+
return passthroughVisitors;
|
|
46892
46915
|
}
|
|
46893
46916
|
});
|
|
46894
46917
|
//#endregion
|