react-doctor 0.5.8-dev.c72b560 → 0.5.8-dev.d241e51
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/README.md +9 -3
- package/dist/cli.js +9311 -1515
- package/dist/index.d.ts +9 -0
- package/dist/index.js +160 -85
- package/dist/lsp.js +167 -86
- package/package.json +6 -5
package/dist/lsp.js
CHANGED
|
@@ -6,7 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import * as Path from "node:path";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
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";
|
|
@@ -33731,6 +33731,9 @@ const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
|
33731
33731
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
33732
33732
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33733
33733
|
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
33734
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
|
|
33735
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
|
|
33736
|
+
const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
|
|
33734
33737
|
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33735
33738
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33736
33739
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
@@ -34578,7 +34581,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34578
34581
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34579
34582
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34580
34583
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34581
|
-
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` —
|
|
34584
|
+
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
|
|
34582
34585
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34583
34586
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34584
34587
|
const candidates = [{
|
|
@@ -35054,6 +35057,7 @@ const collectStringSet = (values) => {
|
|
|
35054
35057
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35055
35058
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35056
35059
|
};
|
|
35060
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35057
35061
|
/**
|
|
35058
35062
|
* Pre-compiles every stateful filter and returns a single
|
|
35059
35063
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35087,11 +35091,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35087
35091
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35088
35092
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35089
35093
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35094
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35090
35095
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35091
35096
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35097
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35092
35098
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35093
35099
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35094
35100
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
35101
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
35102
|
+
const suppress = (diagnostic, source) => {
|
|
35103
|
+
const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
|
|
35104
|
+
const suppressionKey = `${ruleKey}\u0000${source}`;
|
|
35105
|
+
const existing = suppressions.get(suppressionKey);
|
|
35106
|
+
suppressions.set(suppressionKey, existing ? {
|
|
35107
|
+
...existing,
|
|
35108
|
+
count: existing.count + 1
|
|
35109
|
+
} : {
|
|
35110
|
+
rule: ruleKey,
|
|
35111
|
+
source,
|
|
35112
|
+
count: 1
|
|
35113
|
+
});
|
|
35114
|
+
return null;
|
|
35115
|
+
};
|
|
35095
35116
|
const isLibraryFile = (filePath) => {
|
|
35096
35117
|
let cached = libraryFileCache.get(filePath);
|
|
35097
35118
|
if (cached === void 0) {
|
|
@@ -35140,50 +35161,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35140
35161
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35141
35162
|
return false;
|
|
35142
35163
|
};
|
|
35143
|
-
|
|
35144
|
-
if (
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
35155
|
-
if (
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35165
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35166
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35167
|
-
}
|
|
35168
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35169
|
-
const lines = getFileLines(current.filePath);
|
|
35170
|
-
if (lines) {
|
|
35171
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35172
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35173
|
-
if (evaluation.isSuppressed) return null;
|
|
35174
|
-
if (evaluation.nearMissHint) current = {
|
|
35175
|
-
...current,
|
|
35176
|
-
suppressionHint: evaluation.nearMissHint
|
|
35177
|
-
};
|
|
35164
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35165
|
+
if (!hasRuntimeGlobals) return false;
|
|
35166
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35167
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35168
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35169
|
+
};
|
|
35170
|
+
return {
|
|
35171
|
+
apply: (diagnostic) => {
|
|
35172
|
+
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35173
|
+
let current = diagnostic;
|
|
35174
|
+
let explicitSeverityOverride;
|
|
35175
|
+
let explicitRuleOverride;
|
|
35176
|
+
if (severityControls) {
|
|
35177
|
+
const { ruleKey, category } = getDiagnosticRuleIdentity(current);
|
|
35178
|
+
explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
|
|
35179
|
+
explicitSeverityOverride = resolveRuleSeverityOverride({
|
|
35180
|
+
ruleKey,
|
|
35181
|
+
category
|
|
35182
|
+
}, severityControls);
|
|
35183
|
+
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
35184
|
+
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
35178
35185
|
}
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35186
|
+
if (explicitRuleOverride === void 0) {
|
|
35187
|
+
if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
|
|
35188
|
+
}
|
|
35189
|
+
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
35190
|
+
if (userConfig) {
|
|
35191
|
+
if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
|
|
35192
|
+
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35193
|
+
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
35194
|
+
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35195
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35196
|
+
}
|
|
35197
|
+
if (respectInlineDisables && current.line > 0) {
|
|
35198
|
+
const lines = getFileLines(current.filePath);
|
|
35199
|
+
if (lines) {
|
|
35200
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35201
|
+
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35202
|
+
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35203
|
+
if (evaluation.nearMissHint) current = {
|
|
35204
|
+
...current,
|
|
35205
|
+
suppressionHint: evaluation.nearMissHint
|
|
35206
|
+
};
|
|
35207
|
+
}
|
|
35208
|
+
}
|
|
35209
|
+
const fileContext = getFileContext(current.filePath);
|
|
35210
|
+
if (fileContext !== "production") current = {
|
|
35211
|
+
...current,
|
|
35212
|
+
fileContext
|
|
35213
|
+
};
|
|
35214
|
+
return current;
|
|
35215
|
+
},
|
|
35216
|
+
summarizeSuppressions: () => [...suppressions.values()]
|
|
35217
|
+
};
|
|
35187
35218
|
};
|
|
35188
35219
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35189
35220
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35465,13 +35496,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35465
35496
|
* without recompiling react-doctor. Tests override via
|
|
35466
35497
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35467
35498
|
*/
|
|
35468
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35469
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35470
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35471
|
-
const parsed = Number(raw);
|
|
35472
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35473
|
-
return parsed;
|
|
35474
|
-
} }) {};
|
|
35499
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35475
35500
|
/**
|
|
35476
35501
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35477
35502
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35619,6 +35644,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35619
35644
|
"projects",
|
|
35620
35645
|
"textComponents",
|
|
35621
35646
|
"rawTextWrapperComponents",
|
|
35647
|
+
"runtimeGlobals",
|
|
35622
35648
|
"serverAuthFunctionNames"
|
|
35623
35649
|
];
|
|
35624
35650
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -37275,6 +37301,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37275
37301
|
};
|
|
37276
37302
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37277
37303
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37304
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37305
|
+
"react-doctor",
|
|
37306
|
+
"eslint-plugin-react-doctor",
|
|
37307
|
+
"oxlint-plugin-react-doctor"
|
|
37308
|
+
]);
|
|
37278
37309
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37279
37310
|
const inputChunks = [];
|
|
37280
37311
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37580,6 +37611,7 @@ const checkDeadCode = async (options) => {
|
|
|
37580
37611
|
});
|
|
37581
37612
|
}
|
|
37582
37613
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37614
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37583
37615
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37584
37616
|
diagnostics.push({
|
|
37585
37617
|
filePath: "package.json",
|
|
@@ -37758,7 +37790,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37758
37790
|
return yield* tryPromise({
|
|
37759
37791
|
try: (signal) => checkDeadCode({
|
|
37760
37792
|
rootDirectory: input.rootDirectory,
|
|
37761
|
-
userConfig: input.userConfig,
|
|
37762
37793
|
parseConcurrency: input.parseConcurrency,
|
|
37763
37794
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37764
37795
|
abortSignal: signal
|
|
@@ -37881,6 +37912,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37881
37912
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37882
37913
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37883
37914
|
};
|
|
37915
|
+
const resolveSpawnArgsLengthCap = () => {
|
|
37916
|
+
if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
|
|
37917
|
+
if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
|
|
37918
|
+
return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
|
|
37919
|
+
};
|
|
37884
37920
|
/**
|
|
37885
37921
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37886
37922
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37965,7 +38001,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37965
38001
|
return scoped(gen(function* () {
|
|
37966
38002
|
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37967
38003
|
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37968
|
-
|
|
38004
|
+
const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
|
|
38005
|
+
if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
|
|
37969
38006
|
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37970
38007
|
cwd: input.directory,
|
|
37971
38008
|
env: input.env,
|
|
@@ -38134,6 +38171,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38134
38171
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38135
38172
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38136
38173
|
if (baseBranch === null) return null;
|
|
38174
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38137
38175
|
if (explicitBaseBranch !== void 0) {
|
|
38138
38176
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38139
38177
|
}
|
|
@@ -38484,7 +38522,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38484
38522
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38485
38523
|
return clone;
|
|
38486
38524
|
};
|
|
38487
|
-
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("
|
|
38525
|
+
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").digest("hex");
|
|
38488
38526
|
/**
|
|
38489
38527
|
* Loads a plugin module via the local require resolver and extracts
|
|
38490
38528
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38762,24 +38800,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38762
38800
|
updatedAtMs: Date.now(),
|
|
38763
38801
|
files: Object.fromEntries(cappedEntries)
|
|
38764
38802
|
};
|
|
38765
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38766
|
-
const prunedRulesets = {};
|
|
38767
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38768
38803
|
atomicWriteJson(cacheFilePath, {
|
|
38769
38804
|
version: 1,
|
|
38770
|
-
rulesets:
|
|
38805
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
38771
38806
|
});
|
|
38772
38807
|
}
|
|
38773
38808
|
};
|
|
38774
38809
|
};
|
|
38775
38810
|
const bundledRequire = createRequire(import.meta.url);
|
|
38811
|
+
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38812
|
+
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
38813
|
+
if (nodeBinaryPath === process.execPath) return process.version;
|
|
38814
|
+
const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
|
|
38815
|
+
if (cachedVersion !== void 0) return cachedVersion;
|
|
38816
|
+
let version;
|
|
38817
|
+
try {
|
|
38818
|
+
version = execFileSync(nodeBinaryPath, ["--version"], {
|
|
38819
|
+
encoding: "utf8",
|
|
38820
|
+
timeout: NODE_VERSION_PROBE_TIMEOUT_MS
|
|
38821
|
+
}).trim();
|
|
38822
|
+
} catch {
|
|
38823
|
+
version = `unknown:${nodeBinaryPath}`;
|
|
38824
|
+
}
|
|
38825
|
+
childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
|
|
38826
|
+
return version;
|
|
38827
|
+
};
|
|
38776
38828
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38777
38829
|
"oxlint/package.json",
|
|
38778
38830
|
"oxlint-plugin-react-doctor/package.json",
|
|
38779
38831
|
"eslint-plugin-react-hooks/package.json"
|
|
38780
38832
|
];
|
|
38781
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38782
|
-
const versions = [`node=${
|
|
38833
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38834
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38783
38835
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38784
38836
|
const packageJson = bundledRequire(specifier);
|
|
38785
38837
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39313,11 +39365,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39313
39365
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39314
39366
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39315
39367
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
39368
|
+
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
39316
39369
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39317
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39370
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39318
39371
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39319
|
-
if (!normalizedSummary) return
|
|
39320
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39372
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39373
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39321
39374
|
};
|
|
39322
39375
|
const PLUGIN_CATEGORY_MAP = {
|
|
39323
39376
|
react: "Bugs",
|
|
@@ -39366,7 +39419,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39366
39419
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39367
39420
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39368
39421
|
return {
|
|
39369
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39422
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39370
39423
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39371
39424
|
};
|
|
39372
39425
|
}
|
|
@@ -39634,21 +39687,27 @@ const spawnLintBatches = async (input) => {
|
|
|
39634
39687
|
const allDiagnostics = [];
|
|
39635
39688
|
const droppedFiles = [];
|
|
39636
39689
|
let firstDropReason = null;
|
|
39637
|
-
const
|
|
39638
|
-
const spawnLintBatch = async (batch, depth) => {
|
|
39690
|
+
const spawnLintBatch = async (batch, depth, splitBudget) => {
|
|
39639
39691
|
const batchArgs = [...baseArgs, ...batch];
|
|
39640
39692
|
try {
|
|
39641
39693
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39642
39694
|
} catch (error) {
|
|
39643
39695
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39644
|
-
|
|
39645
|
-
|
|
39696
|
+
splitBudget.deadlineMs ??= Date.now() + splitTotalBudgetMs;
|
|
39697
|
+
const isBudgetElapsed = Date.now() >= splitBudget.deadlineMs;
|
|
39698
|
+
const isDepthCapReached = depth >= splitMaxDepth;
|
|
39699
|
+
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
39646
39700
|
droppedFiles.push(...batch);
|
|
39647
|
-
if (firstDropReason === null)
|
|
39701
|
+
if (firstDropReason === null) {
|
|
39702
|
+
let limitHint = "";
|
|
39703
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
39704
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
39705
|
+
firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
39706
|
+
}
|
|
39648
39707
|
return [];
|
|
39649
39708
|
}
|
|
39650
39709
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39651
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
39710
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, splitBudget), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, splitBudget)];
|
|
39652
39711
|
}
|
|
39653
39712
|
};
|
|
39654
39713
|
let startedFileCount = 0;
|
|
@@ -39665,7 +39724,7 @@ const spawnLintBatches = async (input) => {
|
|
|
39665
39724
|
try {
|
|
39666
39725
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39667
39726
|
startedFileCount += batch.length;
|
|
39668
|
-
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
39727
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
|
|
39669
39728
|
scannedFileCount += batch.length;
|
|
39670
39729
|
if (onFileProgress) {
|
|
39671
39730
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39739,7 +39798,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39739
39798
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39740
39799
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39741
39800
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39742
|
-
return Path.join(os.tmpdir(),
|
|
39801
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
39802
|
+
};
|
|
39803
|
+
const resolveUserCacheScope = () => {
|
|
39804
|
+
try {
|
|
39805
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
39806
|
+
} catch {
|
|
39807
|
+
return "shared";
|
|
39808
|
+
}
|
|
39743
39809
|
};
|
|
39744
39810
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39745
39811
|
/**
|
|
@@ -39907,7 +39973,7 @@ const runOxlint = async (options) => {
|
|
|
39907
39973
|
extendsPaths: [],
|
|
39908
39974
|
ruleSelection: "cacheable"
|
|
39909
39975
|
}),
|
|
39910
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39976
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39911
39977
|
ignorePatterns: combinedPatterns,
|
|
39912
39978
|
tsconfigContent
|
|
39913
39979
|
});
|
|
@@ -39921,7 +39987,7 @@ const runOxlint = async (options) => {
|
|
|
39921
39987
|
missFiles.push(candidateFile);
|
|
39922
39988
|
continue;
|
|
39923
39989
|
}
|
|
39924
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
39990
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39925
39991
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39926
39992
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39927
39993
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -40237,7 +40303,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40237
40303
|
})))
|
|
40238
40304
|
});
|
|
40239
40305
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40240
|
-
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
|
|
40306
|
+
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
|
|
40241
40307
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40242
40308
|
const describeFailure = (error) => {
|
|
40243
40309
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40541,7 +40607,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40541
40607
|
medium: 2,
|
|
40542
40608
|
low: 1
|
|
40543
40609
|
};
|
|
40544
|
-
const severityRank = (severity) =>
|
|
40610
|
+
const severityRank = (severity) => {
|
|
40611
|
+
const normalized = severity.toLowerCase();
|
|
40612
|
+
return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
|
|
40613
|
+
};
|
|
40545
40614
|
const displaySeverity = (severity) => {
|
|
40546
40615
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40547
40616
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40558,7 +40627,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40558
40627
|
typosquat: "possible typosquat"
|
|
40559
40628
|
};
|
|
40560
40629
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40561
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40630
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40562
40631
|
const summarizeAlertNote = (note) => {
|
|
40563
40632
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40564
40633
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40653,7 +40722,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40653
40722
|
* outage.
|
|
40654
40723
|
*/
|
|
40655
40724
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40656
|
-
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(
|
|
40725
|
+
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
|
|
40726
|
+
...input,
|
|
40727
|
+
totalTimeoutMs
|
|
40728
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
40657
40729
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40658
40730
|
};
|
|
40659
40731
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40801,10 +40873,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40801
40873
|
...checkExpoProject(scanDirectory, project),
|
|
40802
40874
|
...checkReactNativeProject(scanDirectory, project)
|
|
40803
40875
|
])));
|
|
40804
|
-
const
|
|
40876
|
+
const securityScanFailedRef = yield* make$13(false);
|
|
40877
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40805
40878
|
project,
|
|
40806
40879
|
ignoredTags: input.ignoredTags
|
|
40807
|
-
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40880
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
40808
40881
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40809
40882
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40810
40883
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40842,7 +40915,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40842
40915
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40843
40916
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40844
40917
|
rootDirectory: scanDirectory,
|
|
40845
|
-
userConfig: resolvedConfig.config,
|
|
40846
40918
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40847
40919
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40848
40920
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40970,6 +41042,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40970
41042
|
metadata: scoreMetadata
|
|
40971
41043
|
});
|
|
40972
41044
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41045
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40973
41046
|
return {
|
|
40974
41047
|
project,
|
|
40975
41048
|
userConfig: resolvedConfig.config,
|
|
@@ -40990,8 +41063,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40990
41063
|
scanElapsedMilliseconds,
|
|
40991
41064
|
scanConcurrency,
|
|
40992
41065
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41066
|
+
securityScanFailed,
|
|
40993
41067
|
lintCacheHitFileCount,
|
|
40994
|
-
lintCacheTotalFileCount
|
|
41068
|
+
lintCacheTotalFileCount,
|
|
41069
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40995
41070
|
};
|
|
40996
41071
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40997
41072
|
"inspect.directory": input.directory,
|
|
@@ -43029,6 +43104,8 @@ const METRIC = {
|
|
|
43029
43104
|
scanCheckSkipped: "scan.check_skipped",
|
|
43030
43105
|
baselineDegraded: "baseline.degraded",
|
|
43031
43106
|
ruleFired: "rule.fired",
|
|
43107
|
+
ruleDisabled: "rule.disabled",
|
|
43108
|
+
ruleSuppressed: "rule.suppressed",
|
|
43032
43109
|
lintFailed: "lint.failed",
|
|
43033
43110
|
deadCodeFailed: "deadcode.failed",
|
|
43034
43111
|
scoreUnavailable: "score.unavailable",
|
|
@@ -43041,13 +43118,17 @@ const METRIC = {
|
|
|
43041
43118
|
installWorkflow: "install.workflow",
|
|
43042
43119
|
installAgentHooks: "install.agent_hooks",
|
|
43043
43120
|
installDependency: "install.dependency",
|
|
43121
|
+
ciScaffolded: "ci.scaffolded",
|
|
43122
|
+
ciUpgraded: "ci.upgraded",
|
|
43123
|
+
ciConfigured: "ci.configured",
|
|
43044
43124
|
rulesChanged: "rules.changed",
|
|
43045
43125
|
rulesQueried: "rules.queried",
|
|
43046
43126
|
lspSessionStarted: "lsp.session.started",
|
|
43047
43127
|
lspScanCompleted: "lsp.scan.completed",
|
|
43048
43128
|
lspScanDuration: "lsp.scan.duration",
|
|
43049
43129
|
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43050
|
-
aiTrainingWarningShown: "ai.training.warning_shown"
|
|
43130
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43131
|
+
jsonOutUsed: "json.out_used"
|
|
43051
43132
|
};
|
|
43052
43133
|
//#endregion
|
|
43053
43134
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43364,5 +43445,5 @@ const startLanguageServer = () => {
|
|
|
43364
43445
|
};
|
|
43365
43446
|
//#endregion
|
|
43366
43447
|
export { startLanguageServer };
|
|
43367
|
-
!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]="
|
|
43368
|
-
//# debugId=
|
|
43448
|
+
!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]="349b2e37-65e9-5f86-8168-70334a95aeec")}catch(e){}}();
|
|
43449
|
+
//# debugId=349b2e37-65e9-5f86-8168-70334a95aeec
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.5.8-dev.
|
|
3
|
+
"version": "0.5.8-dev.d241e51",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -60,12 +60,13 @@
|
|
|
60
60
|
"magicast": "^0.5.3",
|
|
61
61
|
"oxlint": ">=1.66.0 <1.67.0",
|
|
62
62
|
"prompts": "^2.4.2",
|
|
63
|
-
"typescript": ">=5.0.4 <
|
|
63
|
+
"typescript": ">=5.0.4 <6",
|
|
64
64
|
"vscode-languageserver": "^9.0.1",
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
|
+
"yaml": "^2.9.0",
|
|
67
68
|
"deslop-js": "0.5.8",
|
|
68
|
-
"oxlint-plugin-react-doctor": "0.5.8-dev.
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.5.8-dev.d241e51"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -74,8 +75,8 @@
|
|
|
74
75
|
"commander": "^14.0.3",
|
|
75
76
|
"ora": "^9.4.0",
|
|
76
77
|
"@react-doctor/api": "0.5.8",
|
|
77
|
-
"@react-doctor/
|
|
78
|
-
"@react-doctor/
|
|
78
|
+
"@react-doctor/language-server": "0.5.8",
|
|
79
|
+
"@react-doctor/core": "0.5.8"
|
|
79
80
|
},
|
|
80
81
|
"engines": {
|
|
81
82
|
"node": "^20.19.0 || >=22.13.0"
|