react-doctor 0.6.3-dev.bdf8074 → 0.6.3-dev.ce49250
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 +319 -737
- package/dist/index.d.ts +0 -8
- package/dist/index.js +42 -379
- package/dist/lsp.js +42 -379
- package/package.json +2 -2
package/dist/lsp.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
9
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS,
|
|
11
|
+
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
12
|
import { parseJSON5 } from "confbox";
|
|
13
13
|
import * as NodeUrl from "node:url";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -32987,7 +32987,6 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32987
32987
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32988
32988
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32989
32989
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32990
|
-
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32991
32990
|
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32992
32991
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32993
32992
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
@@ -35652,29 +35651,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35652
35651
|
return true;
|
|
35653
35652
|
} }) {};
|
|
35654
35653
|
/**
|
|
35655
|
-
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35656
|
-
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35657
|
-
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35658
|
-
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35659
|
-
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35660
|
-
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35661
|
-
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35662
|
-
*
|
|
35663
|
-
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35664
|
-
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35665
|
-
* per-file cache but run the always-fresh sidecar over every hit
|
|
35666
|
-
* (the pre-cache behavior).
|
|
35667
|
-
*
|
|
35668
|
-
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35669
|
-
*/
|
|
35670
|
-
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35671
|
-
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35672
|
-
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35673
|
-
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35674
|
-
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35675
|
-
return true;
|
|
35676
|
-
} }) {};
|
|
35677
|
-
/**
|
|
35678
35654
|
* Whether the whole-project dead-code result cache
|
|
35679
35655
|
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35680
35656
|
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
@@ -37386,10 +37362,10 @@ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON
|
|
|
37386
37362
|
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
37387
37363
|
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37388
37364
|
})).digest("hex");
|
|
37389
|
-
const validateDiagnostic$
|
|
37365
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
37390
37366
|
const decodeCachedDiagnostics = (raw) => {
|
|
37391
37367
|
try {
|
|
37392
|
-
for (const entry of raw) validateDiagnostic$
|
|
37368
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
37393
37369
|
return raw;
|
|
37394
37370
|
} catch {
|
|
37395
37371
|
return null;
|
|
@@ -38879,7 +38855,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38879
38855
|
}
|
|
38880
38856
|
return enabled;
|
|
38881
38857
|
};
|
|
38882
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection
|
|
38858
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38883
38859
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38884
38860
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38885
38861
|
const jsPlugins = [];
|
|
@@ -38891,7 +38867,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38891
38867
|
if (!rule) continue;
|
|
38892
38868
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38893
38869
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38894
|
-
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38895
38870
|
if (rule.scan !== void 0) continue;
|
|
38896
38871
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38897
38872
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38936,23 +38911,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38936
38911
|
}
|
|
38937
38912
|
};
|
|
38938
38913
|
};
|
|
38939
|
-
const validateDiagnostic
|
|
38914
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38940
38915
|
const decodeFileDiagnostics = (raw) => {
|
|
38941
38916
|
if (!Array.isArray(raw)) return null;
|
|
38942
38917
|
try {
|
|
38943
|
-
for (const entry of raw) validateDiagnostic
|
|
38918
|
+
for (const entry of raw) validateDiagnostic(entry);
|
|
38944
38919
|
return raw;
|
|
38945
38920
|
} catch {
|
|
38946
38921
|
return null;
|
|
38947
38922
|
}
|
|
38948
38923
|
};
|
|
38949
|
-
const emptyCache
|
|
38924
|
+
const emptyCache = () => ({
|
|
38950
38925
|
version: 1,
|
|
38951
38926
|
rulesets: {}
|
|
38952
38927
|
});
|
|
38953
|
-
const loadRulesetEntries
|
|
38928
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
38954
38929
|
const entries = /* @__PURE__ */ new Map();
|
|
38955
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache
|
|
38930
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38956
38931
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38957
38932
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38958
38933
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
@@ -38964,156 +38939,20 @@ const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
|
38964
38939
|
};
|
|
38965
38940
|
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38966
38941
|
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38967
|
-
const entries = loadRulesetEntries
|
|
38942
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38968
38943
|
return {
|
|
38969
38944
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38970
38945
|
store: (fileKey, diagnostics) => {
|
|
38971
38946
|
entries.delete(fileKey);
|
|
38972
38947
|
entries.set(fileKey, diagnostics);
|
|
38973
38948
|
},
|
|
38974
|
-
persist: () => {
|
|
38975
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38976
|
-
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38977
|
-
const existingBucket = rulesets[rulesetHash];
|
|
38978
|
-
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38979
|
-
const ourFiles = {};
|
|
38980
|
-
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
38981
|
-
const cappedEntries = Object.entries({
|
|
38982
|
-
...existingFiles,
|
|
38983
|
-
...ourFiles
|
|
38984
|
-
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
38985
|
-
rulesets[rulesetHash] = {
|
|
38986
|
-
updatedAtMs: Date.now(),
|
|
38987
|
-
files: Object.fromEntries(cappedEntries)
|
|
38988
|
-
};
|
|
38989
|
-
atomicWriteJson(cacheFilePath, {
|
|
38990
|
-
version: 1,
|
|
38991
|
-
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
38992
|
-
});
|
|
38993
|
-
}
|
|
38994
|
-
};
|
|
38995
|
-
};
|
|
38996
|
-
const hashFileContents = (filePath) => {
|
|
38997
|
-
try {
|
|
38998
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
38999
|
-
} catch {
|
|
39000
|
-
return null;
|
|
39001
|
-
}
|
|
39002
|
-
};
|
|
39003
|
-
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39004
|
-
/**
|
|
39005
|
-
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39006
|
-
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39007
|
-
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39008
|
-
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39009
|
-
*
|
|
39010
|
-
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39011
|
-
* (missing, a directory, permission failure). Source files reuse the
|
|
39012
|
-
* hashes the cache-partition loop already computed
|
|
39013
|
-
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39014
|
-
* tsconfig, ignored files) is hashed on demand.
|
|
39015
|
-
* - `exists` — the existence class a module-resolution candidate probe
|
|
39016
|
-
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39017
|
-
* `statSync` follows symlinks, matching the resolvers).
|
|
39018
|
-
*
|
|
39019
|
-
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39020
|
-
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39021
|
-
* tsconfig chain) repeat across many files' probe sets.
|
|
39022
|
-
*/
|
|
39023
|
-
const createSidecarProbeAnswerResolver = (input) => {
|
|
39024
|
-
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39025
|
-
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39026
|
-
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39027
|
-
const contentAnswer = (relativePath) => {
|
|
39028
|
-
const memoized = contentAnswerByPath.get(relativePath);
|
|
39029
|
-
if (memoized !== void 0) return memoized;
|
|
39030
|
-
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39031
|
-
contentAnswerByPath.set(relativePath, answer);
|
|
39032
|
-
return answer;
|
|
39033
|
-
};
|
|
39034
|
-
const existsAnswer = (relativePath) => {
|
|
39035
|
-
const memoized = existsAnswerByPath.get(relativePath);
|
|
39036
|
-
if (memoized !== void 0) return memoized;
|
|
39037
|
-
let answer;
|
|
39038
|
-
try {
|
|
39039
|
-
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39040
|
-
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39041
|
-
} catch {
|
|
39042
|
-
answer = "none";
|
|
39043
|
-
}
|
|
39044
|
-
existsAnswerByPath.set(relativePath, answer);
|
|
39045
|
-
return answer;
|
|
39046
|
-
};
|
|
39047
|
-
return {
|
|
39048
|
-
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39049
|
-
toRelativePath
|
|
39050
|
-
};
|
|
39051
|
-
};
|
|
39052
|
-
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39053
|
-
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39054
|
-
const decodeSidecarFileEntry = (raw) => {
|
|
39055
|
-
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39056
|
-
const probes = [];
|
|
39057
|
-
for (const rawProbe of raw.probes) {
|
|
39058
|
-
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39059
|
-
const [kind, probePath, answer] = rawProbe;
|
|
39060
|
-
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39061
|
-
probes.push({
|
|
39062
|
-
kind,
|
|
39063
|
-
path: probePath,
|
|
39064
|
-
answer
|
|
39065
|
-
});
|
|
39066
|
-
}
|
|
39067
|
-
try {
|
|
39068
|
-
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
39069
|
-
} catch {
|
|
39070
|
-
return null;
|
|
39071
|
-
}
|
|
39072
|
-
return {
|
|
39073
|
-
probes,
|
|
39074
|
-
diagnostics: raw.diagnostics
|
|
39075
|
-
};
|
|
39076
|
-
};
|
|
39077
|
-
const encodeSidecarFileEntry = (entry) => ({
|
|
39078
|
-
probes: entry.probes.map((probe) => [
|
|
39079
|
-
probe.kind,
|
|
39080
|
-
probe.path,
|
|
39081
|
-
probe.answer
|
|
39082
|
-
]),
|
|
39083
|
-
diagnostics: entry.diagnostics
|
|
39084
|
-
});
|
|
39085
|
-
const emptyCache = () => ({
|
|
39086
|
-
version: 1,
|
|
39087
|
-
rulesets: {}
|
|
39088
|
-
});
|
|
39089
|
-
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39090
|
-
const entries = /* @__PURE__ */ new Map();
|
|
39091
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39092
|
-
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39093
|
-
const bucket = persisted.rulesets[rulesetHash];
|
|
39094
|
-
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39095
|
-
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39096
|
-
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
39097
|
-
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39098
|
-
}
|
|
39099
|
-
return entries;
|
|
39100
|
-
};
|
|
39101
|
-
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39102
|
-
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
39103
|
-
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39104
|
-
return {
|
|
39105
|
-
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39106
|
-
store: (fileKey, entry) => {
|
|
39107
|
-
entries.delete(fileKey);
|
|
39108
|
-
entries.set(fileKey, entry);
|
|
39109
|
-
},
|
|
39110
38949
|
persist: () => {
|
|
39111
38950
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39112
38951
|
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39113
38952
|
const existingBucket = rulesets[rulesetHash];
|
|
39114
38953
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39115
38954
|
const ourFiles = {};
|
|
39116
|
-
for (const [fileKey,
|
|
38955
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
39117
38956
|
const cappedEntries = Object.entries({
|
|
39118
38957
|
...existingFiles,
|
|
39119
38958
|
...ourFiles
|
|
@@ -40128,6 +39967,13 @@ const validateRuleRegistration = () => {
|
|
|
40128
39967
|
].filter((entry) => entry !== null).join("; ");
|
|
40129
39968
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
40130
39969
|
};
|
|
39970
|
+
const hashFileContents = (filePath) => {
|
|
39971
|
+
try {
|
|
39972
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39973
|
+
} catch {
|
|
39974
|
+
return null;
|
|
39975
|
+
}
|
|
39976
|
+
};
|
|
40131
39977
|
/**
|
|
40132
39978
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
40133
39979
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40209,106 +40055,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40209
40055
|
NFS.closeSync(fileHandle);
|
|
40210
40056
|
}
|
|
40211
40057
|
};
|
|
40212
|
-
/**
|
|
40213
|
-
* Attributes diagnostics back to the file that produced them by the
|
|
40214
|
-
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40215
|
-
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40216
|
-
* store rather than risk caching a wrong empty result for a file that
|
|
40217
|
-
* actually had diagnostics.
|
|
40218
|
-
*/
|
|
40219
|
-
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40220
|
-
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40221
|
-
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40222
|
-
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40223
|
-
for (const diagnostic of diagnostics) {
|
|
40224
|
-
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40225
|
-
if (file === void 0) return null;
|
|
40226
|
-
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40227
|
-
fileDiagnostics.push(diagnostic);
|
|
40228
|
-
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40229
|
-
}
|
|
40230
|
-
return diagnosticsByFile;
|
|
40231
|
-
};
|
|
40232
|
-
/**
|
|
40233
|
-
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40234
|
-
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40235
|
-
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40236
|
-
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40237
|
-
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40238
|
-
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40239
|
-
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40240
|
-
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40241
|
-
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40242
|
-
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40243
|
-
*/
|
|
40244
|
-
const collectSidecarProbesForFiles = async (input) => {
|
|
40245
|
-
const buildProbes = (file) => {
|
|
40246
|
-
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40247
|
-
let trace;
|
|
40248
|
-
try {
|
|
40249
|
-
trace = collectCrossFileDependencyProbes({
|
|
40250
|
-
absoluteFilePath,
|
|
40251
|
-
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40252
|
-
ruleIds: input.boundedSidecarRuleIds
|
|
40253
|
-
});
|
|
40254
|
-
} catch {
|
|
40255
|
-
return null;
|
|
40256
|
-
}
|
|
40257
|
-
if (trace === null) return null;
|
|
40258
|
-
const probes = [];
|
|
40259
|
-
for (const contentPath of trace.contentPaths) {
|
|
40260
|
-
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40261
|
-
probes.push({
|
|
40262
|
-
kind: "content",
|
|
40263
|
-
path: relativePath,
|
|
40264
|
-
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40265
|
-
});
|
|
40266
|
-
}
|
|
40267
|
-
for (const existencePath of trace.existencePaths) {
|
|
40268
|
-
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40269
|
-
probes.push({
|
|
40270
|
-
kind: "exists",
|
|
40271
|
-
path: relativePath,
|
|
40272
|
-
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40273
|
-
});
|
|
40274
|
-
}
|
|
40275
|
-
return probes;
|
|
40276
|
-
};
|
|
40277
|
-
const probesByFile = /* @__PURE__ */ new Map();
|
|
40278
|
-
let collectSliceStartedAt = performance.now();
|
|
40279
|
-
for (const file of input.files) {
|
|
40280
|
-
probesByFile.set(file, buildProbes(file));
|
|
40281
|
-
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40282
|
-
await yieldToEventLoop();
|
|
40283
|
-
collectSliceStartedAt = performance.now();
|
|
40284
|
-
}
|
|
40285
|
-
}
|
|
40286
|
-
return probesByFile;
|
|
40287
|
-
};
|
|
40288
|
-
/**
|
|
40289
|
-
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40290
|
-
* the next scan can replay instead of re-linting.
|
|
40291
|
-
*/
|
|
40292
|
-
const storeSidecarEntries = (input) => {
|
|
40293
|
-
let didStoreAnyEntry = false;
|
|
40294
|
-
for (const pass of input.storablePasses) {
|
|
40295
|
-
if (!pass.isTrusted) continue;
|
|
40296
|
-
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40297
|
-
if (diagnosticsByFile === null) continue;
|
|
40298
|
-
for (const file of pass.files) {
|
|
40299
|
-
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40300
|
-
if (cacheKey === void 0) continue;
|
|
40301
|
-
const probes = pass.probesByFile.get(file);
|
|
40302
|
-
if (probes === void 0 || probes === null) continue;
|
|
40303
|
-
input.sidecarCache.store(cacheKey, {
|
|
40304
|
-
probes,
|
|
40305
|
-
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40306
|
-
});
|
|
40307
|
-
didStoreAnyEntry = true;
|
|
40308
|
-
}
|
|
40309
|
-
}
|
|
40310
|
-
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40311
|
-
};
|
|
40312
40058
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40313
40059
|
/**
|
|
40314
40060
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40350,7 +40096,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40350
40096
|
* 6. always restore disable directives + clean up the temp dir
|
|
40351
40097
|
*/
|
|
40352
40098
|
const runOxlint = async (options) => {
|
|
40353
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false,
|
|
40099
|
+
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;
|
|
40354
40100
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40355
40101
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40356
40102
|
validateRuleRegistration();
|
|
@@ -40368,8 +40114,7 @@ const runOxlint = async (options) => {
|
|
|
40368
40114
|
severityControls,
|
|
40369
40115
|
userPlugins,
|
|
40370
40116
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40371
|
-
ruleSelection: overrides.ruleSelection
|
|
40372
|
-
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40117
|
+
ruleSelection: overrides.ruleSelection
|
|
40373
40118
|
});
|
|
40374
40119
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40375
40120
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40472,18 +40217,15 @@ const runOxlint = async (options) => {
|
|
|
40472
40217
|
});
|
|
40473
40218
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40474
40219
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40475
|
-
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40476
40220
|
const missFiles = [];
|
|
40477
40221
|
const replayedDiagnostics = [];
|
|
40478
40222
|
let hashSliceStartedAt = performance.now();
|
|
40479
40223
|
for (const candidateFile of candidateFiles) {
|
|
40480
|
-
const
|
|
40481
|
-
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40224
|
+
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40482
40225
|
if (contentHash === null) {
|
|
40483
40226
|
missFiles.push(candidateFile);
|
|
40484
40227
|
continue;
|
|
40485
40228
|
}
|
|
40486
|
-
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40487
40229
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40488
40230
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40489
40231
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40495,111 +40237,43 @@ const runOxlint = async (options) => {
|
|
|
40495
40237
|
}
|
|
40496
40238
|
}
|
|
40497
40239
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40498
|
-
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40499
|
-
extendsPaths: [],
|
|
40500
|
-
ruleSelection: "sidecar"
|
|
40501
|
-
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40502
|
-
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40503
|
-
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40504
|
-
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40505
|
-
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40506
|
-
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40507
|
-
config: buildConfig({
|
|
40508
|
-
extendsPaths: [],
|
|
40509
|
-
ruleSelection: "sidecar",
|
|
40510
|
-
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40511
|
-
}),
|
|
40512
|
-
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40513
|
-
ignorePatterns: combinedPatterns,
|
|
40514
|
-
tsconfigContent
|
|
40515
|
-
})) : null;
|
|
40516
|
-
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40517
|
-
rootDirectory,
|
|
40518
|
-
contentHashByRelativePath
|
|
40519
|
-
});
|
|
40520
|
-
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40521
|
-
files: missFiles,
|
|
40522
|
-
rootDirectory,
|
|
40523
|
-
boundedSidecarRuleIds,
|
|
40524
|
-
probeAnswers
|
|
40525
|
-
});
|
|
40526
40240
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40527
40241
|
extendsPaths: [],
|
|
40528
40242
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40529
40243
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40530
40244
|
const missFileSet = new Set(missFiles);
|
|
40531
|
-
const
|
|
40532
|
-
const sidecarReplayedDiagnostics = [];
|
|
40533
|
-
const sidecarLintFiles = [];
|
|
40534
|
-
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40535
|
-
else {
|
|
40536
|
-
let verifySliceStartedAt = performance.now();
|
|
40537
|
-
for (const hitFile of hitFiles) {
|
|
40538
|
-
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40539
|
-
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40540
|
-
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40541
|
-
else sidecarLintFiles.push(hitFile);
|
|
40542
|
-
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40543
|
-
await yieldToEventLoop();
|
|
40544
|
-
verifySliceStartedAt = performance.now();
|
|
40545
|
-
}
|
|
40546
|
-
}
|
|
40547
|
-
}
|
|
40548
|
-
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40549
|
-
files: sidecarLintFiles,
|
|
40550
|
-
rootDirectory,
|
|
40551
|
-
boundedSidecarRuleIds,
|
|
40552
|
-
probeAnswers
|
|
40553
|
-
});
|
|
40554
|
-
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40555
|
-
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40556
|
-
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40557
|
-
extendsPaths: [],
|
|
40558
|
-
ruleSelection: "sidecar",
|
|
40559
|
-
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40560
|
-
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40561
|
-
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40245
|
+
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40562
40246
|
extendsPaths: [],
|
|
40563
|
-
ruleSelection: "sidecar"
|
|
40564
|
-
|
|
40565
|
-
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40566
|
-
diagnostics: [],
|
|
40567
|
-
didDropReactHooksJsPlugin: false,
|
|
40568
|
-
hadPartialFailure: false
|
|
40569
|
-
};
|
|
40247
|
+
ruleSelection: "sidecar"
|
|
40248
|
+
}), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
|
|
40570
40249
|
const freshCacheableDiagnostics = [];
|
|
40571
40250
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40572
40251
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40573
|
-
|
|
40574
|
-
const
|
|
40575
|
-
|
|
40252
|
+
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40253
|
+
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
40254
|
+
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40255
|
+
let isAttributionSound = true;
|
|
40256
|
+
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40257
|
+
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40258
|
+
if (missFile === void 0) {
|
|
40259
|
+
isAttributionSound = false;
|
|
40260
|
+
break;
|
|
40261
|
+
}
|
|
40262
|
+
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40263
|
+
fileDiagnostics.push(diagnostic);
|
|
40264
|
+
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40265
|
+
}
|
|
40266
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40576
40267
|
for (const missFile of missFiles) {
|
|
40577
40268
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40578
40269
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40579
40270
|
}
|
|
40580
40271
|
cache.persist();
|
|
40581
40272
|
}
|
|
40582
|
-
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40583
|
-
sidecarCache,
|
|
40584
|
-
cacheKeyByFile,
|
|
40585
|
-
storablePasses: [{
|
|
40586
|
-
files: sidecarLintFiles,
|
|
40587
|
-
diagnostics: boundedSidecarResult.diagnostics,
|
|
40588
|
-
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40589
|
-
probesByFile: await sidecarProbesTask
|
|
40590
|
-
}, {
|
|
40591
|
-
files: missFiles,
|
|
40592
|
-
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40593
|
-
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40594
|
-
probesByFile: await missProbesTask
|
|
40595
|
-
}]
|
|
40596
|
-
});
|
|
40597
40273
|
return dedupeDiagnostics([
|
|
40598
40274
|
...replayedDiagnostics,
|
|
40599
40275
|
...fullResult.diagnostics,
|
|
40600
|
-
...
|
|
40601
|
-
...boundedSidecarResult.diagnostics,
|
|
40602
|
-
...unboundedSidecarResult.diagnostics
|
|
40276
|
+
...sidecarResult.diagnostics
|
|
40603
40277
|
]);
|
|
40604
40278
|
}
|
|
40605
40279
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40697,7 +40371,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40697
40371
|
const concurrency = yield* OxlintConcurrency;
|
|
40698
40372
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40699
40373
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40700
|
-
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40701
40374
|
const collectedFailures = [];
|
|
40702
40375
|
const diagnostics = yield* tryPromise({
|
|
40703
40376
|
try: (signal) => runOxlint({
|
|
@@ -40716,9 +40389,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40716
40389
|
},
|
|
40717
40390
|
onFileProgress: input.onFileProgress,
|
|
40718
40391
|
perFileLintCacheEnabled,
|
|
40719
|
-
sidecarLintCacheEnabled,
|
|
40720
40392
|
onCacheStats: input.onCacheStats,
|
|
40721
|
-
onSidecarStats: input.onSidecarStats,
|
|
40722
40393
|
spawnTimeoutMs,
|
|
40723
40394
|
outputMaxBytes,
|
|
40724
40395
|
concurrency,
|
|
@@ -41528,8 +41199,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41528
41199
|
let lastReportedTotalFileCount = 0;
|
|
41529
41200
|
let lintCacheHitFileCount = null;
|
|
41530
41201
|
let lintCacheTotalFileCount = null;
|
|
41531
|
-
let lintSidecarReplayedFileCount = null;
|
|
41532
|
-
let lintSidecarTotalFileCount = null;
|
|
41533
41202
|
let deadCodeCacheHit = null;
|
|
41534
41203
|
let deadCodeSummaryCacheHits = null;
|
|
41535
41204
|
let deadCodeSummaryCacheMisses = null;
|
|
@@ -41552,10 +41221,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41552
41221
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41553
41222
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41554
41223
|
},
|
|
41555
|
-
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41556
|
-
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41557
|
-
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41558
|
-
},
|
|
41559
41224
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41560
41225
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41561
41226
|
yield* set(lintFailure, {
|
|
@@ -41666,8 +41331,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41666
41331
|
securityScanFailed,
|
|
41667
41332
|
lintCacheHitFileCount,
|
|
41668
41333
|
lintCacheTotalFileCount,
|
|
41669
|
-
lintSidecarReplayedFileCount,
|
|
41670
|
-
lintSidecarTotalFileCount,
|
|
41671
41334
|
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41672
41335
|
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41673
41336
|
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
@@ -44050,5 +43713,5 @@ const startLanguageServer = () => {
|
|
|
44050
43713
|
};
|
|
44051
43714
|
//#endregion
|
|
44052
43715
|
export { startLanguageServer };
|
|
44053
|
-
!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]="
|
|
44054
|
-
//# debugId=
|
|
43716
|
+
!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]="c5ee1532-72b6-5d6f-83ae-7bb4dccf2226")}catch(e){}}();
|
|
43717
|
+
//# debugId=c5ee1532-72b6-5d6f-83ae-7bb4dccf2226
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.6.3-dev.
|
|
3
|
+
"version": "0.6.3-dev.ce49250",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
68
|
"deslop-js": "0.6.3",
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.6.3-dev.
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.6.3-dev.ce49250"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|