oxlint-plugin-react-doctor 0.6.3-dev.8325343 → 0.6.3-dev.bdf8074
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.d.ts +81 -1
- package/dist/index.js +246 -11
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StaticImport } from "oxc-parser";
|
|
1
2
|
import { TSESTree } from "@typescript-eslint/types";
|
|
2
3
|
|
|
3
4
|
//#region src/plugin/utils/es-tree-node.d.ts
|
|
@@ -16644,6 +16645,85 @@ declare const MOTION_LIBRARY_PACKAGES: Set<string>;
|
|
|
16644
16645
|
//#region src/plugin/constants/cross-file-rule-ids.d.ts
|
|
16645
16646
|
declare const CROSS_FILE_RULE_IDS: ReadonlySet<string>;
|
|
16646
16647
|
//#endregion
|
|
16648
|
+
//#region src/plugin/utils/cross-file-probe-recorder.d.ts
|
|
16649
|
+
interface CrossFileProbeTrace {
|
|
16650
|
+
/** Paths whose existence classification (file / dir / none) was consulted. */
|
|
16651
|
+
readonly existencePaths: Set<string>;
|
|
16652
|
+
/** Paths whose content (bytes, exports, parse) was consulted. */
|
|
16653
|
+
readonly contentPaths: Set<string>;
|
|
16654
|
+
}
|
|
16655
|
+
//#endregion
|
|
16656
|
+
//#region src/plugin/cross-file-dependencies.d.ts
|
|
16657
|
+
/**
|
|
16658
|
+
* Per-rule cross-file dependency collectors — the foundation of the sidecar
|
|
16659
|
+
* lint cache's dependency fingerprints (`@react-doctor/core`,
|
|
16660
|
+
* `runners/oxlint/sidecar-lint-cache.ts`).
|
|
16661
|
+
*
|
|
16662
|
+
* A collector re-runs the SAME resolver helpers its rule calls at lint time
|
|
16663
|
+
* (import resolution, barrel following, ancestor-layout walks, package.json
|
|
16664
|
+
* classification) under the probe recorder
|
|
16665
|
+
* (`utils/cross-file-probe-recorder.ts`), so the recorded probe set is the
|
|
16666
|
+
* exact set of filesystem facts the rule's execution consulted — including
|
|
16667
|
+
* the negative probes (extension candidates that did NOT exist) that make
|
|
16668
|
+
* resolution shadowing detectable when a new file appears.
|
|
16669
|
+
*
|
|
16670
|
+
* SOUNDNESS INVARIANT — each collector's probe set must be a SUPERSET of
|
|
16671
|
+
* every filesystem read its rule can make while linting the file, for any
|
|
16672
|
+
* file content and any filesystem state:
|
|
16673
|
+
*
|
|
16674
|
+
* - Over-approximation is always safe (extra probes only cause a spurious
|
|
16675
|
+
* re-lint); under-approximation is forbidden (a missed probe could
|
|
16676
|
+
* replay a stale verdict).
|
|
16677
|
+
* - Where a rule gates its cross-file reads on in-file conditions, a
|
|
16678
|
+
* collector may skip the gate (probe more) or must mirror it EXACTLY
|
|
16679
|
+
* (probe the same). Each collector documents which it does per gate.
|
|
16680
|
+
* - The replay argument: the stored probe set is the complete read set of
|
|
16681
|
+
* the collector's execution at store time. If every stored probe has
|
|
16682
|
+
* the same answer on a later scan, re-executing the collector — and
|
|
16683
|
+
* therefore the rule, whose reads are a subset — reproduces the same
|
|
16684
|
+
* execution step by step (the file's own content is pinned separately
|
|
16685
|
+
* by the cache key's content hash), so the stored diagnostics are
|
|
16686
|
+
* exactly what a fresh sidecar lint would produce.
|
|
16687
|
+
*
|
|
16688
|
+
* A cross-file rule WITHOUT a collector here must be listed in
|
|
16689
|
+
* `UNBOUNDED_CROSS_FILE_RULE_IDS` — it then re-lints every file on every
|
|
16690
|
+
* scan (the sound fallback). `cross-file-rule-ids.test.ts` in
|
|
16691
|
+
* `@react-doctor/core` forces every `CROSS_FILE_RULE_IDS` entry into
|
|
16692
|
+
* exactly one of the two classifications.
|
|
16693
|
+
*/
|
|
16694
|
+
interface CrossFileDependencyCollectorInput {
|
|
16695
|
+
/** Absolute, forward-slash-normalized path of the file being fingerprinted. */
|
|
16696
|
+
readonly absoluteFilePath: string;
|
|
16697
|
+
readonly sourceText: string;
|
|
16698
|
+
/** oxc module record — the file's static import declarations. */
|
|
16699
|
+
readonly staticImports: ReadonlyArray<StaticImport>;
|
|
16700
|
+
/** Parsed program (no parent references attached). */
|
|
16701
|
+
readonly program: EsTreeNode;
|
|
16702
|
+
}
|
|
16703
|
+
type CrossFileDependencyCollector = (input: CrossFileDependencyCollectorInput) => void;
|
|
16704
|
+
declare const CROSS_FILE_DEPENDENCY_COLLECTORS: ReadonlyMap<string, CrossFileDependencyCollector>;
|
|
16705
|
+
/**
|
|
16706
|
+
* Cross-file rules whose dependency set CANNOT be soundly bounded — they are
|
|
16707
|
+
* excluded from fingerprinting and re-lint every file on every scan. Empty
|
|
16708
|
+
* today; a new cross-file rule must be added either here or to
|
|
16709
|
+
* `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
|
|
16710
|
+
* partition), forcing a conscious classification.
|
|
16711
|
+
*/
|
|
16712
|
+
declare const UNBOUNDED_CROSS_FILE_RULE_IDS: ReadonlySet<string>;
|
|
16713
|
+
/**
|
|
16714
|
+
* Runs the collectors for `ruleIds` over one file and returns every
|
|
16715
|
+
* filesystem probe they made — the file's cross-file dependency set.
|
|
16716
|
+
*
|
|
16717
|
+
* Returns `null` (caller must treat the file as unfingerprintable and always
|
|
16718
|
+
* re-lint it) when the file has a fatal parse error — an execution the
|
|
16719
|
+
* collectors cannot mirror — or when a requested rule has no collector.
|
|
16720
|
+
*/
|
|
16721
|
+
declare const collectCrossFileDependencyProbes: (input: {
|
|
16722
|
+
absoluteFilePath: string;
|
|
16723
|
+
sourceText: string;
|
|
16724
|
+
ruleIds: ReadonlyArray<string>;
|
|
16725
|
+
}) => CrossFileProbeTrace | null;
|
|
16726
|
+
//#endregion
|
|
16647
16727
|
//#region src/plugin/rules/security-scan/utils/classify-security-scan-file.d.ts
|
|
16648
16728
|
interface SecurityScanFileClassification {
|
|
16649
16729
|
readonly bucket: "priority" | "artifact" | "other";
|
|
@@ -16657,5 +16737,5 @@ declare const REACT_NATIVE_DEPENDENCY_NAMES: ReadonlySet<string>;
|
|
|
16657
16737
|
declare const REACT_NATIVE_DEPENDENCY_PREFIXES: ReadonlyArray<string>;
|
|
16658
16738
|
declare const isReactNativeDependencyName: (dependencyName: string) => boolean;
|
|
16659
16739
|
//#endregion
|
|
16660
|
-
export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, EXTERNAL_RULES, type EsTreeNode, FRAMEWORK_SPECIFIC_RULE_KEYS, type FileScan, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, type OxlintRuleSeverity, PREACT_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, type Rule, type RuleFramework, type RulePlugin, type RuleSeverity, type RuleVisitors, type ScanFinding, type ScannedFile, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, classifySecurityScanFile, plugin as default, isReactNativeDependencyName, shouldReadSecurityScanContent };
|
|
16740
|
+
export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, type CrossFileProbeTrace, EXTERNAL_RULES, type EsTreeNode, FRAMEWORK_SPECIFIC_RULE_KEYS, type FileScan, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, type OxlintRuleSeverity, PREACT_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, type Rule, type RuleFramework, type RulePlugin, type RuleSeverity, type RuleVisitors, type ScanFinding, type ScannedFile, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, UNBOUNDED_CROSS_FILE_RULE_IDS, classifySecurityScanFile, collectCrossFileDependencyProbes, plugin as default, isReactNativeDependencyName, shouldReadSecurityScanContent };
|
|
16661
16741
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -15564,6 +15564,30 @@ const nextjsInlineScriptMissingId = defineRule({
|
|
|
15564
15564
|
} })
|
|
15565
15565
|
});
|
|
15566
15566
|
//#endregion
|
|
15567
|
+
//#region src/plugin/utils/cross-file-probe-recorder.ts
|
|
15568
|
+
let activeProbeTrace = null;
|
|
15569
|
+
const recordExistenceProbe = (absolutePath) => {
|
|
15570
|
+
activeProbeTrace?.existencePaths.add(absolutePath);
|
|
15571
|
+
};
|
|
15572
|
+
const recordContentProbe = (absolutePath) => {
|
|
15573
|
+
activeProbeTrace?.contentPaths.add(absolutePath);
|
|
15574
|
+
};
|
|
15575
|
+
const isProbeRecorderActive = () => activeProbeTrace !== null;
|
|
15576
|
+
const collectCrossFileProbes = (collect) => {
|
|
15577
|
+
const previousTrace = activeProbeTrace;
|
|
15578
|
+
const trace = {
|
|
15579
|
+
existencePaths: /* @__PURE__ */ new Set(),
|
|
15580
|
+
contentPaths: /* @__PURE__ */ new Set()
|
|
15581
|
+
};
|
|
15582
|
+
activeProbeTrace = trace;
|
|
15583
|
+
try {
|
|
15584
|
+
collect();
|
|
15585
|
+
} finally {
|
|
15586
|
+
activeProbeTrace = previousTrace;
|
|
15587
|
+
}
|
|
15588
|
+
return trace;
|
|
15589
|
+
};
|
|
15590
|
+
//#endregion
|
|
15567
15591
|
//#region src/plugin/utils/parse-export-specifiers.ts
|
|
15568
15592
|
const getSpecifierName = (rawName) => rawName.replace(/^type\s+/, "").trim();
|
|
15569
15593
|
const parseExportSpecifiers = (specifiersText, declarationIsTypeOnly) => specifiersText.split(",").map((specifierText) => specifierText.trim()).filter(Boolean).map((specifierText) => {
|
|
@@ -15599,6 +15623,7 @@ const collectSourceTextExportNames = (sourceText) => {
|
|
|
15599
15623
|
};
|
|
15600
15624
|
const exportNamesCache = /* @__PURE__ */ new Map();
|
|
15601
15625
|
const doesModuleExportName = (filePath, exportedName) => {
|
|
15626
|
+
recordContentProbe(filePath);
|
|
15602
15627
|
try {
|
|
15603
15628
|
const fileStat = fs.statSync(filePath);
|
|
15604
15629
|
const cached = exportNamesCache.get(filePath);
|
|
@@ -16643,6 +16668,7 @@ const parseSourceText = (filename, sourceText) => {
|
|
|
16643
16668
|
};
|
|
16644
16669
|
const parseCache = /* @__PURE__ */ new Map();
|
|
16645
16670
|
const parseSourceFile = (absoluteFilePath) => {
|
|
16671
|
+
if (!(absoluteFilePath.endsWith(".d.ts") || absoluteFilePath.endsWith(".d.mts") || absoluteFilePath.endsWith(".d.cts"))) recordContentProbe(absoluteFilePath);
|
|
16646
16672
|
let fileStat;
|
|
16647
16673
|
try {
|
|
16648
16674
|
fileStat = fs.statSync(absoluteFilePath);
|
|
@@ -16789,15 +16815,27 @@ const classifyBarrelModule = (sourceText) => {
|
|
|
16789
16815
|
};
|
|
16790
16816
|
const getBarrelIndexModuleInfo = (filePath) => {
|
|
16791
16817
|
if (!isIndexModuleFilePath(filePath)) return createNonBarrelInfo();
|
|
16818
|
+
recordContentProbe(filePath);
|
|
16819
|
+
let fileStat;
|
|
16820
|
+
try {
|
|
16821
|
+
fileStat = fs.statSync(filePath);
|
|
16822
|
+
} catch {
|
|
16823
|
+
fileStat = null;
|
|
16824
|
+
}
|
|
16792
16825
|
const cachedResult = barrelIndexModuleInfoCache.get(filePath);
|
|
16793
|
-
if (cachedResult !== void 0) return cachedResult;
|
|
16826
|
+
if (cachedResult !== void 0 && fileStat !== null && cachedResult.mtimeMs === fileStat.mtimeMs && cachedResult.size === fileStat.size) return cachedResult.moduleInfo;
|
|
16827
|
+
if (fileStat === null) return createNonBarrelInfo();
|
|
16794
16828
|
let moduleInfo = createNonBarrelInfo();
|
|
16795
16829
|
try {
|
|
16796
16830
|
moduleInfo = classifyBarrelModule(fs.readFileSync(filePath, "utf8"));
|
|
16797
16831
|
} catch {
|
|
16798
16832
|
moduleInfo = createNonBarrelInfo();
|
|
16799
16833
|
}
|
|
16800
|
-
barrelIndexModuleInfoCache.set(filePath,
|
|
16834
|
+
barrelIndexModuleInfoCache.set(filePath, {
|
|
16835
|
+
mtimeMs: fileStat.mtimeMs,
|
|
16836
|
+
size: fileStat.size,
|
|
16837
|
+
moduleInfo
|
|
16838
|
+
});
|
|
16801
16839
|
return moduleInfo;
|
|
16802
16840
|
};
|
|
16803
16841
|
const isBarrelIndexModule = (filePath) => getBarrelIndexModuleInfo(filePath).isBarrel;
|
|
@@ -16826,6 +16864,7 @@ const PACKAGE_ENTRY_FIELDS = [
|
|
|
16826
16864
|
"browser"
|
|
16827
16865
|
];
|
|
16828
16866
|
const getExistingFilePath = (filePath) => {
|
|
16867
|
+
recordExistenceProbe(filePath);
|
|
16829
16868
|
try {
|
|
16830
16869
|
return fs.statSync(filePath).isFile() ? filePath : null;
|
|
16831
16870
|
} catch {
|
|
@@ -16833,6 +16872,7 @@ const getExistingFilePath = (filePath) => {
|
|
|
16833
16872
|
}
|
|
16834
16873
|
};
|
|
16835
16874
|
const getExistingDirectoryPath = (directoryPath) => {
|
|
16875
|
+
recordExistenceProbe(directoryPath);
|
|
16836
16876
|
try {
|
|
16837
16877
|
return fs.statSync(directoryPath).isDirectory() ? directoryPath : null;
|
|
16838
16878
|
} catch {
|
|
@@ -16888,6 +16928,7 @@ const resolvePackageDirectoryEntry = (directoryPath) => {
|
|
|
16888
16928
|
const existingDirectoryPath = getExistingDirectoryPath(directoryPath);
|
|
16889
16929
|
if (!existingDirectoryPath) return null;
|
|
16890
16930
|
const packageJsonPath = path.join(existingDirectoryPath, "package.json");
|
|
16931
|
+
recordContentProbe(packageJsonPath);
|
|
16891
16932
|
try {
|
|
16892
16933
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
16893
16934
|
const packageEntry = getPackageExportEntry(packageJson) ?? PACKAGE_ENTRY_FIELDS.map((fieldName) => packageJson[fieldName]).find((value) => typeof value === "string");
|
|
@@ -16996,7 +17037,9 @@ const stripJsonComments = (text) => {
|
|
|
16996
17037
|
}
|
|
16997
17038
|
return output.replace(/,(\s*[}\]])/g, "$1");
|
|
16998
17039
|
};
|
|
16999
|
-
const parseTsconfigFile = (configFilePath) => {
|
|
17040
|
+
const parseTsconfigFile = (configFilePath, probedPaths) => {
|
|
17041
|
+
recordContentProbe(configFilePath);
|
|
17042
|
+
probedPaths.push(configFilePath);
|
|
17000
17043
|
let sourceText;
|
|
17001
17044
|
try {
|
|
17002
17045
|
sourceText = fs.readFileSync(configFilePath, "utf8");
|
|
@@ -17025,8 +17068,8 @@ const parsePathsField = (pathsField) => {
|
|
|
17025
17068
|
}
|
|
17026
17069
|
return paths;
|
|
17027
17070
|
};
|
|
17028
|
-
const readResolvedTsconfig = (configFilePath, extendsDepth) => {
|
|
17029
|
-
const parsed = parseTsconfigFile(configFilePath);
|
|
17071
|
+
const readResolvedTsconfig = (configFilePath, extendsDepth, probedPaths) => {
|
|
17072
|
+
const parsed = parseTsconfigFile(configFilePath, probedPaths);
|
|
17030
17073
|
if (!parsed) return null;
|
|
17031
17074
|
const configDirectory = path.dirname(configFilePath);
|
|
17032
17075
|
const compilerOptions = isObjectRecord(parsed.compilerOptions) ? parsed.compilerOptions : {};
|
|
@@ -17040,7 +17083,7 @@ const readResolvedTsconfig = (configFilePath, extendsDepth) => {
|
|
|
17040
17083
|
};
|
|
17041
17084
|
if (typeof parsed.extends === "string" && extendsDepth < 8) {
|
|
17042
17085
|
const parentPath = resolveExtendsPath(parsed.extends, configDirectory);
|
|
17043
|
-
const inherited = parentPath ? readResolvedTsconfig(parentPath, extendsDepth + 1) : null;
|
|
17086
|
+
const inherited = parentPath ? readResolvedTsconfig(parentPath, extendsDepth + 1, probedPaths) : null;
|
|
17044
17087
|
if (inherited) return inherited;
|
|
17045
17088
|
}
|
|
17046
17089
|
return hasExplicitBaseUrl ? {
|
|
@@ -17051,6 +17094,7 @@ const readResolvedTsconfig = (configFilePath, extendsDepth) => {
|
|
|
17051
17094
|
};
|
|
17052
17095
|
const configByFilePath = /* @__PURE__ */ new Map();
|
|
17053
17096
|
const loadTsconfigCached = (configFilePath) => {
|
|
17097
|
+
recordContentProbe(configFilePath);
|
|
17054
17098
|
let fileStat;
|
|
17055
17099
|
try {
|
|
17056
17100
|
fileStat = fs.statSync(configFilePath);
|
|
@@ -17058,11 +17102,16 @@ const loadTsconfigCached = (configFilePath) => {
|
|
|
17058
17102
|
return null;
|
|
17059
17103
|
}
|
|
17060
17104
|
const cached = configByFilePath.get(configFilePath);
|
|
17061
|
-
if (cached && cached.mtimeMs === fileStat.mtimeMs)
|
|
17062
|
-
|
|
17105
|
+
if (cached && cached.mtimeMs === fileStat.mtimeMs) {
|
|
17106
|
+
for (const probedPath of cached.probedChainPaths) recordContentProbe(probedPath);
|
|
17107
|
+
return cached.config;
|
|
17108
|
+
}
|
|
17109
|
+
const probedChainPaths = [];
|
|
17110
|
+
const config = readResolvedTsconfig(configFilePath, 0, probedChainPaths);
|
|
17063
17111
|
configByFilePath.set(configFilePath, {
|
|
17064
17112
|
mtimeMs: fileStat.mtimeMs,
|
|
17065
|
-
config
|
|
17113
|
+
config,
|
|
17114
|
+
probedChainPaths
|
|
17066
17115
|
});
|
|
17067
17116
|
return config;
|
|
17068
17117
|
};
|
|
@@ -18909,10 +18958,23 @@ const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
|
|
|
18909
18958
|
const findNearestPackageDirectory = (filename) => {
|
|
18910
18959
|
if (!filename) return null;
|
|
18911
18960
|
const fromCache = cachedPackageDirectoryByFilename.get(filename);
|
|
18912
|
-
if (fromCache !== void 0)
|
|
18961
|
+
if (fromCache !== void 0) {
|
|
18962
|
+
if (isProbeRecorderActive()) {
|
|
18963
|
+
let probedDirectory = path.dirname(filename);
|
|
18964
|
+
while (true) {
|
|
18965
|
+
recordExistenceProbe(path.join(probedDirectory, "package.json"));
|
|
18966
|
+
if (probedDirectory === fromCache) break;
|
|
18967
|
+
const parentDirectory = path.dirname(probedDirectory);
|
|
18968
|
+
if (parentDirectory === probedDirectory) break;
|
|
18969
|
+
probedDirectory = parentDirectory;
|
|
18970
|
+
}
|
|
18971
|
+
}
|
|
18972
|
+
return fromCache;
|
|
18973
|
+
}
|
|
18913
18974
|
let currentDirectory = path.dirname(filename);
|
|
18914
18975
|
while (true) {
|
|
18915
18976
|
const candidatePackageJsonPath = path.join(currentDirectory, "package.json");
|
|
18977
|
+
recordExistenceProbe(candidatePackageJsonPath);
|
|
18916
18978
|
let hasPackageJson = false;
|
|
18917
18979
|
try {
|
|
18918
18980
|
hasPackageJson = fs.statSync(candidatePackageJsonPath).isFile();
|
|
@@ -18975,6 +19037,7 @@ const isWebFrameworkOnly = (packageJson) => {
|
|
|
18975
19037
|
const classifyPackagePlatform = (filename) => {
|
|
18976
19038
|
const packageDirectory = findNearestPackageDirectory(filename);
|
|
18977
19039
|
if (!packageDirectory) return "unknown";
|
|
19040
|
+
recordContentProbe(path.join(packageDirectory, "package.json"));
|
|
18978
19041
|
const cached = cachedPlatformByPackageDirectory.get(packageDirectory);
|
|
18979
19042
|
if (cached !== void 0) return cached;
|
|
18980
19043
|
const packageJson = readPackageJsonSafe(path.join(packageDirectory, "package.json"));
|
|
@@ -47486,6 +47549,178 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
47486
47549
|
"rn-prefer-expo-image"
|
|
47487
47550
|
]);
|
|
47488
47551
|
//#endregion
|
|
47552
|
+
//#region src/plugin/cross-file-dependencies.ts
|
|
47553
|
+
const flattenImportEntries = (staticImports) => {
|
|
47554
|
+
const flattened = [];
|
|
47555
|
+
for (const staticImport of staticImports) for (const entry of staticImport.entries) {
|
|
47556
|
+
if (entry.importName.kind === "NamespaceObject") continue;
|
|
47557
|
+
const exportedName = entry.importName.kind === "Default" ? "default" : entry.importName.name ?? null;
|
|
47558
|
+
if (exportedName === null) continue;
|
|
47559
|
+
flattened.push({
|
|
47560
|
+
source: staticImport.moduleRequest.value,
|
|
47561
|
+
exportedName
|
|
47562
|
+
});
|
|
47563
|
+
}
|
|
47564
|
+
return flattened;
|
|
47565
|
+
};
|
|
47566
|
+
const collectNoBarrelImportDependencies = ({ absoluteFilePath, staticImports }) => {
|
|
47567
|
+
let didProbePackagePlatform = false;
|
|
47568
|
+
for (const staticImport of staticImports) {
|
|
47569
|
+
const source = staticImport.moduleRequest.value;
|
|
47570
|
+
if (!source.startsWith(".")) continue;
|
|
47571
|
+
const runtimeEntries = staticImport.entries.filter((entry) => !entry.isType);
|
|
47572
|
+
if (runtimeEntries.length === 0) continue;
|
|
47573
|
+
const resolvedImportPath = resolveRelativeImportPath(absoluteFilePath, source);
|
|
47574
|
+
if (!resolvedImportPath || !isBarrelIndexModule(resolvedImportPath)) continue;
|
|
47575
|
+
if (!didProbePackagePlatform) {
|
|
47576
|
+
didProbePackagePlatform = true;
|
|
47577
|
+
classifyPackagePlatform(absoluteFilePath);
|
|
47578
|
+
}
|
|
47579
|
+
for (const entry of runtimeEntries) {
|
|
47580
|
+
const importedName = entry.importName.kind === "Default" ? "default" : entry.importName.name;
|
|
47581
|
+
if (importedName) resolveBarrelExportFilePath(resolvedImportPath, importedName);
|
|
47582
|
+
}
|
|
47583
|
+
}
|
|
47584
|
+
};
|
|
47585
|
+
const collectNextjsMissingMetadataDependencies = ({ absoluteFilePath }) => {
|
|
47586
|
+
if (!PAGE_FILE_PATTERN.test(absoluteFilePath)) return;
|
|
47587
|
+
if (INTERNAL_PAGE_PATH_PATTERN.test(absoluteFilePath)) return;
|
|
47588
|
+
hasAncestorMetadataLayout(absoluteFilePath);
|
|
47589
|
+
};
|
|
47590
|
+
const collectNextjsSearchParamsDependencies = ({ absoluteFilePath, staticImports }) => {
|
|
47591
|
+
if (!PAGE_OR_LAYOUT_FILE_PATTERN.test(absoluteFilePath)) return;
|
|
47592
|
+
if (hasAncestorSuspenseLayout(absoluteFilePath)) return;
|
|
47593
|
+
for (const entry of flattenImportEntries(staticImports)) resolveCrossFileFunctionExport(absoluteFilePath, entry.source, entry.exportedName);
|
|
47594
|
+
};
|
|
47595
|
+
const collectMutatingReducerDependencies = ({ absoluteFilePath, sourceText, staticImports, program }) => {
|
|
47596
|
+
const namedUseReducerLocals = /* @__PURE__ */ new Set();
|
|
47597
|
+
const reactObjectLocals = /* @__PURE__ */ new Set();
|
|
47598
|
+
for (const staticImport of staticImports) {
|
|
47599
|
+
if (staticImport.moduleRequest.value !== "react") continue;
|
|
47600
|
+
for (const entry of staticImport.entries) if (entry.importName.kind === "Name" && entry.importName.name === "useReducer") namedUseReducerLocals.add(entry.localName.value);
|
|
47601
|
+
else if (entry.importName.kind === "Default" || entry.importName.kind === "NamespaceObject") reactObjectLocals.add(entry.localName.value);
|
|
47602
|
+
}
|
|
47603
|
+
if (!(namedUseReducerLocals.size > 0 || reactObjectLocals.size > 0 && (sourceText.includes("useReducer") || sourceText.includes("\\u")))) return;
|
|
47604
|
+
const identifierName = (node) => {
|
|
47605
|
+
if (node?.type !== "Identifier") return null;
|
|
47606
|
+
const { name } = node;
|
|
47607
|
+
return typeof name === "string" ? name : null;
|
|
47608
|
+
};
|
|
47609
|
+
const isUseReducerCallee = (callee) => {
|
|
47610
|
+
const calleeName = identifierName(callee);
|
|
47611
|
+
if (calleeName !== null) return namedUseReducerLocals.has(calleeName);
|
|
47612
|
+
if (callee.type !== "MemberExpression") return false;
|
|
47613
|
+
const member = callee;
|
|
47614
|
+
const objectName = identifierName(member.object);
|
|
47615
|
+
return objectName !== null && reactObjectLocals.has(objectName) && identifierName(member.property) === "useReducer";
|
|
47616
|
+
};
|
|
47617
|
+
const reducerArgumentNames = /* @__PURE__ */ new Set();
|
|
47618
|
+
walkAst(program, (node) => {
|
|
47619
|
+
if (node.type !== "CallExpression") return;
|
|
47620
|
+
const call = node;
|
|
47621
|
+
if (!call.callee || !isUseReducerCallee(call.callee)) return;
|
|
47622
|
+
const reducerArgument = call.arguments?.[0];
|
|
47623
|
+
if (!reducerArgument) return;
|
|
47624
|
+
const argumentName = identifierName(stripParenExpression(reducerArgument));
|
|
47625
|
+
if (argumentName !== null) reducerArgumentNames.add(argumentName);
|
|
47626
|
+
});
|
|
47627
|
+
if (reducerArgumentNames.size === 0) return;
|
|
47628
|
+
for (const staticImport of staticImports) for (const entry of staticImport.entries) {
|
|
47629
|
+
if (entry.importName.kind === "NamespaceObject") continue;
|
|
47630
|
+
if (!reducerArgumentNames.has(entry.localName.value)) continue;
|
|
47631
|
+
const exportedName = entry.importName.kind === "Default" ? "default" : entry.importName.name;
|
|
47632
|
+
if (exportedName) resolveCrossFileFunctionExport(absoluteFilePath, staticImport.moduleRequest.value, exportedName);
|
|
47633
|
+
}
|
|
47634
|
+
};
|
|
47635
|
+
const collectJsxBoundaryNames = (program) => {
|
|
47636
|
+
const jsxNames = /* @__PURE__ */ new Set();
|
|
47637
|
+
walkAst(program, (node) => {
|
|
47638
|
+
if (node.type !== "JSXOpeningElement") return;
|
|
47639
|
+
const nameNode = node.name;
|
|
47640
|
+
if (!nameNode) return;
|
|
47641
|
+
if (nameNode.type === "JSXIdentifier") {
|
|
47642
|
+
const name = nameNode.name;
|
|
47643
|
+
if (typeof name === "string") jsxNames.add(name);
|
|
47644
|
+
} else if (nameNode.type === "JSXMemberExpression") {
|
|
47645
|
+
const property = nameNode.property;
|
|
47646
|
+
if (property?.type === "JSXIdentifier" && typeof property.name === "string") jsxNames.add(property.name);
|
|
47647
|
+
} else if (nameNode.type === "JSXNamespacedName") {
|
|
47648
|
+
const namespace = nameNode.namespace;
|
|
47649
|
+
if (typeof namespace?.name === "string") jsxNames.add(namespace.name);
|
|
47650
|
+
}
|
|
47651
|
+
});
|
|
47652
|
+
return jsxNames;
|
|
47653
|
+
};
|
|
47654
|
+
const collectRnNoRawTextDependencies = ({ absoluteFilePath, staticImports, program }) => {
|
|
47655
|
+
classifyPackagePlatform(absoluteFilePath);
|
|
47656
|
+
const jsxNames = collectJsxBoundaryNames(program);
|
|
47657
|
+
if (jsxNames.size === 0) return;
|
|
47658
|
+
for (const staticImport of staticImports) for (const entry of staticImport.entries) {
|
|
47659
|
+
if (entry.importName.kind === "NamespaceObject") continue;
|
|
47660
|
+
if (!jsxNames.has(entry.localName.value)) continue;
|
|
47661
|
+
const exportedName = entry.importName.kind === "Default" ? "default" : entry.importName.name;
|
|
47662
|
+
if (exportedName) resolveCrossFileFunctionExport(absoluteFilePath, staticImport.moduleRequest.value, exportedName);
|
|
47663
|
+
}
|
|
47664
|
+
};
|
|
47665
|
+
const collectRnPreferExpoImageDependencies = ({ absoluteFilePath }) => {
|
|
47666
|
+
classifyPackagePlatform(absoluteFilePath);
|
|
47667
|
+
};
|
|
47668
|
+
const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
47669
|
+
["no-barrel-import", collectNoBarrelImportDependencies],
|
|
47670
|
+
["nextjs-missing-metadata", collectNextjsMissingMetadataDependencies],
|
|
47671
|
+
["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
|
|
47672
|
+
["no-mutating-reducer-state", collectMutatingReducerDependencies],
|
|
47673
|
+
["rn-no-raw-text", collectRnNoRawTextDependencies],
|
|
47674
|
+
["rn-prefer-expo-image", collectRnPreferExpoImageDependencies]
|
|
47675
|
+
]);
|
|
47676
|
+
/**
|
|
47677
|
+
* Cross-file rules whose dependency set CANNOT be soundly bounded — they are
|
|
47678
|
+
* excluded from fingerprinting and re-lint every file on every scan. Empty
|
|
47679
|
+
* today; a new cross-file rule must be added either here or to
|
|
47680
|
+
* `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
|
|
47681
|
+
* partition), forcing a conscious classification.
|
|
47682
|
+
*/
|
|
47683
|
+
const UNBOUNDED_CROSS_FILE_RULE_IDS = /* @__PURE__ */ new Set();
|
|
47684
|
+
/**
|
|
47685
|
+
* Runs the collectors for `ruleIds` over one file and returns every
|
|
47686
|
+
* filesystem probe they made — the file's cross-file dependency set.
|
|
47687
|
+
*
|
|
47688
|
+
* Returns `null` (caller must treat the file as unfingerprintable and always
|
|
47689
|
+
* re-lint it) when the file has a fatal parse error — an execution the
|
|
47690
|
+
* collectors cannot mirror — or when a requested rule has no collector.
|
|
47691
|
+
*/
|
|
47692
|
+
const collectCrossFileDependencyProbes = (input) => {
|
|
47693
|
+
const collectors = [];
|
|
47694
|
+
for (const ruleId of input.ruleIds) {
|
|
47695
|
+
const collector = CROSS_FILE_DEPENDENCY_COLLECTORS.get(ruleId);
|
|
47696
|
+
if (!collector) return null;
|
|
47697
|
+
collectors.push(collector);
|
|
47698
|
+
}
|
|
47699
|
+
const absoluteFilePath = normalizeFilename(input.absoluteFilePath);
|
|
47700
|
+
let staticImports;
|
|
47701
|
+
let program;
|
|
47702
|
+
try {
|
|
47703
|
+
const parseResult = parseSync(absoluteFilePath, input.sourceText, {
|
|
47704
|
+
astType: "ts",
|
|
47705
|
+
lang: resolveLang(absoluteFilePath)
|
|
47706
|
+
});
|
|
47707
|
+
if (parseResult.errors.some((parseError) => parseError.severity === "Error")) return null;
|
|
47708
|
+
staticImports = parseResult.module.staticImports;
|
|
47709
|
+
program = parseResult.program;
|
|
47710
|
+
} catch {
|
|
47711
|
+
return null;
|
|
47712
|
+
}
|
|
47713
|
+
const collectorInput = {
|
|
47714
|
+
absoluteFilePath,
|
|
47715
|
+
sourceText: input.sourceText,
|
|
47716
|
+
staticImports,
|
|
47717
|
+
program
|
|
47718
|
+
};
|
|
47719
|
+
return collectCrossFileProbes(() => {
|
|
47720
|
+
for (const collector of collectors) collector(collectorInput);
|
|
47721
|
+
});
|
|
47722
|
+
};
|
|
47723
|
+
//#endregion
|
|
47489
47724
|
//#region src/plugin/rules/security-scan/utils/is-probably-text-file.ts
|
|
47490
47725
|
const isProbablyTextFile = (relativePath) => TEXT_FILE_PATTERN.test(relativePath) || DOTENV_FILE_PATTERN.test(relativePath);
|
|
47491
47726
|
//#endregion
|
|
@@ -47511,6 +47746,6 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
|
|
|
47511
47746
|
//#region src/index.ts
|
|
47512
47747
|
var src_default = plugin;
|
|
47513
47748
|
//#endregion
|
|
47514
|
-
export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, EXTERNAL_RULES, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, PREACT_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, classifySecurityScanFile, src_default as default, isReactNativeDependencyName, shouldReadSecurityScanContent };
|
|
47749
|
+
export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, EXTERNAL_RULES, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, PREACT_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, UNBOUNDED_CROSS_FILE_RULE_IDS, classifySecurityScanFile, collectCrossFileDependencyProbes, src_default as default, isReactNativeDependencyName, shouldReadSecurityScanContent };
|
|
47515
47750
|
|
|
47516
47751
|
//# sourceMappingURL=index.js.map
|