react-doctor 0.5.8-dev.c2ce298 → 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/LICENSE +14 -1
- package/README.md +9 -3
- package/dist/cli.js +9689 -1592
- package/dist/index.d.ts +11 -2
- package/dist/index.js +393 -145
- package/dist/lsp.js +339 -146
- package/package.json +7 -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";
|
|
@@ -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;
|
|
@@ -34576,7 +34581,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34576
34581
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34577
34582
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34578
34583
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34579
|
-
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}\`).`;
|
|
34580
34585
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34581
34586
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34582
34587
|
const candidates = [{
|
|
@@ -35052,6 +35057,7 @@ const collectStringSet = (values) => {
|
|
|
35052
35057
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35053
35058
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35054
35059
|
};
|
|
35060
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35055
35061
|
/**
|
|
35056
35062
|
* Pre-compiles every stateful filter and returns a single
|
|
35057
35063
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35085,11 +35091,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35085
35091
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35086
35092
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35087
35093
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35094
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35088
35095
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35089
35096
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35097
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35090
35098
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35091
35099
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35092
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
|
+
};
|
|
35093
35116
|
const isLibraryFile = (filePath) => {
|
|
35094
35117
|
let cached = libraryFileCache.get(filePath);
|
|
35095
35118
|
if (cached === void 0) {
|
|
@@ -35138,50 +35161,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35138
35161
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35139
35162
|
return false;
|
|
35140
35163
|
};
|
|
35141
|
-
|
|
35142
|
-
if (
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
if (
|
|
35154
|
-
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35163
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35164
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35165
|
-
}
|
|
35166
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35167
|
-
const lines = getFileLines(current.filePath);
|
|
35168
|
-
if (lines) {
|
|
35169
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35170
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35171
|
-
if (evaluation.isSuppressed) return null;
|
|
35172
|
-
if (evaluation.nearMissHint) current = {
|
|
35173
|
-
...current,
|
|
35174
|
-
suppressionHint: evaluation.nearMissHint
|
|
35175
|
-
};
|
|
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);
|
|
35176
35185
|
}
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
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
|
+
};
|
|
35185
35218
|
};
|
|
35186
35219
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35187
35220
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35401,7 +35434,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35401
35434
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35402
35435
|
return Math.min(Math.floor(requested), 32);
|
|
35403
35436
|
};
|
|
35404
|
-
const readSystemFacts = () => ({
|
|
35437
|
+
const readSystemFacts$1 = () => ({
|
|
35405
35438
|
availableCores: os.availableParallelism(),
|
|
35406
35439
|
totalMemoryBytes: os.totalmem(),
|
|
35407
35440
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -35422,7 +35455,7 @@ const readSystemFacts = () => ({
|
|
|
35422
35455
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35423
35456
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35424
35457
|
*/
|
|
35425
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35458
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35426
35459
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35427
35460
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35428
35461
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -35463,13 +35496,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35463
35496
|
* without recompiling react-doctor. Tests override via
|
|
35464
35497
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35465
35498
|
*/
|
|
35466
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35467
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35468
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35469
|
-
const parsed = Number(raw);
|
|
35470
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35471
|
-
return parsed;
|
|
35472
|
-
} }) {};
|
|
35499
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35473
35500
|
/**
|
|
35474
35501
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35475
35502
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35613,6 +35640,13 @@ const BOOLEAN_FIELD_NAMES = [
|
|
|
35613
35640
|
"adoptExistingLintConfig"
|
|
35614
35641
|
];
|
|
35615
35642
|
const STRING_FIELD_NAMES = ["rootDir"];
|
|
35643
|
+
const STRING_ARRAY_FIELD_NAMES = [
|
|
35644
|
+
"projects",
|
|
35645
|
+
"textComponents",
|
|
35646
|
+
"rawTextWrapperComponents",
|
|
35647
|
+
"runtimeGlobals",
|
|
35648
|
+
"serverAuthFunctionNames"
|
|
35649
|
+
];
|
|
35616
35650
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
35617
35651
|
"includeTags",
|
|
35618
35652
|
"excludeTags",
|
|
@@ -35714,6 +35748,7 @@ const validateConfigTypes = (config) => {
|
|
|
35714
35748
|
const validated = { ...config };
|
|
35715
35749
|
for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
|
|
35716
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));
|
|
35717
35752
|
applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
|
|
35718
35753
|
for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
|
|
35719
35754
|
applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
|
|
@@ -36900,7 +36935,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36900
36935
|
}
|
|
36901
36936
|
return true;
|
|
36902
36937
|
};
|
|
36903
|
-
const
|
|
36938
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
36939
|
+
setImmediate(resolve);
|
|
36940
|
+
});
|
|
36941
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36904
36942
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36905
36943
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36906
36944
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36915,7 +36953,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36915
36953
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36916
36954
|
}];
|
|
36917
36955
|
});
|
|
36918
|
-
if (enabledScanRules.length === 0) return
|
|
36956
|
+
if (enabledScanRules.length === 0) return null;
|
|
36919
36957
|
const diagnostics = [];
|
|
36920
36958
|
const seen = /* @__PURE__ */ new Set();
|
|
36921
36959
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36927,15 +36965,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36927
36965
|
}
|
|
36928
36966
|
return status === true;
|
|
36929
36967
|
};
|
|
36930
|
-
|
|
36931
|
-
|
|
36932
|
-
|
|
36933
|
-
|
|
36934
|
-
|
|
36935
|
-
|
|
36936
|
-
|
|
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
|
+
}
|
|
36937
36994
|
}
|
|
36938
|
-
return diagnostics;
|
|
36995
|
+
return session.diagnostics;
|
|
36939
36996
|
};
|
|
36940
36997
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36941
36998
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -37152,6 +37209,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37152
37209
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37153
37210
|
};
|
|
37154
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
|
+
};
|
|
37155
37280
|
/**
|
|
37156
37281
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37157
37282
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37176,6 +37301,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37176
37301
|
};
|
|
37177
37302
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37178
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
|
+
]);
|
|
37179
37309
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37180
37310
|
const inputChunks = [];
|
|
37181
37311
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37442,14 +37572,17 @@ const checkDeadCode = async (options) => {
|
|
|
37442
37572
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37443
37573
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37444
37574
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37445
|
-
const
|
|
37446
|
-
|
|
37447
|
-
|
|
37448
|
-
|
|
37449
|
-
|
|
37450
|
-
|
|
37451
|
-
|
|
37452
|
-
|
|
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());
|
|
37453
37586
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37454
37587
|
const diagnostics = [];
|
|
37455
37588
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37478,6 +37611,7 @@ const checkDeadCode = async (options) => {
|
|
|
37478
37611
|
});
|
|
37479
37612
|
}
|
|
37480
37613
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37614
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37481
37615
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37482
37616
|
diagnostics.push({
|
|
37483
37617
|
filePath: "package.json",
|
|
@@ -37656,7 +37790,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37656
37790
|
return yield* tryPromise({
|
|
37657
37791
|
try: (signal) => checkDeadCode({
|
|
37658
37792
|
rootDirectory: input.rootDirectory,
|
|
37659
|
-
userConfig: input.userConfig,
|
|
37660
37793
|
parseConcurrency: input.parseConcurrency,
|
|
37661
37794
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37662
37795
|
abortSignal: signal
|
|
@@ -37779,6 +37912,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37779
37912
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37780
37913
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37781
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
|
+
};
|
|
37782
37920
|
/**
|
|
37783
37921
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37784
37922
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37850,43 +37988,47 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37850
37988
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
37851
37989
|
* sees a single failure channel.
|
|
37852
37990
|
*/
|
|
37853
|
-
const runCommand = (input) =>
|
|
37854
|
-
const
|
|
37855
|
-
cwd: input.directory,
|
|
37856
|
-
env: input.env,
|
|
37857
|
-
extendEnv: true
|
|
37858
|
-
}));
|
|
37859
|
-
const maxStdoutBytes = input.maxStdoutBytes;
|
|
37860
|
-
const stdoutByteCount = yield* make$13(0);
|
|
37861
|
-
const [stdout, stderr, status] = yield* all([
|
|
37862
|
-
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({
|
|
37863
|
-
args: [...input.args],
|
|
37864
|
-
directory: input.directory,
|
|
37865
|
-
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
37866
|
-
}) })) : void_)))))),
|
|
37867
|
-
mkString(decodeText(handle.stderr)),
|
|
37868
|
-
handle.exitCode
|
|
37869
|
-
], { concurrency: 3 });
|
|
37870
|
-
return {
|
|
37871
|
-
status,
|
|
37872
|
-
stdout,
|
|
37873
|
-
stderr
|
|
37874
|
-
};
|
|
37875
|
-
})).pipe(catchTag$1("PlatformError", (cause) => {
|
|
37876
|
-
if (input.command !== "git") return succeed$2({
|
|
37991
|
+
const runCommand = (input) => {
|
|
37992
|
+
const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
|
|
37877
37993
|
status: 127,
|
|
37878
37994
|
stdout: "",
|
|
37879
37995
|
stderr: String(cause)
|
|
37880
|
-
})
|
|
37881
|
-
return new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37996
|
+
}) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37882
37997
|
args: [...input.args],
|
|
37883
37998
|
directory: input.directory,
|
|
37884
37999
|
cause
|
|
37885
|
-
}) });
|
|
37886
|
-
|
|
37887
|
-
|
|
37888
|
-
|
|
37889
|
-
|
|
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
|
+
};
|
|
37890
38032
|
const runGit = (directory, args) => runCommand({
|
|
37891
38033
|
command: "git",
|
|
37892
38034
|
args,
|
|
@@ -37919,7 +38061,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37919
38061
|
"rev-parse",
|
|
37920
38062
|
"--verify",
|
|
37921
38063
|
branch
|
|
37922
|
-
]).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)));
|
|
37923
38065
|
const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
|
|
37924
38066
|
const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
|
|
37925
38067
|
"merge-base",
|
|
@@ -38029,6 +38171,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38029
38171
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38030
38172
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38031
38173
|
if (baseBranch === null) return null;
|
|
38174
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38032
38175
|
if (explicitBaseBranch !== void 0) {
|
|
38033
38176
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38034
38177
|
}
|
|
@@ -38133,7 +38276,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38133
38276
|
]);
|
|
38134
38277
|
if (result.status !== 0) return null;
|
|
38135
38278
|
return parseChangedLineRanges(result.stdout);
|
|
38136
|
-
}).pipe(withSpan("Git.changedLineRanges"))
|
|
38279
|
+
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38137
38280
|
});
|
|
38138
38281
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
38139
38282
|
/**
|
|
@@ -38379,7 +38522,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38379
38522
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38380
38523
|
return clone;
|
|
38381
38524
|
};
|
|
38382
|
-
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");
|
|
38383
38526
|
/**
|
|
38384
38527
|
* Loads a plugin module via the local require resolver and extracts
|
|
38385
38528
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38657,24 +38800,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38657
38800
|
updatedAtMs: Date.now(),
|
|
38658
38801
|
files: Object.fromEntries(cappedEntries)
|
|
38659
38802
|
};
|
|
38660
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38661
|
-
const prunedRulesets = {};
|
|
38662
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38663
38803
|
atomicWriteJson(cacheFilePath, {
|
|
38664
38804
|
version: 1,
|
|
38665
|
-
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))
|
|
38666
38806
|
});
|
|
38667
38807
|
}
|
|
38668
38808
|
};
|
|
38669
38809
|
};
|
|
38670
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
|
+
};
|
|
38671
38828
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38672
38829
|
"oxlint/package.json",
|
|
38673
38830
|
"oxlint-plugin-react-doctor/package.json",
|
|
38674
38831
|
"eslint-plugin-react-hooks/package.json"
|
|
38675
38832
|
];
|
|
38676
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38677
|
-
const versions = [`node=${
|
|
38833
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38834
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38678
38835
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38679
38836
|
const packageJson = bundledRequire(specifier);
|
|
38680
38837
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39208,11 +39365,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39208
39365
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39209
39366
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39210
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`.";
|
|
39211
39369
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39212
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39370
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39213
39371
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39214
|
-
if (!normalizedSummary) return
|
|
39215
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39372
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39373
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39216
39374
|
};
|
|
39217
39375
|
const PLUGIN_CATEGORY_MAP = {
|
|
39218
39376
|
react: "Bugs",
|
|
@@ -39261,7 +39419,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39261
39419
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39262
39420
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39263
39421
|
return {
|
|
39264
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39422
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39265
39423
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39266
39424
|
};
|
|
39267
39425
|
}
|
|
@@ -39529,21 +39687,27 @@ const spawnLintBatches = async (input) => {
|
|
|
39529
39687
|
const allDiagnostics = [];
|
|
39530
39688
|
const droppedFiles = [];
|
|
39531
39689
|
let firstDropReason = null;
|
|
39532
|
-
const
|
|
39533
|
-
const spawnLintBatch = async (batch, depth) => {
|
|
39690
|
+
const spawnLintBatch = async (batch, depth, splitBudget) => {
|
|
39534
39691
|
const batchArgs = [...baseArgs, ...batch];
|
|
39535
39692
|
try {
|
|
39536
39693
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39537
39694
|
} catch (error) {
|
|
39538
39695
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39539
|
-
|
|
39540
|
-
|
|
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) {
|
|
39541
39700
|
droppedFiles.push(...batch);
|
|
39542
|
-
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
|
+
}
|
|
39543
39707
|
return [];
|
|
39544
39708
|
}
|
|
39545
39709
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39546
|
-
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)];
|
|
39547
39711
|
}
|
|
39548
39712
|
};
|
|
39549
39713
|
let startedFileCount = 0;
|
|
@@ -39560,7 +39724,7 @@ const spawnLintBatches = async (input) => {
|
|
|
39560
39724
|
try {
|
|
39561
39725
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39562
39726
|
startedFileCount += batch.length;
|
|
39563
|
-
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
39727
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
|
|
39564
39728
|
scannedFileCount += batch.length;
|
|
39565
39729
|
if (onFileProgress) {
|
|
39566
39730
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39634,7 +39798,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39634
39798
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39635
39799
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39636
39800
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39637
|
-
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
|
+
}
|
|
39638
39809
|
};
|
|
39639
39810
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39640
39811
|
/**
|
|
@@ -39802,7 +39973,7 @@ const runOxlint = async (options) => {
|
|
|
39802
39973
|
extendsPaths: [],
|
|
39803
39974
|
ruleSelection: "cacheable"
|
|
39804
39975
|
}),
|
|
39805
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39976
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39806
39977
|
ignorePatterns: combinedPatterns,
|
|
39807
39978
|
tsconfigContent
|
|
39808
39979
|
});
|
|
@@ -39816,7 +39987,7 @@ const runOxlint = async (options) => {
|
|
|
39816
39987
|
missFiles.push(candidateFile);
|
|
39817
39988
|
continue;
|
|
39818
39989
|
}
|
|
39819
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
39990
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39820
39991
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39821
39992
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39822
39993
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -40132,7 +40303,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40132
40303
|
})))
|
|
40133
40304
|
});
|
|
40134
40305
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40135
|
-
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);
|
|
40136
40307
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40137
40308
|
const describeFailure = (error) => {
|
|
40138
40309
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40436,7 +40607,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40436
40607
|
medium: 2,
|
|
40437
40608
|
low: 1
|
|
40438
40609
|
};
|
|
40439
|
-
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
|
+
};
|
|
40440
40614
|
const displaySeverity = (severity) => {
|
|
40441
40615
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40442
40616
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40453,7 +40627,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40453
40627
|
typosquat: "possible typosquat"
|
|
40454
40628
|
};
|
|
40455
40629
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40456
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40630
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40457
40631
|
const summarizeAlertNote = (note) => {
|
|
40458
40632
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40459
40633
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40548,7 +40722,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40548
40722
|
* outage.
|
|
40549
40723
|
*/
|
|
40550
40724
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40551
|
-
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"))) }));
|
|
40552
40729
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40553
40730
|
};
|
|
40554
40731
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40610,7 +40787,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40610
40787
|
* diagnostics).
|
|
40611
40788
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
40612
40789
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
40613
|
-
* 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.
|
|
40614
40794
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40615
40795
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40616
40796
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -40630,7 +40810,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40630
40810
|
* order, so terminal output is identical either way; supply-chain
|
|
40631
40811
|
* rides alongside without a spinner.
|
|
40632
40812
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40633
|
-
* 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
|
|
40634
40814
|
* byte-identical regardless of which fiber settled first. The
|
|
40635
40815
|
* viewer-permission fiber is joined later, during score-metadata
|
|
40636
40816
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -40691,12 +40871,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40691
40871
|
...checkPnpmHardening(scanDirectory),
|
|
40692
40872
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40693
40873
|
...checkExpoProject(scanDirectory, project),
|
|
40694
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40695
|
-
...checkSecurityScan(scanDirectory, {
|
|
40696
|
-
project,
|
|
40697
|
-
ignoredTags: input.ignoredTags
|
|
40698
|
-
})
|
|
40874
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40699
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")));
|
|
40700
40881
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40701
40882
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40702
40883
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40734,7 +40915,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40734
40915
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40735
40916
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40736
40917
|
rootDirectory: scanDirectory,
|
|
40737
|
-
userConfig: resolvedConfig.config,
|
|
40738
40918
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40739
40919
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40740
40920
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40832,9 +41012,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40832
41012
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40833
41013
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40834
41014
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
41015
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40835
41016
|
yield* reporterService.finalize;
|
|
40836
41017
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40837
41018
|
...envCollected,
|
|
41019
|
+
...securityScanCollected,
|
|
40838
41020
|
...supplyChainCollected,
|
|
40839
41021
|
...lintCollected,
|
|
40840
41022
|
...deadCodeCollected
|
|
@@ -40860,6 +41042,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40860
41042
|
metadata: scoreMetadata
|
|
40861
41043
|
});
|
|
40862
41044
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41045
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40863
41046
|
return {
|
|
40864
41047
|
project,
|
|
40865
41048
|
userConfig: resolvedConfig.config,
|
|
@@ -40880,8 +41063,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40880
41063
|
scanElapsedMilliseconds,
|
|
40881
41064
|
scanConcurrency,
|
|
40882
41065
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41066
|
+
securityScanFailed,
|
|
40883
41067
|
lintCacheHitFileCount,
|
|
40884
|
-
lintCacheTotalFileCount
|
|
41068
|
+
lintCacheTotalFileCount,
|
|
41069
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40885
41070
|
};
|
|
40886
41071
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40887
41072
|
"inspect.directory": input.directory,
|
|
@@ -42906,6 +43091,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
|
|
|
42906
43091
|
const METRIC = {
|
|
42907
43092
|
cliInvoked: "cli.invoked",
|
|
42908
43093
|
cliError: "cli.error",
|
|
43094
|
+
cliEnvironmentError: "cli.env_error",
|
|
42909
43095
|
projectDetected: "project.detected",
|
|
42910
43096
|
projectPathSelected: "project.path_selected",
|
|
42911
43097
|
projectConfigSelected: "project.config_selected",
|
|
@@ -42918,6 +43104,8 @@ const METRIC = {
|
|
|
42918
43104
|
scanCheckSkipped: "scan.check_skipped",
|
|
42919
43105
|
baselineDegraded: "baseline.degraded",
|
|
42920
43106
|
ruleFired: "rule.fired",
|
|
43107
|
+
ruleDisabled: "rule.disabled",
|
|
43108
|
+
ruleSuppressed: "rule.suppressed",
|
|
42921
43109
|
lintFailed: "lint.failed",
|
|
42922
43110
|
deadCodeFailed: "deadcode.failed",
|
|
42923
43111
|
scoreUnavailable: "score.unavailable",
|
|
@@ -42930,12 +43118,17 @@ const METRIC = {
|
|
|
42930
43118
|
installWorkflow: "install.workflow",
|
|
42931
43119
|
installAgentHooks: "install.agent_hooks",
|
|
42932
43120
|
installDependency: "install.dependency",
|
|
43121
|
+
ciScaffolded: "ci.scaffolded",
|
|
43122
|
+
ciUpgraded: "ci.upgraded",
|
|
43123
|
+
ciConfigured: "ci.configured",
|
|
42933
43124
|
rulesChanged: "rules.changed",
|
|
42934
43125
|
rulesQueried: "rules.queried",
|
|
42935
43126
|
lspSessionStarted: "lsp.session.started",
|
|
42936
43127
|
lspScanCompleted: "lsp.scan.completed",
|
|
42937
43128
|
lspScanDuration: "lsp.scan.duration",
|
|
42938
|
-
lspScanDiagnostics: "lsp.scan.diagnostics"
|
|
43129
|
+
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43130
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43131
|
+
jsonOutUsed: "json.out_used"
|
|
42939
43132
|
};
|
|
42940
43133
|
//#endregion
|
|
42941
43134
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43252,5 +43445,5 @@ const startLanguageServer = () => {
|
|
|
43252
43445
|
};
|
|
43253
43446
|
//#endregion
|
|
43254
43447
|
export { startLanguageServer };
|
|
43255
|
-
!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]="
|
|
43256
|
-
//# 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
|