diff --git a/.changeset/imported-call-static-values.md b/.changeset/imported-call-static-values.md
new file mode 100644
index 0000000000..3f77be00f8
--- /dev/null
+++ b/.changeset/imported-call-static-values.md
@@ -0,0 +1,7 @@
+---
+"oxlint-plugin-react-doctor": patch
+"eslint-plugin-react-doctor": patch
+"react-doctor": patch
+---
+
+Avoid recommending module-scope hoisting for literals that evaluate imported functions, including readonly const aliases. This conservatively retains per-render evaluation without classifying imported functions as impure.
diff --git a/.changeset/semantic-runtime-reference-resolution.md b/.changeset/semantic-runtime-reference-resolution.md
new file mode 100644
index 0000000000..b26de3ebac
--- /dev/null
+++ b/.changeset/semantic-runtime-reference-resolution.md
@@ -0,0 +1,7 @@
+---
+"oxlint-plugin-react-doctor": patch
+"eslint-plugin-react-doctor": patch
+"react-doctor": patch
+---
+
+Preserve runtime value references and writes when erased TypeScript declarations share their names, so provider exposure cannot bypass diagnostic proofs.
diff --git a/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--destructured-callee-alias-write.tsx b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--destructured-callee-alias-write.tsx
new file mode 100644
index 0000000000..56becfc90f
--- /dev/null
+++ b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--destructured-callee-alias-write.tsx
@@ -0,0 +1,11 @@
+// rule: prefer-module-scope-static-value
+// verdict: fail
+// weakness: cross-file
+// source: independent synthetic imported-call boundary regression
+import readSnapshot from "./state-reader";
+const selectedReader = readSnapshot;
+({ selectedReader } = { selectedReader: () => "replacement" });
+export const useSettings = () => {
+ const options = { label: selectedReader() };
+ return options;
+};
diff --git a/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--imported-evaluation.tsx b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--imported-evaluation.tsx
new file mode 100644
index 0000000000..6b203ae467
--- /dev/null
+++ b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--imported-evaluation.tsx
@@ -0,0 +1,13 @@
+// rule: prefer-module-scope-static-value
+// verdict: pass
+// weakness: cross-file
+// source: independent synthetic imported evaluation control
+import readSnapshot from "synthetic-snapshot-provider";
+
+const firstReader = readSnapshot;
+const selectedReader = firstReader;
+
+export const useSnapshotOptions = () => {
+ const options = { label: selectedReader() };
+ return options;
+};
diff --git a/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--merged-imported-reader-interface.tsx b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--merged-imported-reader-interface.tsx
new file mode 100644
index 0000000000..ddfb480aab
--- /dev/null
+++ b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--merged-imported-reader-interface.tsx
@@ -0,0 +1,12 @@
+// rule: prefer-module-scope-static-value
+// verdict: pass
+// weakness: cross-file
+// source: independent synthetic imported-call boundary regression
+import { useState } from "react";
+import readSnapshot from "./state-reader";
+interface readSnapshot {}
+export const useSettings = () => {
+ const [enabled] = useState(true);
+ const options = { label: readSnapshot() };
+ return { options, enabled };
+};
diff --git a/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--static-allocation-positive.tsx b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--static-allocation-positive.tsx
new file mode 100644
index 0000000000..c414096fbf
--- /dev/null
+++ b/packages/fuzz/corpus/regressions/prefer-module-scope-static-value--static-allocation-positive.tsx
@@ -0,0 +1,9 @@
+// rule: prefer-module-scope-static-value
+// verdict: fail
+// weakness: cross-file
+// source: independent synthetic static-allocation positive control
+
+export const StaticOptions = () => {
+ const options = { label: "fixed" };
+ return ;
+};
diff --git a/packages/fuzz/src/snippet-pools.ts b/packages/fuzz/src/snippet-pools.ts
index accb163dd2..3d0e0a1290 100644
--- a/packages/fuzz/src/snippet-pools.ts
+++ b/packages/fuzz/src/snippet-pools.ts
@@ -314,6 +314,7 @@ export const LIBRARY_SNIPPET_POOL = [
// Module scope — SSR hazards, guard aliases, contexts, caches, styled.
export const MODULE_SCOPE_SNIPPET_POOL = [
+ `import { readSnapshot as fuzzReadImportedSnapshot } from "synthetic-snapshot-provider"; const fuzzSnapshotReader = fuzzReadImportedSnapshot; export const useFuzzImportedOptions = () => { const options = { label: fuzzSnapshotReader() }; return options; }; export const FuzzStaticOptionsControl = () => { const options = { label: "fixed" }; return ; };`,
`import { create as createFuzzZustandStore } from "zustand"; export const useFuzzZustandStore = createFuzzZustandStore(() => ({ items: [] })); export const fuzzZustandSelection = useFuzzZustandStore((state) => ({ items: state.items }));`,
`import { create as createFuzzZustandTarget } from "zustand"; const useFuzzZustandTarget = createFuzzZustandTarget(() => ({ value: 0, other: 0 })); export const FuzzZustandWholeStoreTarget = () => { const { value: fuzzZustandValue } = useFuzzZustandTarget(); return ; };`,
`import { create as createFuzzZustandGetStore } from "zustand"; const useFuzzZustandGetStore = createFuzzZustandGetStore((_set, get) => ({ count: get().count }));`,
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call-review.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call-review.test.ts
new file mode 100644
index 0000000000..e727248b99
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call-review.test.ts
@@ -0,0 +1,84 @@
+import { describe, expect, it } from "vite-plus/test";
+import { runRule } from "../../../test-utils/run-rule.js";
+import { preferModuleScopeStaticValue } from "./prefer-module-scope-static-value.js";
+
+const cases = [
+ {
+ name: "baseline-imported-evaluation",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ expected: 0,
+ },
+ {
+ name: "merged-imported-reader-interface",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\ninterface readSnapshot {}\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ expected: 0,
+ },
+ {
+ name: "merged-readonly-reader-alias-interface",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\ninterface selectedReader {}\nconst selectedReader = readSnapshot;\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: selectedReader() };\n return { options, enabled };\n};\n',
+ expected: 0,
+ },
+ {
+ name: "merged-intermediate-reader-alias-interface",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\ninterface firstReader {}\nconst firstReader = readSnapshot;\nconst selectedReader = firstReader;\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: selectedReader() };\n return { options, enabled };\n};\n',
+ expected: 0,
+ },
+ {
+ name: "genuine-local-reader-with-interface",
+ code: 'import { useState } from "react";\nconst readSnapshot = () => "fixed";\ninterface readSnapshot {}\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ expected: 1,
+ },
+ {
+ name: "ordinary-local-reader-control",
+ code: 'import { useState } from "react";\nconst readSnapshot = () => "fixed";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ expected: 1,
+ },
+ {
+ name: "type-only-reader-import-control",
+ code: 'import { useState } from "react";\nimport type readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ expected: 1,
+ },
+ {
+ name: "static-object-activation",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: "fixed" };\n return { options, enabled };\n};\n',
+ expected: 1,
+ },
+ {
+ name: "plain-const-alias-write",
+ code: 'import readSnapshot from "./state-reader";\nconst selectedReader = readSnapshot;\nselectedReader = () => "replacement";\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ expected: 1,
+ },
+ {
+ name: "destructured-const-alias-write",
+ code: 'import readSnapshot from "./state-reader";\nconst selectedReader = readSnapshot;\n({ selectedReader } = { selectedReader: () => "replacement" });\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ expected: 1,
+ },
+ {
+ name: "readonly-const-alias",
+ code: 'import readSnapshot from "./state-reader";\nconst selectedReader = readSnapshot;\n\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ expected: 0,
+ },
+ {
+ name: "destructuring-rhs-readonly-alias",
+ code: 'import readSnapshot from "./state-reader";\nconst selectedReader = readSnapshot;\n\nconst holder: {reader?: unknown} = {};\n({reader: holder.reader} = {reader: selectedReader});\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ expected: 0,
+ },
+ {
+ name: "destructuring-computed-key-readonly-alias",
+ code: 'import readSnapshot from "./state-reader";\nconst selectedReader = readSnapshot;\n\nlet ignored: unknown;\n({[selectedReader.name]: ignored} = {});\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ expected: 0,
+ },
+];
+
+describe("imported call runtime binding and mutation boundaries", () => {
+ for (const testCase of cases) {
+ it(testCase.name, () => {
+ const result = runRule(preferModuleScopeStaticValue, testCase.code, {
+ filename: "src/imported-call-review.tsx",
+ includeLocations: true,
+ });
+ expect(result.parseErrors).toEqual([]);
+ expect(result.diagnostics).toHaveLength(testCase.expected);
+ });
+ }
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call.performance.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call.performance.test.ts
new file mode 100644
index 0000000000..3cd1af6f36
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call.performance.test.ts
@@ -0,0 +1,107 @@
+import { describe, expect, it } from "vite-plus/test";
+import type { SymbolDescriptor } from "../../semantic/scope-analysis.js";
+import { analyzeScopes } from "../../semantic/scope-analysis.js";
+import { attachParentReferences } from "../../../test-utils/attach-parent-references.js";
+import { parseFixture } from "../../../test-utils/parse-fixture.js";
+import { runRule } from "../../../test-utils/run-rule.js";
+import {
+ IMPORTED_CALL_AST_READS_PER_NODE,
+ IMPORTED_CALL_PROOF_COUNTS,
+ IMPORTED_CALL_REFERENCE_PASSES,
+} from "../../../test-utils/imported-call-proof/constants.js";
+import { containsImportedValueCall } from "../../utils/contains-imported-value-call.js";
+import type { EsTreeNode } from "../../utils/es-tree-node.js";
+import { isNodeOfType } from "../../utils/is-node-of-type.js";
+import { walkAst } from "../../utils/walk-ast.js";
+import { preferModuleScopeStaticValue } from "./prefer-module-scope-static-value.js";
+
+describe("imported call cold proof work", () => {
+ for (const directImport of [true, false]) {
+ for (const count of IMPORTED_CALL_PROOF_COUNTS) {
+ it(`${count} initializers sharing ${directImport ? "an import" : "a readonly alias chain"}`, () => {
+ const aliases = directImport
+ ? ""
+ : Array.from(
+ { length: count },
+ (_, index) =>
+ `const ${index === count - 1 ? "selectedReader" : `reader${index}`}=${index === 0 ? "readSnapshot" : `reader${index - 1}`};`,
+ ).join("");
+ const callee = directImport ? "readSnapshot" : "selectedReader";
+ const declarations = Array.from(
+ { length: count },
+ (_, index) => `const options${index}={label:${callee}()};`,
+ ).join("");
+ const returned = Array.from({ length: count }, (_, index) => `options${index}`).join(",");
+ const code = `import{useState}from'react';import readSnapshot from'./state-reader';${aliases}export const useSettings=()=>{const[enabled]=useState(true);${declarations}return{${returned},enabled}};`;
+ const rejectedCode = directImport
+ ? code.replace(
+ "import readSnapshot from'./state-reader';",
+ "const readSnapshot=()=> 'fixed';",
+ )
+ : code.replace("const selectedReader=", "let selectedReader=");
+ const parsed = parseFixture(code);
+ attachParentReferences(parsed.program);
+ const scopes = analyzeScopes(parsed.program);
+ const nodes: EsTreeNode[] = [];
+ const initializers: EsTreeNode[] = [];
+ const symbols = new Set();
+ walkAst(parsed.program, (node) => {
+ nodes.push(node);
+ if (
+ isNodeOfType(node, "VariableDeclarator") &&
+ isNodeOfType(node.id, "Identifier") &&
+ node.id.name.startsWith("options") &&
+ node.init
+ )
+ initializers.push(node.init);
+ if (isNodeOfType(node, "Identifier")) {
+ const symbol = scopes.symbolFor(node);
+ if (symbol) symbols.add(symbol);
+ }
+ });
+ let referenceVisits = 0;
+ let referenceCount = 0;
+ let astTypeReads = 0;
+ for (const symbol of symbols) {
+ const references = symbol.references;
+ referenceCount += references.length;
+ Object.defineProperty(symbol, "references", {
+ configurable: true,
+ value: new Proxy(references, {
+ get(target, property, receiver) {
+ if (typeof property === "string" && /^\d+$/.test(property)) referenceVisits++;
+ return Reflect.get(target, property, receiver);
+ },
+ }),
+ });
+ }
+ for (const node of nodes) {
+ const nodeType = node.type;
+ Object.defineProperty(node, "type", {
+ configurable: true,
+ enumerable: true,
+ get: () => {
+ astTypeReads++;
+ return nodeType;
+ },
+ });
+ }
+ expect(parsed.errors).toEqual([]);
+ expect(
+ initializers.map((initializer) => containsImportedValueCall(initializer, scopes)),
+ ).toEqual(Array.from({ length: count }, () => true));
+ expect(referenceVisits).toBeLessThanOrEqual(
+ referenceCount * IMPORTED_CALL_REFERENCE_PASSES,
+ );
+ expect(astTypeReads).toBeLessThanOrEqual(nodes.length * IMPORTED_CALL_AST_READS_PER_NODE);
+ const options = { filename: "src/imported-call-work.tsx", includeLocations: true };
+ const actual = runRule(preferModuleScopeStaticValue, code, options);
+ const rejected = runRule(preferModuleScopeStaticValue, rejectedCode, options);
+ expect(actual.parseErrors).toEqual([]);
+ expect(actual.diagnostics).toHaveLength(0);
+ expect(rejected.parseErrors).toEqual([]);
+ expect(rejected.diagnostics).toHaveLength(count);
+ });
+ }
+ }
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call.test.ts
new file mode 100644
index 0000000000..a7fb7807b2
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value-imported-call.test.ts
@@ -0,0 +1,397 @@
+import { describe, expect, it } from "vite-plus/test";
+import { runRule, type RunRuleOptions } from "../../../test-utils/run-rule.js";
+import { preferModuleScopeStaticValue } from "./prefer-module-scope-static-value.js";
+
+interface ImportedValueCallCase {
+ name: string;
+ code: string;
+ options: RunRuleOptions;
+ expectedCount: number;
+}
+
+const cases: ReadonlyArray = [
+ {
+ name: "imported-default-state-reader",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/imported-default-state-reader.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "imported-named-state-reader",
+ code: 'import { useState } from "react";\nimport { readSnapshot } from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/imported-named-state-reader.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "renamed-reader-import",
+ code: 'import { useState } from "react";\nimport { readSnapshot as selectLabel } from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: selectLabel() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/renamed-reader-import.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "nested-imported-reader-result",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { detail: { label: readSnapshot() } };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/nested-imported-reader-result.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "array-imported-reader-result",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = [readSnapshot()];\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/array-imported-reader-result.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "readonly-import-callee-alias",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nconst selectLabel = readSnapshot;\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: selectLabel() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/readonly-import-callee-alias.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "namespace-static-reader-call",
+ code: 'import { useState } from "react";\nimport * as state from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: state.readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/namespace-static-reader-call.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "literal-only-object",
+ code: 'import { useState } from "react";\n\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: "ready" };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/literal-only-object.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "literal-only-array",
+ code: 'import { useState } from "react";\n\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = ["ready", "waiting"];\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/literal-only-array.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "imported-constant-value-not-call",
+ code: 'import { useState } from "react";\nimport { stableLabel } from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: stableLabel };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/imported-constant-value-not-call.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "inline-function-initializer",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { read: () => readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/inline-function-initializer.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "known-date-now-call",
+ code: 'import { useState } from "react";\n\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { value: Date.now() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/known-date-now-call.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "known-random-call",
+ code: 'import { useState } from "react";\n\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { value: Math.random() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/known-random-call.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "literal-native-math-call",
+ code: 'import { useState } from "react";\n\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { value: Math.max(1, 2) };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/literal-native-math-call.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "pure-local-name-shadow",
+ code: 'import { useState } from "react";\nimport { readSnapshot as importedReader } from "./state-reader";\nconst readSnapshot = () => "fixed";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/pure-local-name-shadow.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "nested-event-allocation",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const Panel = () => { const click = () => { const options = { label: readSnapshot() }; console.log(options); }; return ; };\n',
+ options: {
+ filename: "src/nested-event-allocation.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "actually-pure-import-still-conservatively-abstains",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/actually-pure-import-still-conservatively-abstains.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "imported-function-value-without-evaluation",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { read: readSnapshot };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/imported-function-value-without-evaluation.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "two-readonly-module-callee-aliases",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nconst firstReader = (readSnapshot);\nconst secondReader = firstReader;\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: secondReader() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/two-readonly-module-callee-aliases.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "type-only-import-is-not-runtime-value-evidence",
+ code: 'import { useState } from "react";\nimport type readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/type-only-import-is-not-runtime-value-evidence.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "imported-constructor-outside-bare-call-v1",
+ code: 'import { useState } from "react";\nimport Snapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { value: new Snapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/imported-constructor-outside-bare-call-v1.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "statically-dead-imported-call-conservative-policy",
+ code: 'import { useState } from "react";\nimport readSnapshot from "./state-reader";\nexport const useSettings = () => {\n const [enabled] = useState(true);\n const options = { label: false && readSnapshot() };\n return { options, enabled };\n};\n',
+ options: {
+ filename: "src/statically-dead-imported-call-conservative-policy.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "inline-type-only-specifier-is-not-value-evidence",
+ code: 'import { type readSnapshot } from "./state-reader";\n\nexport const useSettings = () => {\n const options = { label: readSnapshot() };\n return options;\n};\n',
+ options: {
+ filename: "src/inline-type-only-specifier-is-not-value-evidence.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "bare-namespace-import-is-outside-call-policy",
+ code: 'import * as state from "./state-reader";\n\nexport const useSettings = () => {\n const options = { label: state() };\n return options;\n};\n',
+ options: {
+ filename: "src/bare-namespace-import-is-outside-call-policy.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "module-let-callee-alias-is-not-const",
+ code: 'import readSnapshot from "./state-reader";\nlet selectedReader = readSnapshot;\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ options: {
+ filename: "src/module-let-callee-alias-is-not-const.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "forward-reference-alias-initializer-declines",
+ code: 'import readSnapshot from "./state-reader";\nconst firstReader = secondReader;\nconst secondReader = readSnapshot;\nexport const useSettings = () => {\n const options = { label: firstReader() };\n return options;\n};\n',
+ options: {
+ filename: "src/forward-reference-alias-initializer-declines.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "cyclic-const-aliases-decline",
+ code: "\nconst firstReader = secondReader;\nconst secondReader = firstReader;\nexport const useSettings = () => {\n const options = { label: firstReader() };\n return options;\n};\n",
+ options: {
+ filename: "src/cyclic-const-aliases-decline.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "transparent-readonly-callee-aliases",
+ code: 'import readSnapshot from "./state-reader";\nconst firstReader = (readSnapshot as () => string);\nconst secondReader = firstReader!;\nexport const useSettings = () => {\n const options = { label: (secondReader)() };\n return options;\n};\n',
+ options: {
+ filename: "src/transparent-readonly-callee-aliases.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "named-default-value-import",
+ code: 'import { default as readSnapshot } from "./state-reader";\n\nexport const useSettings = () => {\n const options = { label: readSnapshot() };\n return options;\n};\n',
+ options: {
+ filename: "src/named-default-value-import.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "optional-imported-call-remains-outside-v1",
+ code: 'import readSnapshot from "./state-reader";\n\nexport const useSettings = () => {\n const options = { label: readSnapshot?.() };\n return options;\n};\n',
+ options: {
+ filename: "src/optional-imported-call-remains-outside-v1.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "tagged-imported-call-remains-outside-v1",
+ code: 'import readSnapshot from "./state-reader";\n\nexport const useSettings = () => {\n const options = { label: readSnapshot`label` };\n return options;\n};\n',
+ options: {
+ filename: "src/tagged-imported-call-remains-outside-v1.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "module-local-callee-shadow-is-not-import",
+ code: 'import { readSnapshot as providerReader } from "./state-reader";\nconst readSnapshot = () => "fixed";\nexport const useSettings = () => {\n const options = { label: readSnapshot() };\n return options;\n};\n',
+ options: {
+ filename: "src/module-local-callee-shadow-is-not-import.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+ {
+ name: "runtime-import-declaration-is-hoisted",
+ code: '\n\nexport const useSettings = () => {\n const options = { label: readSnapshot() };\n return options;\n};\nimport readSnapshot from "./state-reader";\n',
+ options: {
+ filename: "src/runtime-import-declaration-is-hoisted.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 0,
+ },
+ {
+ name: "const-alias-write-reference-declines",
+ code: 'import readSnapshot from "./state-reader";\nconst selectedReader = readSnapshot;\nselectedReader = () => "replacement";\nexport const useSettings = () => {\n const options = { label: selectedReader() };\n return options;\n};\n',
+ options: {
+ filename: "src/const-alias-write-reference-declines.tsx",
+ forceJsx: false,
+ includeLocations: true,
+ settings: { "react-doctor": { portedRuleMode: "curated", capabilities: ["react"] } },
+ },
+ expectedCount: 1,
+ },
+];
+
+describe("imported value calls in static initializers", () => {
+ for (const testCase of cases) {
+ it(testCase.name, () => {
+ const result = runRule(preferModuleScopeStaticValue, testCase.code, testCase.options);
+ expect(result.parseErrors).toEqual([]);
+ expect(result.diagnostics).toHaveLength(testCase.expectedCount);
+ });
+ }
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value.ts b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value.ts
index a3912779ff..14f0ecac87 100644
--- a/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value.ts
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/rules/architecture/prefer-module-scope-static-value.ts
@@ -1,4 +1,5 @@
import { MUTATING_ARRAY_METHODS, MUTATING_COLLECTION_METHODS } from "../../constants/js.js";
+import { containsImportedValueCall } from "../../utils/contains-imported-value-call.js";
import { defineRule } from "../../utils/define-rule.js";
import { enclosingComponentOrHookScope } from "../../utils/enclosing-component-or-hook-scope.js";
import type { EsTreeNode } from "../../utils/es-tree-node.js";
@@ -343,6 +344,7 @@ export const preferModuleScopeStaticValue = defineRule({
// to recompute each render; hoisting would freeze them to a single
// module-load value and change observable behavior.
if (containsImpureExpression(initializer, context.scopes)) return;
+ if (containsImportedValueCall(initializer, context.scopes)) return;
// A binding consumed ONLY as the receiver of scalar lookups
// (`KEYS.includes(k)`) never escapes as a value — identity can't
// break memoized children, so the hoist advice is pure noise.
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.runtime-values.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.runtime-values.test.ts
new file mode 100644
index 0000000000..cd27c428fb
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.runtime-values.test.ts
@@ -0,0 +1,274 @@
+import { describe, expect, it } from "vite-plus/test";
+import { attachParentReferences } from "../../test-utils/attach-parent-references.js";
+import { parseFixture } from "../../test-utils/parse-fixture.js";
+import type { EsTreeNode } from "../utils/es-tree-node.js";
+import { walkAst } from "../utils/walk-ast.js";
+import { analyzeScopes } from "./scope-analysis.js";
+import type {
+ ReferenceDescriptor,
+ ReferenceFlag,
+ SymbolDescriptor,
+ SymbolKind,
+} from "./scope-analysis.js";
+
+interface RuntimeResolutionCase {
+ name: string;
+ code: string;
+ expectedKind: SymbolKind | null;
+ flags: ReferenceFlag[];
+}
+
+const cases: RuntimeResolutionCase[] = [
+ {
+ name: "destructured parameter beneath interface",
+ code: "function read({subject}){interface subject{} consume(subject);}",
+ expectedKind: "parameter",
+ flags: ["read"],
+ },
+ {
+ name: "computed key const beneath interface",
+ code: "const subject='useState'; interface subject{} Object.defineProperty(React,subject,{});",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "const before interface",
+ code: "const subject=1; interface subject{} consume(subject);",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "interface before const",
+ code: "interface subject{} const subject=1; consume(subject);",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "const before type alias",
+ code: "const subject=1; type subject=unknown; consume(subject);",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "outer const beneath inner interface",
+ code: "const subject=1; function read(){interface subject{} consume(subject);}",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "outer const beneath inner type alias",
+ code: "const subject=1; function read(){type subject=unknown; consume(subject);}",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "outer const beneath empty namespace",
+ code: "const subject=1; namespace Outer{namespace subject{} consume(subject);}",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "outer const beneath type-only namespace",
+ code: "const subject=1; namespace Outer{namespace subject{export interface Marker{}} consume(subject);}",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "outer const beneath ambient value",
+ code: "const subject=1; function read(){declare const subject:unknown; consume(subject);}",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "parameter beneath inner interface",
+ code: "function read(subject){interface subject{} consume(subject);}",
+ expectedKind: "parameter",
+ flags: ["read"],
+ },
+ {
+ name: "parameter beneath inner type alias",
+ code: "function read(subject){type subject=unknown; consume(subject);}",
+ expectedKind: "parameter",
+ flags: ["read"],
+ },
+ {
+ name: "parameter writes beneath interface",
+ code: "function read(subject){interface subject{} subject=2; consume(subject);}",
+ expectedKind: "parameter",
+ flags: ["write", "read"],
+ },
+ {
+ name: "nested parameter writes beneath interface",
+ code: "function read(subject){function update(){interface subject{} subject+=2;} consume(subject);}",
+ expectedKind: "parameter",
+ flags: ["read-write", "read"],
+ },
+ {
+ name: "nearest real parameter shadows outer const",
+ code: "const subject=1; function read(subject){interface subject{} consume(subject);}",
+ expectedKind: "parameter",
+ flags: ["read"],
+ },
+ {
+ name: "default import beneath interface",
+ code: "import subject from 'provider'; interface subject{} consume(subject);",
+ expectedKind: "import",
+ flags: ["read"],
+ },
+ {
+ name: "namespace import beneath nested interface",
+ code: "import * as subject from 'provider'; function read(){interface subject{} consume(subject);}",
+ expectedKind: "import",
+ flags: ["read"],
+ },
+ {
+ name: "named import beneath nested type alias",
+ code: "import {subject} from 'provider'; function read(){type subject=unknown; consume(subject);}",
+ expectedKind: "import",
+ flags: ["read"],
+ },
+ {
+ name: "named import beneath type-only namespace",
+ code: "import {subject} from 'provider'; namespace Outer{namespace subject{export type Marker=unknown;} consume(subject);}",
+ expectedKind: "import",
+ flags: ["read"],
+ },
+ {
+ name: "import write beneath nested interface",
+ code: "import {subject} from 'provider'; function update(){interface subject{} subject=other;}",
+ expectedKind: "import",
+ flags: ["write"],
+ },
+ {
+ name: "function beneath merged interface",
+ code: "function subject(){} interface subject{} consume(subject);",
+ expectedKind: "function",
+ flags: ["read"],
+ },
+ {
+ name: "class beneath merged interface",
+ code: "class subject{} interface subject{} consume(subject);",
+ expectedKind: "class",
+ flags: ["read"],
+ },
+ {
+ name: "runtime namespace beneath interface",
+ code: "namespace subject{export const present=1;} interface subject{} consume(subject);",
+ expectedKind: "ts-module",
+ flags: ["read"],
+ },
+ {
+ name: "latest real var declaration wins",
+ code: "var subject; var subject; interface subject{} consume(subject);",
+ expectedKind: "var",
+ flags: ["read"],
+ },
+ {
+ name: "value import-equals stays runtime",
+ code: "import subject=require('provider'); interface subject{} consume(subject);",
+ expectedKind: "ts-import-equals",
+ flags: ["read"],
+ },
+ {
+ name: "interface-only fallback stays bound",
+ code: "interface subject{} consume(subject);",
+ expectedKind: "ts-interface",
+ flags: ["read"],
+ },
+ {
+ name: "nearest erased fallback stays bound",
+ code: "type subject=unknown; function read(){interface subject{} consume(subject);}",
+ expectedKind: "ts-interface",
+ flags: ["read"],
+ },
+ {
+ name: "type-alias-only fallback stays bound",
+ code: "type subject=unknown; consume(subject);",
+ expectedKind: "ts-type-alias",
+ flags: ["read"],
+ },
+ {
+ name: "type-only import fallback stays bound",
+ code: "import type {subject} from 'provider'; consume(subject);",
+ expectedKind: "import",
+ flags: ["read"],
+ },
+ {
+ name: "type-only import-equals fallback stays bound",
+ code: "import type subject=require('provider'); consume(subject);",
+ expectedKind: "ts-import-equals",
+ flags: ["read"],
+ },
+ {
+ name: "ambient-only fallback stays bound",
+ code: "declare const subject:unknown; consume(subject);",
+ expectedKind: "const",
+ flags: ["read"],
+ },
+ {
+ name: "empty namespace fallback stays bound",
+ code: "namespace subject{} consume(subject);",
+ expectedKind: "ts-module",
+ flags: ["read"],
+ },
+ {
+ name: "type-only namespace fallback stays bound",
+ code: "namespace subject{export type Marker=unknown;} consume(subject);",
+ expectedKind: "ts-module",
+ flags: ["read"],
+ },
+ {
+ name: "unrelated inner values do not replace erased fallback",
+ code: "interface subject{} function unrelated(){const subject=1;} consume(subject);",
+ expectedKind: "ts-interface",
+ flags: ["read"],
+ },
+ {
+ name: "unbound value stays global",
+ code: "consume(subject);",
+ expectedKind: null,
+ flags: ["read"],
+ },
+];
+
+describe("runtime reference resolution across erased declarations", () => {
+ for (const scenario of cases) {
+ it(scenario.name, () => {
+ const parsed = parseFixture(scenario.code);
+ expect(parsed.errors).toEqual([]);
+ attachParentReferences(parsed.program);
+ const analysis = analyzeScopes(parsed.program);
+ const references: ReferenceDescriptor[] = [];
+ walkAst(parsed.program, (node) => {
+ if (node.type !== "Identifier" || node.name !== "subject") return;
+ const reference = analysis.referenceFor(node);
+ if (reference) references.push(reference);
+ });
+ expect(references.map((reference) => reference.flag)).toEqual(scenario.flags);
+ const resolved = references[0]?.resolvedSymbol ?? null;
+ expect(resolved?.kind ?? null).toBe(scenario.expectedKind);
+ for (const reference of references) {
+ expect(analysis.symbolFor(reference.identifier)).toBe(resolved);
+ expect(analysis.isGlobalReference(reference.identifier)).toBe(resolved === null);
+ if (resolved) expect(resolved.references).toContain(reference);
+ }
+ const bindingsByIdentifier = new Map();
+ const pending = [analysis.rootScope];
+ while (pending.length) {
+ const scope = pending.pop();
+ if (!scope) continue;
+ pending.push(...scope.children);
+ for (const binding of scope.symbols) {
+ const previous = bindingsByIdentifier.get(binding.bindingIdentifier);
+ if (!previous || previous.id < binding.id)
+ bindingsByIdentifier.set(binding.bindingIdentifier, binding);
+ expect(scope.symbolsByName.get(binding.name)?.id).toBe(
+ scope.symbols.filter((symbol) => symbol.name === binding.name).at(-1)?.id,
+ );
+ }
+ }
+ for (const [identifier, binding] of bindingsByIdentifier)
+ expect(analysis.symbolFor(identifier)?.id).toBe(binding.id);
+ });
+ }
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts
index 08f6718178..a7a51a0e5c 100644
--- a/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/semantic/scope-analysis.ts
@@ -4,6 +4,7 @@ import { getValuePositionChildKeys } from "../utils/get-value-position-child-key
import { isAstNode } from "../utils/is-ast-node.js";
import { isFunctionLike } from "../utils/is-function-like.js";
import { isNodeOfType } from "../utils/is-node-of-type.js";
+import { isErasedTypeScriptBinding } from "../utils/is-erased-typescript-binding.js";
// Scope analyzer — per-file walker building a scope tree, symbol
// table, and identifier reference resolution. Mirrors the subset of
@@ -856,24 +857,35 @@ const walkValuePositionChildren = (node: EsTreeNode, state: BuilderState): void
}
};
-// Resolution pass: for each scope's references, walk up the scope
-// chain to find the first scope that binds the same name. Set
-// `resolvedSymbol` and append to the symbol's `references` list.
+// Resolve runtime values first, retaining the nearest erased binding
+// when the scope chain contains no runtime value with that name.
const resolveReferences = (rootScope: ScopeDescriptor): void => {
+ const runtimeSymbolsByScope = new Map>();
const visitScope = (scope: ScopeDescriptor): void => {
+ const runtimeSymbols = new Map();
+ for (const symbol of scope.symbols) {
+ if (!isErasedTypeScriptBinding(symbol)) runtimeSymbols.set(symbol.name, symbol);
+ }
+ runtimeSymbolsByScope.set(scope, runtimeSymbols);
for (const reference of scope.references) {
const name = (reference.identifier as { name?: string }).name;
if (typeof name !== "string") continue;
let lookup: ScopeDescriptor | null = scope;
+ let nearestErasedSymbol: SymbolDescriptor | null = null;
while (lookup) {
- const found = lookup.symbolsByName.get(name);
+ const found = runtimeSymbolsByScope.get(lookup)?.get(name);
if (found) {
reference.resolvedSymbol = found;
found.references.push(reference);
break;
}
+ nearestErasedSymbol ??= lookup.symbolsByName.get(name) ?? null;
lookup = lookup.parent;
}
+ if (!reference.resolvedSymbol && nearestErasedSymbol) {
+ reference.resolvedSymbol = nearestErasedSymbol;
+ nearestErasedSymbol.references.push(reference);
+ }
}
for (const child of scope.children) visitScope(child);
};
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/contains-imported-value-call.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/contains-imported-value-call.test.ts
new file mode 100644
index 0000000000..96e8c8412b
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/contains-imported-value-call.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it } from "vite-plus/test";
+import { analyzeScopes } from "../semantic/scope-analysis.js";
+import { attachParentReferences } from "../../test-utils/attach-parent-references.js";
+import { parseFixture } from "../../test-utils/parse-fixture.js";
+import { containsImportedValueCall } from "./contains-imported-value-call.js";
+import type { EsTreeNode } from "./es-tree-node.js";
+import { isNodeOfType } from "./is-node-of-type.js";
+import { walkAst } from "./walk-ast.js";
+
+describe("imported alias origin cache declaration order", () => {
+ for (const reverse of [false, true]) {
+ it(`keeps per-use order after ${reverse ? "late" : "early"} use warms the origin`, () => {
+ const parsed = parseFixture(`import readSnapshot from './reader';
+ const early = {value: selectedReader()};
+ const selectedReader = readSnapshot;
+ const late = {value: selectedReader()};`);
+ attachParentReferences(parsed.program);
+ const scopes = analyzeScopes(parsed.program);
+ const initializers: EsTreeNode[] = [];
+ walkAst(parsed.program, (node) => {
+ if (
+ isNodeOfType(node, "VariableDeclarator") &&
+ isNodeOfType(node.id, "Identifier") &&
+ (node.id.name === "early" || node.id.name === "late") &&
+ node.init
+ )
+ initializers.push(node.init);
+ });
+ expect(parsed.errors).toEqual([]);
+ expect(initializers).toHaveLength(2);
+ const ordered = reverse ? [...initializers].reverse() : initializers;
+ const expected = reverse ? [true, false] : [false, true];
+ expect(ordered.map((expression) => containsImportedValueCall(expression, scopes))).toEqual(
+ expected,
+ );
+ expect(
+ [...ordered].reverse().map((expression) => containsImportedValueCall(expression, scopes)),
+ ).toEqual([...expected].reverse());
+ expect(ordered.map((expression) => containsImportedValueCall(expression, scopes))).toEqual(
+ expected,
+ );
+ });
+ }
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/contains-imported-value-call.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/contains-imported-value-call.ts
new file mode 100644
index 0000000000..d49bea0d3b
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/contains-imported-value-call.ts
@@ -0,0 +1,73 @@
+import type { ScopeAnalysis, SymbolDescriptor } from "../semantic/scope-analysis.js";
+import type { EsTreeNode } from "./es-tree-node.js";
+import { getDirectConstInitializer } from "./get-direct-const-initializer.js";
+import { getValueImportDeclaration } from "./get-value-import-declaration.js";
+import { isMemberMutationTarget } from "./is-member-mutation-target.js";
+import { isNodeOfType } from "./is-node-of-type.js";
+import { stripParenExpression } from "./strip-paren-expression.js";
+import { walkAst } from "./walk-ast.js";
+
+const readonlySymbols = new WeakMap();
+const importedOrigins = new WeakMap();
+
+const getReadonlyCalleeSymbol = (
+ callee: EsTreeNode,
+ scopes: ScopeAnalysis,
+): SymbolDescriptor | null => {
+ if (!isNodeOfType(callee, "Identifier")) return null;
+ const reference = scopes.referenceFor(callee);
+ const symbol = reference?.resolvedSymbol;
+ if (!symbol || reference.flag !== "read") return null;
+ let isReadonly = readonlySymbols.get(symbol);
+ if (isReadonly === undefined) {
+ isReadonly = symbol.references.every(
+ (candidateReference) =>
+ candidateReference.flag === "read" &&
+ !isMemberMutationTarget(candidateReference.identifier),
+ );
+ readonlySymbols.set(symbol, isReadonly);
+ }
+ if (!isReadonly) return null;
+ if (getValueImportDeclaration(symbol)) return symbol;
+ return getDirectConstInitializer(symbol) && symbol.declarationNode.range[1] <= callee.range[0]
+ ? symbol
+ : null;
+};
+
+const hasImportedOrigin = (symbol: SymbolDescriptor, scopes: ScopeAnalysis): boolean => {
+ const visitedSymbols = new Set();
+ let currentSymbol: SymbolDescriptor | null = symbol;
+ let hasValueImport = false;
+ while (currentSymbol && !visitedSymbols.has(currentSymbol)) {
+ const cachedOrigin = importedOrigins.get(currentSymbol);
+ if (cachedOrigin !== undefined) {
+ hasValueImport = cachedOrigin;
+ break;
+ }
+ visitedSymbols.add(currentSymbol);
+ if (getValueImportDeclaration(currentSymbol)) {
+ hasValueImport = !isNodeOfType(currentSymbol.declarationNode, "ImportNamespaceSpecifier");
+ break;
+ }
+ const initializer = getDirectConstInitializer(currentSymbol);
+ currentSymbol = initializer
+ ? getReadonlyCalleeSymbol(stripParenExpression(initializer), scopes)
+ : null;
+ }
+ for (const visitedSymbol of visitedSymbols) importedOrigins.set(visitedSymbol, hasValueImport);
+ return hasValueImport;
+};
+
+export const containsImportedValueCall = (
+ expression: EsTreeNode,
+ scopes: ScopeAnalysis,
+): boolean => {
+ let foundImportedCall = false;
+ walkAst(expression, (node) => {
+ if (foundImportedCall) return false;
+ if (!isNodeOfType(node, "CallExpression") || node.optional) return;
+ const symbol = getReadonlyCalleeSymbol(stripParenExpression(node.callee), scopes);
+ if (symbol) foundImportedCall = hasImportedOrigin(symbol, scopes);
+ });
+ return foundImportedCall;
+};
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/has-runtime-value-binding.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/has-runtime-value-binding.test.ts
new file mode 100644
index 0000000000..fbefa50736
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/has-runtime-value-binding.test.ts
@@ -0,0 +1,111 @@
+import { describe, expect, it } from "vite-plus/test";
+import { attachParentReferences } from "../../test-utils/attach-parent-references.js";
+import { parseFixture } from "../../test-utils/parse-fixture.js";
+import { analyzeScopes } from "../semantic/scope-analysis.js";
+import { hasRuntimeValueBinding } from "./has-runtime-value-binding.js";
+
+describe("hasRuntimeValueBinding", () => {
+ it.each([
+ { code: "namespace candidate {}", expected: false, nested: false },
+ { code: "namespace candidate { interface Marker {} }", expected: false, nested: false },
+ { code: "namespace candidate { export interface Marker {} }", expected: false, nested: false },
+ { code: "namespace candidate { type Marker = string; }", expected: false, nested: false },
+ {
+ code: "namespace candidate { export type Marker = string; }",
+ expected: false,
+ nested: false,
+ },
+ {
+ code: "namespace candidate { export namespace Nested { export interface Marker {} } }",
+ expected: false,
+ nested: false,
+ },
+ { code: "namespace candidate { export const marker = 0; }", expected: true, nested: false },
+ { code: "namespace candidate { const marker = 0; }", expected: true, nested: false },
+ { code: "namespace candidate { export function marker() {} }", expected: true, nested: false },
+ { code: "namespace candidate { export class Marker {} }", expected: true, nested: false },
+ { code: "namespace candidate { export enum Marker { Value } }", expected: true, nested: false },
+ {
+ code: "namespace candidate { export namespace Nested { export const marker = 0; } }",
+ expected: true,
+ nested: false,
+ },
+ {
+ code: "namespace candidate { type Marker = string; export const marker = 0; }",
+ expected: true,
+ nested: false,
+ },
+ { code: "namespace candidate {;}", expected: true, nested: false },
+ {
+ code: "namespace candidate { declare const marker: unknown; }",
+ expected: true,
+ nested: false,
+ },
+ {
+ code: "namespace candidate { export declare const marker: unknown; }",
+ expected: true,
+ nested: false,
+ },
+ { code: "namespace candidate { import Alias = Some.Value; }", expected: false, nested: false },
+ {
+ code: "namespace candidate { export import Alias = Some.Value; }",
+ expected: true,
+ nested: false,
+ },
+ {
+ code: "function candidate() {} namespace candidate { export interface Marker {} }",
+ expected: true,
+ nested: false,
+ },
+ { code: "interface candidate {}", expected: false, nested: false },
+ { code: "type candidate = unknown;", expected: false, nested: false },
+ { code: "declare const candidate: unknown;", expected: false, nested: false },
+ { code: "declare class candidate {}", expected: false, nested: false },
+ { code: 'import type candidate from "provider";', expected: false, nested: false },
+ { code: 'import type candidate = require("provider");', expected: false, nested: false },
+ { code: "const candidate = 0;", expected: true, nested: false },
+ { code: "let candidate = 0;", expected: true, nested: false },
+ { code: "var candidate = 0;", expected: true, nested: false },
+ { code: "const candidate = 0; interface candidate {}", expected: true, nested: false },
+ { code: "interface candidate {} const candidate = 0;", expected: true, nested: false },
+ { code: "const candidate = 0; type candidate = unknown;", expected: true, nested: false },
+ { code: "class candidate {} interface candidate {}", expected: true, nested: false },
+ { code: "function candidate() {} interface candidate {}", expected: true, nested: false },
+ { code: 'import candidate from "provider";', expected: true, nested: false },
+ { code: 'import candidate = require("provider");', expected: true, nested: false },
+ {
+ code: "const candidate = 0; function owner() { interface candidate {} }",
+ expected: true,
+ nested: true,
+ },
+ {
+ code: "interface candidate {} function owner() { const candidate = 0; }",
+ expected: false,
+ nested: false,
+ },
+ {
+ code: "declare const candidate: unknown; function owner() { interface candidate {} }",
+ expected: false,
+ nested: true,
+ },
+ {
+ code: "function owner(candidate: unknown) { interface candidate {} }",
+ expected: true,
+ nested: true,
+ },
+ { code: "const unrelated = 0; interface candidate {}", expected: false, nested: false },
+ ])("resolves $code", ({ code, expected, nested }) => {
+ const parsed = parseFixture(code);
+ expect(parsed.errors).toEqual([]);
+ attachParentReferences(parsed.program);
+ const scopes = analyzeScopes(parsed.program);
+ const bindingScope = nested
+ ? scopes.rootScope.children.find((scope) => scope.kind === "function")
+ : scopes.rootScope;
+ const symbol = bindingScope?.symbols.findLast((binding) => binding.name === "candidate");
+ expect(symbol).toBeDefined();
+ if (!symbol) throw new Error("Missing fixture binding");
+ expect(hasRuntimeValueBinding(symbol)).toBe(expected);
+ expect(hasRuntimeValueBinding(symbol)).toBe(expected);
+ });
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/has-runtime-value-binding.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/has-runtime-value-binding.ts
new file mode 100644
index 0000000000..0fdd76042e
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/has-runtime-value-binding.ts
@@ -0,0 +1,23 @@
+import type { ScopeDescriptor, SymbolDescriptor } from "../semantic/scope-analysis.js";
+import { isErasedTypeScriptBinding } from "./is-erased-typescript-binding.js";
+
+const runtimeValueBindings = new WeakMap();
+
+export const hasRuntimeValueBinding = (symbol: SymbolDescriptor): boolean => {
+ const cached = runtimeValueBindings.get(symbol);
+ if (cached !== undefined) return cached;
+ let scope: ScopeDescriptor | null = symbol.scope;
+ while (scope) {
+ if (
+ scope.symbols.some(
+ (candidate) => candidate.name === symbol.name && !isErasedTypeScriptBinding(candidate),
+ )
+ ) {
+ runtimeValueBindings.set(symbol, true);
+ return true;
+ }
+ scope = scope.parent;
+ }
+ runtimeValueBindings.set(symbol, false);
+ return false;
+};
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-binding.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-binding.test.ts
new file mode 100644
index 0000000000..48c3d151dc
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-binding.test.ts
@@ -0,0 +1,64 @@
+import { describe, expect, it } from "vite-plus/test";
+import { attachParentReferences } from "../../test-utils/attach-parent-references.js";
+import { parseFixture } from "../../test-utils/parse-fixture.js";
+import { analyzeScopes } from "../semantic/scope-analysis.js";
+import { isErasedTypeScriptBinding } from "./is-erased-typescript-binding.js";
+
+describe("isErasedTypeScriptBinding", () => {
+ it.each([
+ { code: "namespace candidate {}", expected: true },
+ { code: "namespace candidate { interface Marker {} }", expected: true },
+ { code: "namespace candidate { export interface Marker {} }", expected: true },
+ { code: "namespace candidate { type Marker = string; }", expected: true },
+ { code: "namespace candidate { export type Marker = string; }", expected: true },
+ {
+ code: "namespace candidate { export namespace Nested { export interface Marker {} } }",
+ expected: true,
+ },
+ { code: "namespace candidate { export const marker = 0; }", expected: false },
+ { code: "namespace candidate { const marker = 0; }", expected: false },
+ { code: "namespace candidate { export function marker() {} }", expected: false },
+ { code: "namespace candidate { export class Marker {} }", expected: false },
+ { code: "namespace candidate { export enum Marker { Value } }", expected: false },
+ {
+ code: "namespace candidate { export namespace Nested { export const marker = 0; } }",
+ expected: false,
+ },
+ {
+ code: "namespace candidate { type Marker = string; export const marker = 0; }",
+ expected: false,
+ },
+ { code: "namespace candidate {;}", expected: false },
+ { code: "namespace candidate { declare const marker: unknown; }", expected: false },
+ { code: "namespace candidate { export declare const marker: unknown; }", expected: false },
+ { code: "namespace candidate { import Alias = Some.Value; }", expected: true },
+ { code: "namespace candidate { export import Alias = Some.Value; }", expected: false },
+ { code: "declare const candidate: unknown;", expected: true },
+ { code: "declare let candidate: unknown;", expected: true },
+ { code: "declare var candidate: unknown;", expected: true },
+ { code: "declare class candidate {}", expected: true },
+ { code: "export declare const candidate: unknown;", expected: true },
+ { code: "interface candidate {}", expected: true },
+ { code: "type candidate = unknown;", expected: true },
+ { code: 'import type candidate from "provider";', expected: true },
+ { code: 'import type * as candidate from "provider";', expected: true },
+ { code: 'import { type candidate } from "provider";', expected: true },
+ { code: 'import type candidate = require("provider");', expected: true },
+ { code: 'import candidate = require("provider");', expected: false },
+ { code: "const candidate = 0;", expected: false },
+ { code: "let candidate = 0;", expected: false },
+ { code: "var candidate = 0;", expected: false },
+ { code: "class candidate {}", expected: false },
+ { code: "function candidate() {}", expected: false },
+ { code: 'import { candidate } from "provider";', expected: false },
+ { code: 'import candidate from "provider";', expected: false },
+ { code: 'import * as candidate from "provider";', expected: false },
+ ])("classifies $code", ({ code, expected }) => {
+ const parsed = parseFixture(code);
+ expect(parsed.errors).toEqual([]);
+ attachParentReferences(parsed.program);
+ const scopes = analyzeScopes(parsed.program);
+ const symbol = scopes.rootScope.symbols.find((binding) => binding.name === "candidate");
+ expect(symbol && isErasedTypeScriptBinding(symbol)).toBe(expected);
+ });
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-binding.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-binding.ts
new file mode 100644
index 0000000000..2b535d26d5
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-binding.ts
@@ -0,0 +1,22 @@
+import type { SymbolDescriptor } from "../semantic/scope-analysis.js";
+import { isErasedTypeScriptNamespace } from "./is-erased-typescript-namespace.js";
+import { getValueImportDeclaration } from "./get-value-import-declaration.js";
+
+export const isErasedTypeScriptBinding = (symbol: SymbolDescriptor): boolean => {
+ if (symbol.kind === "ts-interface" || symbol.kind === "ts-type-alias") return true;
+ if (symbol.kind === "import") return getValueImportDeclaration(symbol) === null;
+ if (
+ symbol.kind === "ts-import-equals" &&
+ "importKind" in symbol.declarationNode &&
+ symbol.declarationNode.importKind === "type"
+ )
+ return true;
+ if (symbol.kind === "ts-module" && isErasedTypeScriptNamespace(symbol.declarationNode))
+ return true;
+ let declaration = symbol.declarationNode;
+ while (declaration.parent) {
+ if ("declare" in declaration && declaration.declare === true) return true;
+ declaration = declaration.parent;
+ }
+ return false;
+};
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-namespace.test.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-namespace.test.ts
new file mode 100644
index 0000000000..461e7b930e
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-namespace.test.ts
@@ -0,0 +1,19 @@
+import { describe, expect, it } from "vite-plus/test";
+import { parseFixture } from "../../test-utils/parse-fixture.js";
+import { isNodeOfType } from "./is-node-of-type.js";
+import { isErasedTypeScriptNamespace } from "./is-erased-typescript-namespace.js";
+
+describe("dotted TypeScript namespace erasure", () => {
+ it.each([
+ { code: "namespace candidate.Nested {}", expected: true },
+ { code: "namespace candidate.Nested { export interface Marker {} }", expected: true },
+ { code: "namespace candidate.Nested { export type Marker = string; }", expected: true },
+ { code: "namespace candidate.Nested { export const marker = 0; }", expected: false },
+ ])("classifies $code", ({ code, expected }) => {
+ const parsed = parseFixture(code);
+ expect(parsed.errors).toEqual([]);
+ if (!isNodeOfType(parsed.program, "Program")) throw new Error("Expected namespace program");
+ const namespace = parsed.program.body[0];
+ expect(namespace && isErasedTypeScriptNamespace(namespace)).toBe(expected);
+ });
+});
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-namespace.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-namespace.ts
new file mode 100644
index 0000000000..a1cfc39812
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-erased-typescript-namespace.ts
@@ -0,0 +1,27 @@
+import type { EsTreeNode } from "./es-tree-node.js";
+import { isNodeOfType } from "./is-node-of-type.js";
+
+const erasedNamespaces = new WeakMap();
+
+export const isErasedTypeScriptNamespace = (node: EsTreeNode): boolean => {
+ const cached = erasedNamespaces.get(node);
+ if (cached !== undefined) return cached;
+ let isErased = false;
+ if (isNodeOfType(node, "TSInterfaceDeclaration") || isNodeOfType(node, "TSTypeAliasDeclaration"))
+ isErased = true;
+ else if (isNodeOfType(node, "TSImportEqualsDeclaration"))
+ isErased =
+ node.importKind === "type" ||
+ !isNodeOfType(node.moduleReference, "TSExternalModuleReference");
+ else if (isNodeOfType(node, "ExportNamedDeclaration"))
+ isErased = isNodeOfType(node.declaration, "TSImportEqualsDeclaration")
+ ? node.declaration.importKind === "type"
+ : Boolean(node.declaration && isErasedTypeScriptNamespace(node.declaration));
+ else if (isNodeOfType(node, "TSModuleDeclaration") && node.body)
+ isErased = isNodeOfType(node.body, "TSModuleDeclaration")
+ ? isErasedTypeScriptNamespace(node.body)
+ : isNodeOfType(node.body, "TSModuleBlock") &&
+ node.body.body.every(isErasedTypeScriptNamespace);
+ erasedNamespaces.set(node, isErased);
+ return isErased;
+};
diff --git a/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-member-mutation-target.ts b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-member-mutation-target.ts
new file mode 100644
index 0000000000..6d2fade0ee
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/plugin/utils/is-member-mutation-target.ts
@@ -0,0 +1,38 @@
+import type { EsTreeNode } from "./es-tree-node.js";
+import { findTransparentExpressionRoot } from "./find-transparent-expression-root.js";
+import { isNodeOfType } from "./is-node-of-type.js";
+
+export const isMemberMutationTarget = (memberExpression: EsTreeNode): boolean => {
+ let currentNode = findTransparentExpressionRoot(memberExpression);
+ while (currentNode.parent) {
+ const parentNode = currentNode.parent;
+ if (isNodeOfType(parentNode, "AssignmentExpression")) {
+ return parentNode.left === currentNode;
+ }
+ if (
+ isNodeOfType(parentNode, "UpdateExpression") ||
+ (isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete")
+ ) {
+ return parentNode.argument === currentNode;
+ }
+ if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) {
+ return parentNode.left === currentNode;
+ }
+ if (isNodeOfType(parentNode, "Property")) {
+ if (parentNode.value !== currentNode || !isNodeOfType(parentNode.parent, "ObjectPattern")) {
+ return false;
+ }
+ } else if (isNodeOfType(parentNode, "AssignmentPattern")) {
+ if (parentNode.left !== currentNode) return false;
+ } else if (isNodeOfType(parentNode, "RestElement")) {
+ if (parentNode.argument !== currentNode) return false;
+ } else if (
+ !isNodeOfType(parentNode, "ArrayPattern") &&
+ !isNodeOfType(parentNode, "ObjectPattern")
+ ) {
+ return false;
+ }
+ currentNode = findTransparentExpressionRoot(parentNode);
+ }
+ return false;
+};
diff --git a/packages/oxlint-plugin-react-doctor/src/test-utils/imported-call-proof/constants.ts b/packages/oxlint-plugin-react-doctor/src/test-utils/imported-call-proof/constants.ts
new file mode 100644
index 0000000000..10c577c84b
--- /dev/null
+++ b/packages/oxlint-plugin-react-doctor/src/test-utils/imported-call-proof/constants.ts
@@ -0,0 +1,3 @@
+export const IMPORTED_CALL_PROOF_COUNTS = [20, 100];
+export const IMPORTED_CALL_AST_READS_PER_NODE = 64;
+export const IMPORTED_CALL_REFERENCE_PASSES = 2;