react-doctor 0.6.3-dev.e257a5e → 0.6.3-dev.ea9a775
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/cli.js +545 -93
- package/dist/index.d.ts +18 -0
- package/dist/index.js +433 -81
- package/dist/lsp.js +433 -81
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b55ef8b2-8e9d-512b-81de-a01f6ce3e60e")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
20
|
import crypto, { createHash } from "node:crypto";
|
|
21
|
+
import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
|
|
21
22
|
import { gzipSync } from "node:zlib";
|
|
22
23
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
23
24
|
/**
|
|
@@ -32927,7 +32928,10 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32927
32928
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32928
32929
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32929
32930
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32931
|
+
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32932
|
+
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32930
32933
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32934
|
+
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32931
32935
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32932
32936
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32933
32937
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
@@ -35615,6 +35619,29 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35615
35619
|
return true;
|
|
35616
35620
|
} }) {};
|
|
35617
35621
|
/**
|
|
35622
|
+
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35623
|
+
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35624
|
+
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35625
|
+
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35626
|
+
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35627
|
+
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35628
|
+
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35629
|
+
*
|
|
35630
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35631
|
+
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35632
|
+
* per-file cache but run the always-fresh sidecar over every hit
|
|
35633
|
+
* (the pre-cache behavior).
|
|
35634
|
+
*
|
|
35635
|
+
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35636
|
+
*/
|
|
35637
|
+
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35638
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35639
|
+
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35640
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35641
|
+
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35642
|
+
return true;
|
|
35643
|
+
} }) {};
|
|
35644
|
+
/**
|
|
35618
35645
|
* Whether the whole-project dead-code result cache
|
|
35619
35646
|
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35620
35647
|
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
@@ -37338,38 +37365,9 @@ const failOpenReadJson = (filePath, fallback) => {
|
|
|
37338
37365
|
return fallback;
|
|
37339
37366
|
}
|
|
37340
37367
|
};
|
|
37341
|
-
const ANALYZED_FILE_EXTENSIONS = new Set(
|
|
37342
|
-
|
|
37343
|
-
|
|
37344
|
-
".js",
|
|
37345
|
-
".jsx",
|
|
37346
|
-
".mts",
|
|
37347
|
-
".mjs",
|
|
37348
|
-
".cts",
|
|
37349
|
-
".cjs",
|
|
37350
|
-
".mdx",
|
|
37351
|
-
".astro",
|
|
37352
|
-
".graphql",
|
|
37353
|
-
".gql",
|
|
37354
|
-
".css",
|
|
37355
|
-
".scss",
|
|
37356
|
-
".vue",
|
|
37357
|
-
".svelte"
|
|
37358
|
-
]);
|
|
37359
|
-
const ANALYZED_MANIFEST_NAMES = new Set([
|
|
37360
|
-
"package.json",
|
|
37361
|
-
"knip.json",
|
|
37362
|
-
"app.json",
|
|
37363
|
-
"pnpm-workspace.yaml",
|
|
37364
|
-
"lerna.json",
|
|
37365
|
-
"pnpm-lock.yaml",
|
|
37366
|
-
"package-lock.json",
|
|
37367
|
-
"yarn.lock",
|
|
37368
|
-
"bun.lock",
|
|
37369
|
-
"bun.lockb",
|
|
37370
|
-
"deno.lock",
|
|
37371
|
-
".gitignore"
|
|
37372
|
-
]);
|
|
37368
|
+
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37369
|
+
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37370
|
+
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37373
37371
|
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37374
37372
|
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37375
37373
|
const collectAnalyzedFileFingerprints = (rootDirectory) => {
|
|
@@ -37395,6 +37393,7 @@ const resolveDeslopVersion = () => {
|
|
|
37395
37393
|
};
|
|
37396
37394
|
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37397
37395
|
schemaVersion: 1,
|
|
37396
|
+
coreVersion: input.coreVersion,
|
|
37398
37397
|
deslopVersion: resolveDeslopVersion(),
|
|
37399
37398
|
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37400
37399
|
entryPatterns: input.entryPatterns,
|
|
@@ -37402,10 +37401,10 @@ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON
|
|
|
37402
37401
|
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
37403
37402
|
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37404
37403
|
})).digest("hex");
|
|
37405
|
-
const validateDiagnostic$
|
|
37404
|
+
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37406
37405
|
const decodeCachedDiagnostics = (raw) => {
|
|
37407
37406
|
try {
|
|
37408
|
-
for (const entry of raw) validateDiagnostic$
|
|
37407
|
+
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37409
37408
|
return raw;
|
|
37410
37409
|
} catch {
|
|
37411
37410
|
return null;
|
|
@@ -37559,6 +37558,14 @@ process.stdin.on("end", () => {
|
|
|
37559
37558
|
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
37560
37559
|
files: cycle.files,
|
|
37561
37560
|
})),
|
|
37561
|
+
...(result.incrementalCacheStats
|
|
37562
|
+
? {
|
|
37563
|
+
summaryCacheStats: {
|
|
37564
|
+
hits: result.incrementalCacheStats.summaryHits,
|
|
37565
|
+
misses: result.incrementalCacheStats.summaryMisses,
|
|
37566
|
+
},
|
|
37567
|
+
}
|
|
37568
|
+
: {}),
|
|
37562
37569
|
});
|
|
37563
37570
|
|
|
37564
37571
|
const serializeError = (error) =>
|
|
@@ -37582,6 +37589,9 @@ process.stdin.on("end", () => {
|
|
|
37582
37589
|
...(workerInput.ignorePatterns.length > 0
|
|
37583
37590
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
37584
37591
|
: {}),
|
|
37592
|
+
...(workerInput.incrementalCachePath
|
|
37593
|
+
? { incrementalCachePath: workerInput.incrementalCachePath }
|
|
37594
|
+
: {}),
|
|
37585
37595
|
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37586
37596
|
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37587
37597
|
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
@@ -37592,12 +37602,18 @@ process.stdin.on("end", () => {
|
|
|
37592
37602
|
// are the single most expensive pass — duplicate-block detection alone was
|
|
37593
37603
|
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37594
37604
|
// speedup on a large repo.
|
|
37595
|
-
//
|
|
37605
|
+
// - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
|
|
37606
|
+
// simplifiable functions, identity wrappers, …) — ~120 ms of discarded
|
|
37607
|
+
// output per scan, and skipping them lets the incremental cache drop the
|
|
37608
|
+
// DRY-pattern summary fields (the largest slice of the cache file).
|
|
37609
|
+
// All are provably safe: the consumed graph findings are computed by their own
|
|
37596
37610
|
// detectors, independent of these passes (confirmed byte-identical on
|
|
37597
|
-
// excalidraw + mui-material + sentry
|
|
37598
|
-
//
|
|
37611
|
+
// excalidraw + mui-material + sentry; re-verified on sentry after the
|
|
37612
|
+
// reportRedundancy flip). tsConfigPath stays — the module resolver needs it
|
|
37613
|
+
// for path-alias resolution in the import graph.
|
|
37599
37614
|
semantic: { enabled: false },
|
|
37600
37615
|
reportCodeQuality: false,
|
|
37616
|
+
reportRedundancy: false,
|
|
37601
37617
|
};
|
|
37602
37618
|
const result = await analyze(defineConfig(config));
|
|
37603
37619
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37681,13 +37697,23 @@ const parseCircularDependencies = (value) => {
|
|
|
37681
37697
|
}
|
|
37682
37698
|
return circularDependencies;
|
|
37683
37699
|
};
|
|
37700
|
+
const parseSummaryCacheStats = (value) => {
|
|
37701
|
+
if (!isRecord(value)) return void 0;
|
|
37702
|
+
if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
|
|
37703
|
+
return {
|
|
37704
|
+
hits: value.hits,
|
|
37705
|
+
misses: value.misses
|
|
37706
|
+
};
|
|
37707
|
+
};
|
|
37684
37708
|
const parseDeadCodeWorkerResult = (value) => {
|
|
37685
37709
|
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
37710
|
+
const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
|
|
37686
37711
|
return {
|
|
37687
37712
|
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
37688
37713
|
unusedExports: parseUnusedExports(value.unusedExports),
|
|
37689
37714
|
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
37690
|
-
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37715
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies),
|
|
37716
|
+
...summaryCacheStats ? { summaryCacheStats } : {}
|
|
37691
37717
|
};
|
|
37692
37718
|
};
|
|
37693
37719
|
const parseDeadCodeWorkerError = (value) => {
|
|
@@ -37808,13 +37834,15 @@ const checkDeadCode = async (options) => {
|
|
|
37808
37834
|
entryPatterns,
|
|
37809
37835
|
ignorePatterns,
|
|
37810
37836
|
tsConfigPath,
|
|
37811
|
-
deslopJsModuleSpecifier
|
|
37837
|
+
deslopJsModuleSpecifier,
|
|
37838
|
+
coreVersion: CORE_PACKAGE_VERSION
|
|
37812
37839
|
}) : null;
|
|
37813
37840
|
if (cacheKey !== null) {
|
|
37814
37841
|
const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
|
|
37815
37842
|
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37816
37843
|
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37817
37844
|
}
|
|
37845
|
+
const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
|
|
37818
37846
|
const spawnAndRun = () => {
|
|
37819
37847
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37820
37848
|
rootDirectory,
|
|
@@ -37822,10 +37850,12 @@ const checkDeadCode = async (options) => {
|
|
|
37822
37850
|
tsConfigPath,
|
|
37823
37851
|
ignorePatterns,
|
|
37824
37852
|
deslopJsModuleSpecifier,
|
|
37825
|
-
parseConcurrency: options.parseConcurrency
|
|
37853
|
+
parseConcurrency: options.parseConcurrency,
|
|
37854
|
+
incrementalCachePath
|
|
37826
37855
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37827
37856
|
};
|
|
37828
37857
|
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37858
|
+
if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
|
|
37829
37859
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37830
37860
|
const diagnostics = [];
|
|
37831
37861
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37993,7 +38023,8 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37993
38023
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37994
38024
|
abortSignal: signal,
|
|
37995
38025
|
cacheEnabled,
|
|
37996
|
-
onCacheOutcome: input.onCacheOutcome
|
|
38026
|
+
onCacheOutcome: input.onCacheOutcome,
|
|
38027
|
+
onSummaryCacheStats: input.onSummaryCacheStats
|
|
37997
38028
|
}),
|
|
37998
38029
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37999
38030
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38863,7 +38894,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38863
38894
|
}
|
|
38864
38895
|
return enabled;
|
|
38865
38896
|
};
|
|
38866
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38897
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
38867
38898
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38868
38899
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38869
38900
|
const jsPlugins = [];
|
|
@@ -38875,6 +38906,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38875
38906
|
if (!rule) continue;
|
|
38876
38907
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38877
38908
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38909
|
+
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38878
38910
|
if (rule.scan !== void 0) continue;
|
|
38879
38911
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38880
38912
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38919,23 +38951,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38919
38951
|
}
|
|
38920
38952
|
};
|
|
38921
38953
|
};
|
|
38922
|
-
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38954
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
38923
38955
|
const decodeFileDiagnostics = (raw) => {
|
|
38924
38956
|
if (!Array.isArray(raw)) return null;
|
|
38925
38957
|
try {
|
|
38926
|
-
for (const entry of raw) validateDiagnostic(entry);
|
|
38958
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
38927
38959
|
return raw;
|
|
38928
38960
|
} catch {
|
|
38929
38961
|
return null;
|
|
38930
38962
|
}
|
|
38931
38963
|
};
|
|
38932
|
-
const emptyCache = () => ({
|
|
38964
|
+
const emptyCache$1 = () => ({
|
|
38933
38965
|
version: 1,
|
|
38934
38966
|
rulesets: {}
|
|
38935
38967
|
});
|
|
38936
|
-
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
38968
|
+
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
38937
38969
|
const entries = /* @__PURE__ */ new Map();
|
|
38938
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38970
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38939
38971
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38940
38972
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38941
38973
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
@@ -38947,7 +38979,7 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
38947
38979
|
};
|
|
38948
38980
|
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38949
38981
|
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38950
|
-
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38982
|
+
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
38951
38983
|
return {
|
|
38952
38984
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38953
38985
|
store: (fileKey, diagnostics) => {
|
|
@@ -38955,7 +38987,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38955
38987
|
entries.set(fileKey, diagnostics);
|
|
38956
38988
|
},
|
|
38957
38989
|
persist: () => {
|
|
38958
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38990
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38959
38991
|
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38960
38992
|
const existingBucket = rulesets[rulesetHash];
|
|
38961
38993
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
@@ -38976,6 +39008,142 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38976
39008
|
}
|
|
38977
39009
|
};
|
|
38978
39010
|
};
|
|
39011
|
+
const hashFileContents = (filePath) => {
|
|
39012
|
+
try {
|
|
39013
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39014
|
+
} catch {
|
|
39015
|
+
return null;
|
|
39016
|
+
}
|
|
39017
|
+
};
|
|
39018
|
+
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39019
|
+
/**
|
|
39020
|
+
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39021
|
+
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39022
|
+
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39023
|
+
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39024
|
+
*
|
|
39025
|
+
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39026
|
+
* (missing, a directory, permission failure). Source files reuse the
|
|
39027
|
+
* hashes the cache-partition loop already computed
|
|
39028
|
+
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39029
|
+
* tsconfig, ignored files) is hashed on demand.
|
|
39030
|
+
* - `exists` — the existence class a module-resolution candidate probe
|
|
39031
|
+
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39032
|
+
* `statSync` follows symlinks, matching the resolvers).
|
|
39033
|
+
*
|
|
39034
|
+
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39035
|
+
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39036
|
+
* tsconfig chain) repeat across many files' probe sets.
|
|
39037
|
+
*/
|
|
39038
|
+
const createSidecarProbeAnswerResolver = (input) => {
|
|
39039
|
+
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39040
|
+
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39041
|
+
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39042
|
+
const contentAnswer = (relativePath) => {
|
|
39043
|
+
const memoized = contentAnswerByPath.get(relativePath);
|
|
39044
|
+
if (memoized !== void 0) return memoized;
|
|
39045
|
+
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39046
|
+
contentAnswerByPath.set(relativePath, answer);
|
|
39047
|
+
return answer;
|
|
39048
|
+
};
|
|
39049
|
+
const existsAnswer = (relativePath) => {
|
|
39050
|
+
const memoized = existsAnswerByPath.get(relativePath);
|
|
39051
|
+
if (memoized !== void 0) return memoized;
|
|
39052
|
+
let answer;
|
|
39053
|
+
try {
|
|
39054
|
+
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39055
|
+
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39056
|
+
} catch {
|
|
39057
|
+
answer = "none";
|
|
39058
|
+
}
|
|
39059
|
+
existsAnswerByPath.set(relativePath, answer);
|
|
39060
|
+
return answer;
|
|
39061
|
+
};
|
|
39062
|
+
return {
|
|
39063
|
+
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39064
|
+
toRelativePath
|
|
39065
|
+
};
|
|
39066
|
+
};
|
|
39067
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39068
|
+
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39069
|
+
const decodeSidecarFileEntry = (raw) => {
|
|
39070
|
+
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39071
|
+
const probes = [];
|
|
39072
|
+
for (const rawProbe of raw.probes) {
|
|
39073
|
+
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39074
|
+
const [kind, probePath, answer] = rawProbe;
|
|
39075
|
+
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39076
|
+
probes.push({
|
|
39077
|
+
kind,
|
|
39078
|
+
path: probePath,
|
|
39079
|
+
answer
|
|
39080
|
+
});
|
|
39081
|
+
}
|
|
39082
|
+
try {
|
|
39083
|
+
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
39084
|
+
} catch {
|
|
39085
|
+
return null;
|
|
39086
|
+
}
|
|
39087
|
+
return {
|
|
39088
|
+
probes,
|
|
39089
|
+
diagnostics: raw.diagnostics
|
|
39090
|
+
};
|
|
39091
|
+
};
|
|
39092
|
+
const encodeSidecarFileEntry = (entry) => ({
|
|
39093
|
+
probes: entry.probes.map((probe) => [
|
|
39094
|
+
probe.kind,
|
|
39095
|
+
probe.path,
|
|
39096
|
+
probe.answer
|
|
39097
|
+
]),
|
|
39098
|
+
diagnostics: entry.diagnostics
|
|
39099
|
+
});
|
|
39100
|
+
const emptyCache = () => ({
|
|
39101
|
+
version: 1,
|
|
39102
|
+
rulesets: {}
|
|
39103
|
+
});
|
|
39104
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39105
|
+
const entries = /* @__PURE__ */ new Map();
|
|
39106
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39107
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39108
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
39109
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39110
|
+
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39111
|
+
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
39112
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39113
|
+
}
|
|
39114
|
+
return entries;
|
|
39115
|
+
};
|
|
39116
|
+
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39117
|
+
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
39118
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39119
|
+
return {
|
|
39120
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39121
|
+
store: (fileKey, entry) => {
|
|
39122
|
+
entries.delete(fileKey);
|
|
39123
|
+
entries.set(fileKey, entry);
|
|
39124
|
+
},
|
|
39125
|
+
persist: () => {
|
|
39126
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39127
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39128
|
+
const existingBucket = rulesets[rulesetHash];
|
|
39129
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39130
|
+
const ourFiles = {};
|
|
39131
|
+
for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
|
|
39132
|
+
const cappedEntries = Object.entries({
|
|
39133
|
+
...existingFiles,
|
|
39134
|
+
...ourFiles
|
|
39135
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39136
|
+
rulesets[rulesetHash] = {
|
|
39137
|
+
updatedAtMs: Date.now(),
|
|
39138
|
+
files: Object.fromEntries(cappedEntries)
|
|
39139
|
+
};
|
|
39140
|
+
atomicWriteJson(cacheFilePath, {
|
|
39141
|
+
version: 1,
|
|
39142
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39143
|
+
});
|
|
39144
|
+
}
|
|
39145
|
+
};
|
|
39146
|
+
};
|
|
38979
39147
|
const bundledRequire = createRequire(import.meta.url);
|
|
38980
39148
|
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38981
39149
|
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
@@ -39975,13 +40143,6 @@ const validateRuleRegistration = () => {
|
|
|
39975
40143
|
].filter((entry) => entry !== null).join("; ");
|
|
39976
40144
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39977
40145
|
};
|
|
39978
|
-
const hashFileContents = (filePath) => {
|
|
39979
|
-
try {
|
|
39980
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39981
|
-
} catch {
|
|
39982
|
-
return null;
|
|
39983
|
-
}
|
|
39984
|
-
};
|
|
39985
40146
|
/**
|
|
39986
40147
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39987
40148
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40063,6 +40224,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40063
40224
|
NFS.closeSync(fileHandle);
|
|
40064
40225
|
}
|
|
40065
40226
|
};
|
|
40227
|
+
/**
|
|
40228
|
+
* Attributes diagnostics back to the file that produced them by the
|
|
40229
|
+
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40230
|
+
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40231
|
+
* store rather than risk caching a wrong empty result for a file that
|
|
40232
|
+
* actually had diagnostics.
|
|
40233
|
+
*/
|
|
40234
|
+
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40235
|
+
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40236
|
+
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40237
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40238
|
+
for (const diagnostic of diagnostics) {
|
|
40239
|
+
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40240
|
+
if (file === void 0) return null;
|
|
40241
|
+
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40242
|
+
fileDiagnostics.push(diagnostic);
|
|
40243
|
+
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40244
|
+
}
|
|
40245
|
+
return diagnosticsByFile;
|
|
40246
|
+
};
|
|
40247
|
+
/**
|
|
40248
|
+
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40249
|
+
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40250
|
+
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40251
|
+
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40252
|
+
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40253
|
+
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40254
|
+
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40255
|
+
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40256
|
+
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40257
|
+
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40258
|
+
*/
|
|
40259
|
+
const collectSidecarProbesForFiles = async (input) => {
|
|
40260
|
+
const buildProbes = (file) => {
|
|
40261
|
+
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40262
|
+
let trace;
|
|
40263
|
+
try {
|
|
40264
|
+
trace = collectCrossFileDependencyProbes({
|
|
40265
|
+
absoluteFilePath,
|
|
40266
|
+
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40267
|
+
ruleIds: input.boundedSidecarRuleIds
|
|
40268
|
+
});
|
|
40269
|
+
} catch {
|
|
40270
|
+
return null;
|
|
40271
|
+
}
|
|
40272
|
+
if (trace === null) return null;
|
|
40273
|
+
const probes = [];
|
|
40274
|
+
for (const contentPath of trace.contentPaths) {
|
|
40275
|
+
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40276
|
+
probes.push({
|
|
40277
|
+
kind: "content",
|
|
40278
|
+
path: relativePath,
|
|
40279
|
+
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40280
|
+
});
|
|
40281
|
+
}
|
|
40282
|
+
for (const existencePath of trace.existencePaths) {
|
|
40283
|
+
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40284
|
+
probes.push({
|
|
40285
|
+
kind: "exists",
|
|
40286
|
+
path: relativePath,
|
|
40287
|
+
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40288
|
+
});
|
|
40289
|
+
}
|
|
40290
|
+
return probes;
|
|
40291
|
+
};
|
|
40292
|
+
const probesByFile = /* @__PURE__ */ new Map();
|
|
40293
|
+
let collectSliceStartedAt = performance.now();
|
|
40294
|
+
for (const file of input.files) {
|
|
40295
|
+
probesByFile.set(file, buildProbes(file));
|
|
40296
|
+
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40297
|
+
await yieldToEventLoop();
|
|
40298
|
+
collectSliceStartedAt = performance.now();
|
|
40299
|
+
}
|
|
40300
|
+
}
|
|
40301
|
+
return probesByFile;
|
|
40302
|
+
};
|
|
40303
|
+
/**
|
|
40304
|
+
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40305
|
+
* the next scan can replay instead of re-linting.
|
|
40306
|
+
*/
|
|
40307
|
+
const storeSidecarEntries = (input) => {
|
|
40308
|
+
let didStoreAnyEntry = false;
|
|
40309
|
+
for (const pass of input.storablePasses) {
|
|
40310
|
+
if (!pass.isTrusted) continue;
|
|
40311
|
+
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40312
|
+
if (diagnosticsByFile === null) continue;
|
|
40313
|
+
for (const file of pass.files) {
|
|
40314
|
+
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40315
|
+
if (cacheKey === void 0) continue;
|
|
40316
|
+
const probes = pass.probesByFile.get(file);
|
|
40317
|
+
if (probes === void 0 || probes === null) continue;
|
|
40318
|
+
input.sidecarCache.store(cacheKey, {
|
|
40319
|
+
probes,
|
|
40320
|
+
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40321
|
+
});
|
|
40322
|
+
didStoreAnyEntry = true;
|
|
40323
|
+
}
|
|
40324
|
+
}
|
|
40325
|
+
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40326
|
+
};
|
|
40066
40327
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40067
40328
|
/**
|
|
40068
40329
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40104,7 +40365,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40104
40365
|
* 6. always restore disable directives + clean up the temp dir
|
|
40105
40366
|
*/
|
|
40106
40367
|
const runOxlint = async (options) => {
|
|
40107
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40368
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40108
40369
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40109
40370
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40110
40371
|
validateRuleRegistration();
|
|
@@ -40122,7 +40383,8 @@ const runOxlint = async (options) => {
|
|
|
40122
40383
|
severityControls,
|
|
40123
40384
|
userPlugins,
|
|
40124
40385
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40125
|
-
ruleSelection: overrides.ruleSelection
|
|
40386
|
+
ruleSelection: overrides.ruleSelection,
|
|
40387
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40126
40388
|
});
|
|
40127
40389
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40128
40390
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40225,15 +40487,18 @@ const runOxlint = async (options) => {
|
|
|
40225
40487
|
});
|
|
40226
40488
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40227
40489
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40490
|
+
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40228
40491
|
const missFiles = [];
|
|
40229
40492
|
const replayedDiagnostics = [];
|
|
40230
40493
|
let hashSliceStartedAt = performance.now();
|
|
40231
40494
|
for (const candidateFile of candidateFiles) {
|
|
40232
|
-
const
|
|
40495
|
+
const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
|
|
40496
|
+
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40233
40497
|
if (contentHash === null) {
|
|
40234
40498
|
missFiles.push(candidateFile);
|
|
40235
40499
|
continue;
|
|
40236
40500
|
}
|
|
40501
|
+
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40237
40502
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40238
40503
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40239
40504
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40245,43 +40510,111 @@ const runOxlint = async (options) => {
|
|
|
40245
40510
|
}
|
|
40246
40511
|
}
|
|
40247
40512
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40513
|
+
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40514
|
+
extendsPaths: [],
|
|
40515
|
+
ruleSelection: "sidecar"
|
|
40516
|
+
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40517
|
+
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40518
|
+
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40519
|
+
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40520
|
+
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40521
|
+
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40522
|
+
config: buildConfig({
|
|
40523
|
+
extendsPaths: [],
|
|
40524
|
+
ruleSelection: "sidecar",
|
|
40525
|
+
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40526
|
+
}),
|
|
40527
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40528
|
+
ignorePatterns: combinedPatterns,
|
|
40529
|
+
tsconfigContent
|
|
40530
|
+
})) : null;
|
|
40531
|
+
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40532
|
+
rootDirectory,
|
|
40533
|
+
contentHashByRelativePath
|
|
40534
|
+
});
|
|
40535
|
+
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40536
|
+
files: missFiles,
|
|
40537
|
+
rootDirectory,
|
|
40538
|
+
boundedSidecarRuleIds,
|
|
40539
|
+
probeAnswers
|
|
40540
|
+
});
|
|
40248
40541
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40249
40542
|
extendsPaths: [],
|
|
40250
40543
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40251
40544
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40252
40545
|
const missFileSet = new Set(missFiles);
|
|
40253
|
-
const
|
|
40546
|
+
const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
|
|
40547
|
+
const sidecarReplayedDiagnostics = [];
|
|
40548
|
+
const sidecarLintFiles = [];
|
|
40549
|
+
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40550
|
+
else {
|
|
40551
|
+
let verifySliceStartedAt = performance.now();
|
|
40552
|
+
for (const hitFile of hitFiles) {
|
|
40553
|
+
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40554
|
+
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40555
|
+
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40556
|
+
else sidecarLintFiles.push(hitFile);
|
|
40557
|
+
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40558
|
+
await yieldToEventLoop();
|
|
40559
|
+
verifySliceStartedAt = performance.now();
|
|
40560
|
+
}
|
|
40561
|
+
}
|
|
40562
|
+
}
|
|
40563
|
+
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40564
|
+
files: sidecarLintFiles,
|
|
40565
|
+
rootDirectory,
|
|
40566
|
+
boundedSidecarRuleIds,
|
|
40567
|
+
probeAnswers
|
|
40568
|
+
});
|
|
40569
|
+
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40570
|
+
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40571
|
+
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40254
40572
|
extendsPaths: [],
|
|
40255
|
-
ruleSelection: "sidecar"
|
|
40256
|
-
|
|
40573
|
+
ruleSelection: "sidecar",
|
|
40574
|
+
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40575
|
+
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40576
|
+
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40577
|
+
extendsPaths: [],
|
|
40578
|
+
ruleSelection: "sidecar",
|
|
40579
|
+
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40580
|
+
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40581
|
+
diagnostics: [],
|
|
40582
|
+
didDropReactHooksJsPlugin: false,
|
|
40583
|
+
hadPartialFailure: false
|
|
40584
|
+
};
|
|
40257
40585
|
const freshCacheableDiagnostics = [];
|
|
40258
40586
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40259
40587
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40260
|
-
|
|
40261
|
-
|
|
40262
|
-
|
|
40263
|
-
let isAttributionSound = true;
|
|
40264
|
-
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40265
|
-
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40266
|
-
if (missFile === void 0) {
|
|
40267
|
-
isAttributionSound = false;
|
|
40268
|
-
break;
|
|
40269
|
-
}
|
|
40270
|
-
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40271
|
-
fileDiagnostics.push(diagnostic);
|
|
40272
|
-
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40273
|
-
}
|
|
40274
|
-
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40588
|
+
if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
|
|
40589
|
+
const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
|
|
40590
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
|
|
40275
40591
|
for (const missFile of missFiles) {
|
|
40276
40592
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40277
40593
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40278
40594
|
}
|
|
40279
40595
|
cache.persist();
|
|
40280
40596
|
}
|
|
40597
|
+
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40598
|
+
sidecarCache,
|
|
40599
|
+
cacheKeyByFile,
|
|
40600
|
+
storablePasses: [{
|
|
40601
|
+
files: sidecarLintFiles,
|
|
40602
|
+
diagnostics: boundedSidecarResult.diagnostics,
|
|
40603
|
+
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40604
|
+
probesByFile: await sidecarProbesTask
|
|
40605
|
+
}, {
|
|
40606
|
+
files: missFiles,
|
|
40607
|
+
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40608
|
+
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40609
|
+
probesByFile: await missProbesTask
|
|
40610
|
+
}]
|
|
40611
|
+
});
|
|
40281
40612
|
return dedupeDiagnostics([
|
|
40282
40613
|
...replayedDiagnostics,
|
|
40283
40614
|
...fullResult.diagnostics,
|
|
40284
|
-
...
|
|
40615
|
+
...sidecarReplayedDiagnostics,
|
|
40616
|
+
...boundedSidecarResult.diagnostics,
|
|
40617
|
+
...unboundedSidecarResult.diagnostics
|
|
40285
40618
|
]);
|
|
40286
40619
|
}
|
|
40287
40620
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40379,6 +40712,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40379
40712
|
const concurrency = yield* OxlintConcurrency;
|
|
40380
40713
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40381
40714
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40715
|
+
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40382
40716
|
const collectedFailures = [];
|
|
40383
40717
|
const diagnostics = yield* tryPromise({
|
|
40384
40718
|
try: (signal) => runOxlint({
|
|
@@ -40397,7 +40731,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40397
40731
|
},
|
|
40398
40732
|
onFileProgress: input.onFileProgress,
|
|
40399
40733
|
perFileLintCacheEnabled,
|
|
40734
|
+
sidecarLintCacheEnabled,
|
|
40400
40735
|
onCacheStats: input.onCacheStats,
|
|
40736
|
+
onSidecarStats: input.onSidecarStats,
|
|
40401
40737
|
spawnTimeoutMs,
|
|
40402
40738
|
outputMaxBytes,
|
|
40403
40739
|
concurrency,
|
|
@@ -41175,6 +41511,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41175
41511
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
|
|
41176
41512
|
onCacheOutcome: (didHitCache) => {
|
|
41177
41513
|
deadCodeCacheHit = didHitCache;
|
|
41514
|
+
},
|
|
41515
|
+
onSummaryCacheStats: (stats) => {
|
|
41516
|
+
deadCodeSummaryCacheHits = stats.hits;
|
|
41517
|
+
deadCodeSummaryCacheMisses = stats.misses;
|
|
41178
41518
|
}
|
|
41179
41519
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41180
41520
|
yield* set(deadCodeFailure, {
|
|
@@ -41203,7 +41543,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41203
41543
|
let lastReportedTotalFileCount = 0;
|
|
41204
41544
|
let lintCacheHitFileCount = null;
|
|
41205
41545
|
let lintCacheTotalFileCount = null;
|
|
41546
|
+
let lintSidecarReplayedFileCount = null;
|
|
41547
|
+
let lintSidecarTotalFileCount = null;
|
|
41206
41548
|
let deadCodeCacheHit = null;
|
|
41549
|
+
let deadCodeSummaryCacheHits = null;
|
|
41550
|
+
let deadCodeSummaryCacheMisses = null;
|
|
41207
41551
|
const baseLintStream = linterService.run({
|
|
41208
41552
|
rootDirectory: scanDirectory,
|
|
41209
41553
|
project,
|
|
@@ -41223,6 +41567,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41223
41567
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41224
41568
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41225
41569
|
},
|
|
41570
|
+
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41571
|
+
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41572
|
+
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41573
|
+
},
|
|
41226
41574
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41227
41575
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41228
41576
|
yield* set(lintFailure, {
|
|
@@ -41333,7 +41681,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41333
41681
|
securityScanFailed,
|
|
41334
41682
|
lintCacheHitFileCount,
|
|
41335
41683
|
lintCacheTotalFileCount,
|
|
41684
|
+
lintSidecarReplayedFileCount,
|
|
41685
|
+
lintSidecarTotalFileCount,
|
|
41336
41686
|
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41687
|
+
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41688
|
+
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
41337
41689
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41338
41690
|
};
|
|
41339
41691
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -41974,4 +42326,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41974
42326
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41975
42327
|
|
|
41976
42328
|
//# sourceMappingURL=index.js.map
|
|
41977
|
-
//# debugId=
|
|
42329
|
+
//# debugId=b55ef8b2-8e9d-512b-81de-a01f6ce3e60e
|