react-doctor 0.5.8-dev.7f9e7f4 → 0.5.8-dev.80e3093
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/LICENSE +14 -1
- package/README.md +9 -3
- package/dist/cli.js +9571 -1520
- package/dist/index.d.ts +11 -2
- package/dist/index.js +397 -146
- package/dist/lsp.js +343 -147
- package/package.json +10 -8
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";
|
|
@@ -6021,7 +6021,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
|
|
|
6021
6021
|
* @since 2.0.0
|
|
6022
6022
|
*/
|
|
6023
6023
|
const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
|
|
6024
|
-
const setImmediate = "setImmediate" in globalThis ? (f) => {
|
|
6024
|
+
const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
|
|
6025
6025
|
const timer = globalThis.setImmediate(f);
|
|
6026
6026
|
return () => globalThis.clearImmediate(timer);
|
|
6027
6027
|
} : (f) => {
|
|
@@ -6065,7 +6065,7 @@ var PriorityBuckets = class {
|
|
|
6065
6065
|
var MixedScheduler = class {
|
|
6066
6066
|
executionMode;
|
|
6067
6067
|
setImmediate;
|
|
6068
|
-
constructor(executionMode = "async", setImmediateFn = setImmediate) {
|
|
6068
|
+
constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
|
|
6069
6069
|
this.executionMode = executionMode;
|
|
6070
6070
|
this.setImmediate = setImmediateFn;
|
|
6071
6071
|
}
|
|
@@ -6090,7 +6090,7 @@ var MixedSchedulerDispatcher = class {
|
|
|
6090
6090
|
tasks = /* @__PURE__ */ new PriorityBuckets();
|
|
6091
6091
|
running = void 0;
|
|
6092
6092
|
setImmediate;
|
|
6093
|
-
constructor(setImmediateFn = setImmediate) {
|
|
6093
|
+
constructor(setImmediateFn = setImmediate$1) {
|
|
6094
6094
|
this.setImmediate = setImmediateFn;
|
|
6095
6095
|
}
|
|
6096
6096
|
/**
|
|
@@ -33730,6 +33730,10 @@ const MILLISECONDS_PER_SECOND = 1e3;
|
|
|
33730
33730
|
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
|
+
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;
|
|
33733
33737
|
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33734
33738
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33735
33739
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
@@ -33810,6 +33814,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
|
33810
33814
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33811
33815
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33812
33816
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33817
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33813
33818
|
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33814
33819
|
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33815
33820
|
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
@@ -34546,7 +34551,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34546
34551
|
NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
|
|
34547
34552
|
}
|
|
34548
34553
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34549
|
-
const canonicalizeRuleKey = (ruleKey) =>
|
|
34554
|
+
const canonicalizeRuleKey = (ruleKey) => {
|
|
34555
|
+
const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
|
|
34556
|
+
return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
|
|
34557
|
+
};
|
|
34550
34558
|
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34551
34559
|
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34552
34560
|
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
@@ -34573,7 +34581,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34573
34581
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34574
34582
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34575
34583
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34576
|
-
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}\`).`;
|
|
34577
34585
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34578
34586
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34579
34587
|
const candidates = [{
|
|
@@ -35049,6 +35057,7 @@ const collectStringSet = (values) => {
|
|
|
35049
35057
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35050
35058
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35051
35059
|
};
|
|
35060
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35052
35061
|
/**
|
|
35053
35062
|
* Pre-compiles every stateful filter and returns a single
|
|
35054
35063
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35082,11 +35091,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35082
35091
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35083
35092
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35084
35093
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35094
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35085
35095
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35086
35096
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35097
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35087
35098
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35088
35099
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35089
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
|
+
};
|
|
35090
35116
|
const isLibraryFile = (filePath) => {
|
|
35091
35117
|
let cached = libraryFileCache.get(filePath);
|
|
35092
35118
|
if (cached === void 0) {
|
|
@@ -35135,50 +35161,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35135
35161
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35136
35162
|
return false;
|
|
35137
35163
|
};
|
|
35138
|
-
|
|
35139
|
-
if (
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
if (
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35160
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35161
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35162
|
-
}
|
|
35163
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35164
|
-
const lines = getFileLines(current.filePath);
|
|
35165
|
-
if (lines) {
|
|
35166
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35167
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35168
|
-
if (evaluation.isSuppressed) return null;
|
|
35169
|
-
if (evaluation.nearMissHint) current = {
|
|
35170
|
-
...current,
|
|
35171
|
-
suppressionHint: evaluation.nearMissHint
|
|
35172
|
-
};
|
|
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);
|
|
35173
35185
|
}
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
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
|
+
};
|
|
35182
35218
|
};
|
|
35183
35219
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35184
35220
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35398,7 +35434,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35398
35434
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35399
35435
|
return Math.min(Math.floor(requested), 32);
|
|
35400
35436
|
};
|
|
35401
|
-
const readSystemFacts = () => ({
|
|
35437
|
+
const readSystemFacts$1 = () => ({
|
|
35402
35438
|
availableCores: os.availableParallelism(),
|
|
35403
35439
|
totalMemoryBytes: os.totalmem(),
|
|
35404
35440
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -35419,7 +35455,7 @@ const readSystemFacts = () => ({
|
|
|
35419
35455
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35420
35456
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35421
35457
|
*/
|
|
35422
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35458
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35423
35459
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35424
35460
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35425
35461
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -35460,13 +35496,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35460
35496
|
* without recompiling react-doctor. Tests override via
|
|
35461
35497
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35462
35498
|
*/
|
|
35463
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35464
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35465
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35466
|
-
const parsed = Number(raw);
|
|
35467
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35468
|
-
return parsed;
|
|
35469
|
-
} }) {};
|
|
35499
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35470
35500
|
/**
|
|
35471
35501
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35472
35502
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35610,6 +35640,13 @@ const BOOLEAN_FIELD_NAMES = [
|
|
|
35610
35640
|
"adoptExistingLintConfig"
|
|
35611
35641
|
];
|
|
35612
35642
|
const STRING_FIELD_NAMES = ["rootDir"];
|
|
35643
|
+
const STRING_ARRAY_FIELD_NAMES = [
|
|
35644
|
+
"projects",
|
|
35645
|
+
"textComponents",
|
|
35646
|
+
"rawTextWrapperComponents",
|
|
35647
|
+
"runtimeGlobals",
|
|
35648
|
+
"serverAuthFunctionNames"
|
|
35649
|
+
];
|
|
35613
35650
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
35614
35651
|
"includeTags",
|
|
35615
35652
|
"excludeTags",
|
|
@@ -35711,6 +35748,7 @@ const validateConfigTypes = (config) => {
|
|
|
35711
35748
|
const validated = { ...config };
|
|
35712
35749
|
for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
|
|
35713
35750
|
for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
|
|
35751
|
+
for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
|
|
35714
35752
|
applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
|
|
35715
35753
|
for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
|
|
35716
35754
|
applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
|
|
@@ -36897,7 +36935,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36897
36935
|
}
|
|
36898
36936
|
return true;
|
|
36899
36937
|
};
|
|
36900
|
-
const
|
|
36938
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
36939
|
+
setImmediate(resolve);
|
|
36940
|
+
});
|
|
36941
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36901
36942
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36902
36943
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36903
36944
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36912,7 +36953,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36912
36953
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36913
36954
|
}];
|
|
36914
36955
|
});
|
|
36915
|
-
if (enabledScanRules.length === 0) return
|
|
36956
|
+
if (enabledScanRules.length === 0) return null;
|
|
36916
36957
|
const diagnostics = [];
|
|
36917
36958
|
const seen = /* @__PURE__ */ new Set();
|
|
36918
36959
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36924,15 +36965,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36924
36965
|
}
|
|
36925
36966
|
return status === true;
|
|
36926
36967
|
};
|
|
36927
|
-
|
|
36928
|
-
|
|
36929
|
-
|
|
36930
|
-
|
|
36931
|
-
|
|
36932
|
-
|
|
36933
|
-
|
|
36968
|
+
const scanFile = (file) => {
|
|
36969
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
36970
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
36971
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
36972
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
36973
|
+
if (seen.has(key)) continue;
|
|
36974
|
+
seen.add(key);
|
|
36975
|
+
diagnostics.push(diagnostic);
|
|
36976
|
+
}
|
|
36977
|
+
};
|
|
36978
|
+
return {
|
|
36979
|
+
scanFile,
|
|
36980
|
+
diagnostics
|
|
36981
|
+
};
|
|
36982
|
+
};
|
|
36983
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
36984
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
36985
|
+
if (session === null) return [];
|
|
36986
|
+
let filesSinceYield = 0;
|
|
36987
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
36988
|
+
session.scanFile(file);
|
|
36989
|
+
filesSinceYield += 1;
|
|
36990
|
+
if (filesSinceYield >= 16) {
|
|
36991
|
+
filesSinceYield = 0;
|
|
36992
|
+
await yieldToEventLoop();
|
|
36993
|
+
}
|
|
36934
36994
|
}
|
|
36935
|
-
return diagnostics;
|
|
36995
|
+
return session.diagnostics;
|
|
36936
36996
|
};
|
|
36937
36997
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36938
36998
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -37149,6 +37209,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37149
37209
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37150
37210
|
};
|
|
37151
37211
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37212
|
+
const readSystemFacts = () => ({
|
|
37213
|
+
availableCores: os.availableParallelism(),
|
|
37214
|
+
totalMemoryBytes: os.totalmem(),
|
|
37215
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
37216
|
+
});
|
|
37217
|
+
/**
|
|
37218
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
37219
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
37220
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
37221
|
+
* workers that fit in available memory, floored at 1.
|
|
37222
|
+
*
|
|
37223
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
37224
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
37225
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
37226
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
37227
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
37228
|
+
* cap the per-project spawn path lacked.
|
|
37229
|
+
*
|
|
37230
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
37231
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
37232
|
+
*/
|
|
37233
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
37234
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
37235
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
37236
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
37237
|
+
};
|
|
37238
|
+
let availableSlots = -1;
|
|
37239
|
+
const waiters = [];
|
|
37240
|
+
const releaseSlot = () => {
|
|
37241
|
+
const nextWaiter = waiters.shift();
|
|
37242
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
37243
|
+
else availableSlots += 1;
|
|
37244
|
+
};
|
|
37245
|
+
/**
|
|
37246
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
37247
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
37248
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
37249
|
+
* queue and run as slots free.
|
|
37250
|
+
*
|
|
37251
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
37252
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
37253
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
37254
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
37255
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
37256
|
+
*/
|
|
37257
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
37258
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
37259
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
37260
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
37261
|
+
else await new Promise((resolve, reject) => {
|
|
37262
|
+
const waiter = () => {
|
|
37263
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37264
|
+
resolve();
|
|
37265
|
+
};
|
|
37266
|
+
const onAbort = () => {
|
|
37267
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
37268
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
37269
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
37270
|
+
};
|
|
37271
|
+
waiters.push(waiter);
|
|
37272
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37273
|
+
});
|
|
37274
|
+
try {
|
|
37275
|
+
return await task();
|
|
37276
|
+
} finally {
|
|
37277
|
+
releaseSlot();
|
|
37278
|
+
}
|
|
37279
|
+
};
|
|
37152
37280
|
/**
|
|
37153
37281
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37154
37282
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37173,6 +37301,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37173
37301
|
};
|
|
37174
37302
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37175
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
|
+
]);
|
|
37176
37309
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37177
37310
|
const inputChunks = [];
|
|
37178
37311
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37439,14 +37572,17 @@ const checkDeadCode = async (options) => {
|
|
|
37439
37572
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37440
37573
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37441
37574
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37442
|
-
const
|
|
37443
|
-
|
|
37444
|
-
|
|
37445
|
-
|
|
37446
|
-
|
|
37447
|
-
|
|
37448
|
-
|
|
37449
|
-
|
|
37575
|
+
const spawnAndRun = () => {
|
|
37576
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37577
|
+
rootDirectory,
|
|
37578
|
+
entryPatterns,
|
|
37579
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37580
|
+
ignorePatterns,
|
|
37581
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37582
|
+
parseConcurrency: options.parseConcurrency
|
|
37583
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37584
|
+
};
|
|
37585
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37450
37586
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37451
37587
|
const diagnostics = [];
|
|
37452
37588
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37475,6 +37611,7 @@ const checkDeadCode = async (options) => {
|
|
|
37475
37611
|
});
|
|
37476
37612
|
}
|
|
37477
37613
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37614
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37478
37615
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37479
37616
|
diagnostics.push({
|
|
37480
37617
|
filePath: "package.json",
|
|
@@ -37653,7 +37790,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37653
37790
|
return yield* tryPromise({
|
|
37654
37791
|
try: (signal) => checkDeadCode({
|
|
37655
37792
|
rootDirectory: input.rootDirectory,
|
|
37656
|
-
userConfig: input.userConfig,
|
|
37657
37793
|
parseConcurrency: input.parseConcurrency,
|
|
37658
37794
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37659
37795
|
abortSignal: signal
|
|
@@ -37776,6 +37912,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37776
37912
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37777
37913
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37778
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
|
+
};
|
|
37779
37920
|
/**
|
|
37780
37921
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37781
37922
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37847,43 +37988,47 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37847
37988
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
37848
37989
|
* sees a single failure channel.
|
|
37849
37990
|
*/
|
|
37850
|
-
const runCommand = (input) =>
|
|
37851
|
-
const
|
|
37852
|
-
cwd: input.directory,
|
|
37853
|
-
env: input.env,
|
|
37854
|
-
extendEnv: true
|
|
37855
|
-
}));
|
|
37856
|
-
const maxStdoutBytes = input.maxStdoutBytes;
|
|
37857
|
-
const stdoutByteCount = yield* make$13(0);
|
|
37858
|
-
const [stdout, stderr, status] = yield* all([
|
|
37859
|
-
mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37860
|
-
args: [...input.args],
|
|
37861
|
-
directory: input.directory,
|
|
37862
|
-
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
37863
|
-
}) })) : void_)))))),
|
|
37864
|
-
mkString(decodeText(handle.stderr)),
|
|
37865
|
-
handle.exitCode
|
|
37866
|
-
], { concurrency: 3 });
|
|
37867
|
-
return {
|
|
37868
|
-
status,
|
|
37869
|
-
stdout,
|
|
37870
|
-
stderr
|
|
37871
|
-
};
|
|
37872
|
-
})).pipe(catchTag$1("PlatformError", (cause) => {
|
|
37873
|
-
if (input.command !== "git") return succeed$2({
|
|
37991
|
+
const runCommand = (input) => {
|
|
37992
|
+
const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
|
|
37874
37993
|
status: 127,
|
|
37875
37994
|
stdout: "",
|
|
37876
37995
|
stderr: String(cause)
|
|
37877
|
-
})
|
|
37878
|
-
return new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37996
|
+
}) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37879
37997
|
args: [...input.args],
|
|
37880
37998
|
directory: input.directory,
|
|
37881
37999
|
cause
|
|
37882
|
-
}) });
|
|
37883
|
-
|
|
37884
|
-
|
|
37885
|
-
|
|
37886
|
-
|
|
38000
|
+
}) }));
|
|
38001
|
+
return scoped(gen(function* () {
|
|
38002
|
+
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
38003
|
+
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
38004
|
+
const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
|
|
38005
|
+
if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
|
|
38006
|
+
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
38007
|
+
cwd: input.directory,
|
|
38008
|
+
env: input.env,
|
|
38009
|
+
extendEnv: true
|
|
38010
|
+
}));
|
|
38011
|
+
const maxStdoutBytes = input.maxStdoutBytes;
|
|
38012
|
+
const stdoutByteCount = yield* make$13(0);
|
|
38013
|
+
const [stdout, stderr, status] = yield* all([
|
|
38014
|
+
mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
38015
|
+
args: [...input.args],
|
|
38016
|
+
directory: input.directory,
|
|
38017
|
+
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
38018
|
+
}) })) : void_)))))),
|
|
38019
|
+
mkString(decodeText(handle.stderr)),
|
|
38020
|
+
handle.exitCode
|
|
38021
|
+
], { concurrency: 3 });
|
|
38022
|
+
return {
|
|
38023
|
+
status,
|
|
38024
|
+
stdout,
|
|
38025
|
+
stderr
|
|
38026
|
+
};
|
|
38027
|
+
})).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
|
|
38028
|
+
"git.command": input.command,
|
|
38029
|
+
"git.subcommand": input.args[0] ?? ""
|
|
38030
|
+
} }));
|
|
38031
|
+
};
|
|
37887
38032
|
const runGit = (directory, args) => runCommand({
|
|
37888
38033
|
command: "git",
|
|
37889
38034
|
args,
|
|
@@ -37916,7 +38061,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37916
38061
|
"rev-parse",
|
|
37917
38062
|
"--verify",
|
|
37918
38063
|
branch
|
|
37919
|
-
]).pipe(map$3((result) => result.status === 0));
|
|
38064
|
+
]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
|
|
37920
38065
|
const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
|
|
37921
38066
|
const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
|
|
37922
38067
|
"merge-base",
|
|
@@ -38026,6 +38171,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38026
38171
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38027
38172
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38028
38173
|
if (baseBranch === null) return null;
|
|
38174
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38029
38175
|
if (explicitBaseBranch !== void 0) {
|
|
38030
38176
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38031
38177
|
}
|
|
@@ -38130,7 +38276,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38130
38276
|
]);
|
|
38131
38277
|
if (result.status !== 0) return null;
|
|
38132
38278
|
return parseChangedLineRanges(result.stdout);
|
|
38133
|
-
}).pipe(withSpan("Git.changedLineRanges"))
|
|
38279
|
+
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38134
38280
|
});
|
|
38135
38281
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
38136
38282
|
/**
|
|
@@ -38376,7 +38522,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38376
38522
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38377
38523
|
return clone;
|
|
38378
38524
|
};
|
|
38379
|
-
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");
|
|
38380
38526
|
/**
|
|
38381
38527
|
* Loads a plugin module via the local require resolver and extracts
|
|
38382
38528
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38654,24 +38800,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38654
38800
|
updatedAtMs: Date.now(),
|
|
38655
38801
|
files: Object.fromEntries(cappedEntries)
|
|
38656
38802
|
};
|
|
38657
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38658
|
-
const prunedRulesets = {};
|
|
38659
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38660
38803
|
atomicWriteJson(cacheFilePath, {
|
|
38661
38804
|
version: 1,
|
|
38662
|
-
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))
|
|
38663
38806
|
});
|
|
38664
38807
|
}
|
|
38665
38808
|
};
|
|
38666
38809
|
};
|
|
38667
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
|
+
};
|
|
38668
38828
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38669
38829
|
"oxlint/package.json",
|
|
38670
38830
|
"oxlint-plugin-react-doctor/package.json",
|
|
38671
38831
|
"eslint-plugin-react-hooks/package.json"
|
|
38672
38832
|
];
|
|
38673
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38674
|
-
const versions = [`node=${
|
|
38833
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38834
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38675
38835
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38676
38836
|
const packageJson = bundledRequire(specifier);
|
|
38677
38837
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39205,11 +39365,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39205
39365
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39206
39366
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39207
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`.";
|
|
39208
39369
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39209
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39370
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39210
39371
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39211
|
-
if (!normalizedSummary) return
|
|
39212
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39372
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39373
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39213
39374
|
};
|
|
39214
39375
|
const PLUGIN_CATEGORY_MAP = {
|
|
39215
39376
|
react: "Bugs",
|
|
@@ -39258,7 +39419,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39258
39419
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39259
39420
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39260
39421
|
return {
|
|
39261
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39422
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39262
39423
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39263
39424
|
};
|
|
39264
39425
|
}
|
|
@@ -39526,21 +39687,27 @@ const spawnLintBatches = async (input) => {
|
|
|
39526
39687
|
const allDiagnostics = [];
|
|
39527
39688
|
const droppedFiles = [];
|
|
39528
39689
|
let firstDropReason = null;
|
|
39529
|
-
const
|
|
39530
|
-
const spawnLintBatch = async (batch, depth) => {
|
|
39690
|
+
const spawnLintBatch = async (batch, depth, splitBudget) => {
|
|
39531
39691
|
const batchArgs = [...baseArgs, ...batch];
|
|
39532
39692
|
try {
|
|
39533
39693
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39534
39694
|
} catch (error) {
|
|
39535
39695
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39536
|
-
|
|
39537
|
-
|
|
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) {
|
|
39538
39700
|
droppedFiles.push(...batch);
|
|
39539
|
-
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
|
+
}
|
|
39540
39707
|
return [];
|
|
39541
39708
|
}
|
|
39542
39709
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39543
|
-
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)];
|
|
39544
39711
|
}
|
|
39545
39712
|
};
|
|
39546
39713
|
let startedFileCount = 0;
|
|
@@ -39557,7 +39724,7 @@ const spawnLintBatches = async (input) => {
|
|
|
39557
39724
|
try {
|
|
39558
39725
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39559
39726
|
startedFileCount += batch.length;
|
|
39560
|
-
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
39727
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
|
|
39561
39728
|
scannedFileCount += batch.length;
|
|
39562
39729
|
if (onFileProgress) {
|
|
39563
39730
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39631,7 +39798,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39631
39798
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39632
39799
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39633
39800
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39634
|
-
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
|
+
}
|
|
39635
39809
|
};
|
|
39636
39810
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39637
39811
|
/**
|
|
@@ -39799,7 +39973,7 @@ const runOxlint = async (options) => {
|
|
|
39799
39973
|
extendsPaths: [],
|
|
39800
39974
|
ruleSelection: "cacheable"
|
|
39801
39975
|
}),
|
|
39802
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39976
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39803
39977
|
ignorePatterns: combinedPatterns,
|
|
39804
39978
|
tsconfigContent
|
|
39805
39979
|
});
|
|
@@ -39813,7 +39987,7 @@ const runOxlint = async (options) => {
|
|
|
39813
39987
|
missFiles.push(candidateFile);
|
|
39814
39988
|
continue;
|
|
39815
39989
|
}
|
|
39816
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
39990
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39817
39991
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39818
39992
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39819
39993
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -40129,7 +40303,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40129
40303
|
})))
|
|
40130
40304
|
});
|
|
40131
40305
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40132
|
-
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);
|
|
40133
40307
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40134
40308
|
const describeFailure = (error) => {
|
|
40135
40309
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40433,7 +40607,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40433
40607
|
medium: 2,
|
|
40434
40608
|
low: 1
|
|
40435
40609
|
};
|
|
40436
|
-
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
|
+
};
|
|
40437
40614
|
const displaySeverity = (severity) => {
|
|
40438
40615
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40439
40616
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40450,7 +40627,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40450
40627
|
typosquat: "possible typosquat"
|
|
40451
40628
|
};
|
|
40452
40629
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40453
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40630
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40454
40631
|
const summarizeAlertNote = (note) => {
|
|
40455
40632
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40456
40633
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40545,7 +40722,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40545
40722
|
* outage.
|
|
40546
40723
|
*/
|
|
40547
40724
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40548
|
-
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"))) }));
|
|
40549
40729
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40550
40730
|
};
|
|
40551
40731
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40607,7 +40787,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40607
40787
|
* diagnostics).
|
|
40608
40788
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
40609
40789
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
40610
|
-
* expo/react-native
|
|
40790
|
+
* expo/react-native), collected synchronously. The heavier
|
|
40791
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
40792
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
40793
|
+
* rather than blocking the event loop before it.
|
|
40611
40794
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40612
40795
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40613
40796
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -40627,7 +40810,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40627
40810
|
* order, so terminal output is identical either way; supply-chain
|
|
40628
40811
|
* rides alongside without a spinner.
|
|
40629
40812
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40630
|
-
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40813
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
40631
40814
|
* byte-identical regardless of which fiber settled first. The
|
|
40632
40815
|
* viewer-permission fiber is joined later, during score-metadata
|
|
40633
40816
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -40688,12 +40871,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40688
40871
|
...checkPnpmHardening(scanDirectory),
|
|
40689
40872
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40690
40873
|
...checkExpoProject(scanDirectory, project),
|
|
40691
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40692
|
-
...checkSecurityScan(scanDirectory, {
|
|
40693
|
-
project,
|
|
40694
|
-
ignoredTags: input.ignoredTags
|
|
40695
|
-
})
|
|
40874
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40696
40875
|
])));
|
|
40876
|
+
const securityScanFailedRef = yield* make$13(false);
|
|
40877
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40878
|
+
project,
|
|
40879
|
+
ignoredTags: input.ignoredTags
|
|
40880
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
40697
40881
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40698
40882
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40699
40883
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40731,7 +40915,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40731
40915
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40732
40916
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40733
40917
|
rootDirectory: scanDirectory,
|
|
40734
|
-
userConfig: resolvedConfig.config,
|
|
40735
40918
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40736
40919
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40737
40920
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40829,9 +41012,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40829
41012
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40830
41013
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40831
41014
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
41015
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40832
41016
|
yield* reporterService.finalize;
|
|
40833
41017
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40834
41018
|
...envCollected,
|
|
41019
|
+
...securityScanCollected,
|
|
40835
41020
|
...supplyChainCollected,
|
|
40836
41021
|
...lintCollected,
|
|
40837
41022
|
...deadCodeCollected
|
|
@@ -40857,6 +41042,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40857
41042
|
metadata: scoreMetadata
|
|
40858
41043
|
});
|
|
40859
41044
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41045
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40860
41046
|
return {
|
|
40861
41047
|
project,
|
|
40862
41048
|
userConfig: resolvedConfig.config,
|
|
@@ -40877,8 +41063,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40877
41063
|
scanElapsedMilliseconds,
|
|
40878
41064
|
scanConcurrency,
|
|
40879
41065
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41066
|
+
securityScanFailed,
|
|
40880
41067
|
lintCacheHitFileCount,
|
|
40881
|
-
lintCacheTotalFileCount
|
|
41068
|
+
lintCacheTotalFileCount,
|
|
41069
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40882
41070
|
};
|
|
40883
41071
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40884
41072
|
"inspect.directory": input.directory,
|
|
@@ -42903,6 +43091,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
|
|
|
42903
43091
|
const METRIC = {
|
|
42904
43092
|
cliInvoked: "cli.invoked",
|
|
42905
43093
|
cliError: "cli.error",
|
|
43094
|
+
cliEnvironmentError: "cli.env_error",
|
|
42906
43095
|
projectDetected: "project.detected",
|
|
42907
43096
|
projectPathSelected: "project.path_selected",
|
|
42908
43097
|
projectConfigSelected: "project.config_selected",
|
|
@@ -42915,6 +43104,8 @@ const METRIC = {
|
|
|
42915
43104
|
scanCheckSkipped: "scan.check_skipped",
|
|
42916
43105
|
baselineDegraded: "baseline.degraded",
|
|
42917
43106
|
ruleFired: "rule.fired",
|
|
43107
|
+
ruleDisabled: "rule.disabled",
|
|
43108
|
+
ruleSuppressed: "rule.suppressed",
|
|
42918
43109
|
lintFailed: "lint.failed",
|
|
42919
43110
|
deadCodeFailed: "deadcode.failed",
|
|
42920
43111
|
scoreUnavailable: "score.unavailable",
|
|
@@ -42927,12 +43118,17 @@ const METRIC = {
|
|
|
42927
43118
|
installWorkflow: "install.workflow",
|
|
42928
43119
|
installAgentHooks: "install.agent_hooks",
|
|
42929
43120
|
installDependency: "install.dependency",
|
|
43121
|
+
ciScaffolded: "ci.scaffolded",
|
|
43122
|
+
ciUpgraded: "ci.upgraded",
|
|
43123
|
+
ciConfigured: "ci.configured",
|
|
42930
43124
|
rulesChanged: "rules.changed",
|
|
42931
43125
|
rulesQueried: "rules.queried",
|
|
42932
43126
|
lspSessionStarted: "lsp.session.started",
|
|
42933
43127
|
lspScanCompleted: "lsp.scan.completed",
|
|
42934
43128
|
lspScanDuration: "lsp.scan.duration",
|
|
42935
|
-
lspScanDiagnostics: "lsp.scan.diagnostics"
|
|
43129
|
+
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43130
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43131
|
+
jsonOutUsed: "json.out_used"
|
|
42936
43132
|
};
|
|
42937
43133
|
//#endregion
|
|
42938
43134
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43249,5 +43445,5 @@ const startLanguageServer = () => {
|
|
|
43249
43445
|
};
|
|
43250
43446
|
//#endregion
|
|
43251
43447
|
export { startLanguageServer };
|
|
43252
|
-
!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]="
|
|
43253
|
-
//# 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
|