react-doctor 0.5.8-dev.ea00b1b → 0.5.8-dev.f028d8b
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 +9398 -1495
- package/dist/index.d.ts +11 -2
- package/dist/index.js +279 -80
- package/dist/lsp.js +224 -81
- package/package.json +10 -8
package/dist/lsp.js
CHANGED
|
@@ -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
|
/**
|
|
@@ -33811,6 +33811,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
|
33811
33811
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33812
33812
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33813
33813
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33814
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33814
33815
|
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33815
33816
|
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33816
33817
|
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
@@ -35053,6 +35054,7 @@ const collectStringSet = (values) => {
|
|
|
35053
35054
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35054
35055
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35055
35056
|
};
|
|
35057
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35056
35058
|
/**
|
|
35057
35059
|
* Pre-compiles every stateful filter and returns a single
|
|
35058
35060
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35086,11 +35088,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35086
35088
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35087
35089
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35088
35090
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35091
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35089
35092
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35090
35093
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35094
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35091
35095
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35092
35096
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35093
35097
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
35098
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
35099
|
+
const suppress = (diagnostic, source) => {
|
|
35100
|
+
const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
|
|
35101
|
+
const suppressionKey = `${ruleKey}\u0000${source}`;
|
|
35102
|
+
const existing = suppressions.get(suppressionKey);
|
|
35103
|
+
suppressions.set(suppressionKey, existing ? {
|
|
35104
|
+
...existing,
|
|
35105
|
+
count: existing.count + 1
|
|
35106
|
+
} : {
|
|
35107
|
+
rule: ruleKey,
|
|
35108
|
+
source,
|
|
35109
|
+
count: 1
|
|
35110
|
+
});
|
|
35111
|
+
return null;
|
|
35112
|
+
};
|
|
35094
35113
|
const isLibraryFile = (filePath) => {
|
|
35095
35114
|
let cached = libraryFileCache.get(filePath);
|
|
35096
35115
|
if (cached === void 0) {
|
|
@@ -35139,50 +35158,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35139
35158
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35140
35159
|
return false;
|
|
35141
35160
|
};
|
|
35142
|
-
|
|
35143
|
-
if (
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
if (
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35164
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35165
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35166
|
-
}
|
|
35167
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35168
|
-
const lines = getFileLines(current.filePath);
|
|
35169
|
-
if (lines) {
|
|
35170
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35171
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35172
|
-
if (evaluation.isSuppressed) return null;
|
|
35173
|
-
if (evaluation.nearMissHint) current = {
|
|
35174
|
-
...current,
|
|
35175
|
-
suppressionHint: evaluation.nearMissHint
|
|
35176
|
-
};
|
|
35161
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35162
|
+
if (!hasRuntimeGlobals) return false;
|
|
35163
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35164
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35165
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35166
|
+
};
|
|
35167
|
+
return {
|
|
35168
|
+
apply: (diagnostic) => {
|
|
35169
|
+
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35170
|
+
let current = diagnostic;
|
|
35171
|
+
let explicitSeverityOverride;
|
|
35172
|
+
let explicitRuleOverride;
|
|
35173
|
+
if (severityControls) {
|
|
35174
|
+
const { ruleKey, category } = getDiagnosticRuleIdentity(current);
|
|
35175
|
+
explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
|
|
35176
|
+
explicitSeverityOverride = resolveRuleSeverityOverride({
|
|
35177
|
+
ruleKey,
|
|
35178
|
+
category
|
|
35179
|
+
}, severityControls);
|
|
35180
|
+
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
35181
|
+
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
35177
35182
|
}
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35183
|
+
if (explicitRuleOverride === void 0) {
|
|
35184
|
+
if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
|
|
35185
|
+
}
|
|
35186
|
+
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
35187
|
+
if (userConfig) {
|
|
35188
|
+
if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
|
|
35189
|
+
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35190
|
+
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
35191
|
+
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35192
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35193
|
+
}
|
|
35194
|
+
if (respectInlineDisables && current.line > 0) {
|
|
35195
|
+
const lines = getFileLines(current.filePath);
|
|
35196
|
+
if (lines) {
|
|
35197
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35198
|
+
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35199
|
+
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35200
|
+
if (evaluation.nearMissHint) current = {
|
|
35201
|
+
...current,
|
|
35202
|
+
suppressionHint: evaluation.nearMissHint
|
|
35203
|
+
};
|
|
35204
|
+
}
|
|
35205
|
+
}
|
|
35206
|
+
const fileContext = getFileContext(current.filePath);
|
|
35207
|
+
if (fileContext !== "production") current = {
|
|
35208
|
+
...current,
|
|
35209
|
+
fileContext
|
|
35210
|
+
};
|
|
35211
|
+
return current;
|
|
35212
|
+
},
|
|
35213
|
+
summarizeSuppressions: () => [...suppressions.values()]
|
|
35214
|
+
};
|
|
35186
35215
|
};
|
|
35187
35216
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35188
35217
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35402,7 +35431,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35402
35431
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35403
35432
|
return Math.min(Math.floor(requested), 32);
|
|
35404
35433
|
};
|
|
35405
|
-
const readSystemFacts = () => ({
|
|
35434
|
+
const readSystemFacts$1 = () => ({
|
|
35406
35435
|
availableCores: os.availableParallelism(),
|
|
35407
35436
|
totalMemoryBytes: os.totalmem(),
|
|
35408
35437
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -35423,7 +35452,7 @@ const readSystemFacts = () => ({
|
|
|
35423
35452
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35424
35453
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35425
35454
|
*/
|
|
35426
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35455
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35427
35456
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35428
35457
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35429
35458
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -35618,6 +35647,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35618
35647
|
"projects",
|
|
35619
35648
|
"textComponents",
|
|
35620
35649
|
"rawTextWrapperComponents",
|
|
35650
|
+
"runtimeGlobals",
|
|
35621
35651
|
"serverAuthFunctionNames"
|
|
35622
35652
|
];
|
|
35623
35653
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -36908,7 +36938,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36908
36938
|
}
|
|
36909
36939
|
return true;
|
|
36910
36940
|
};
|
|
36911
|
-
const
|
|
36941
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
36942
|
+
setImmediate(resolve);
|
|
36943
|
+
});
|
|
36944
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36912
36945
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36913
36946
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36914
36947
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36923,7 +36956,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36923
36956
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36924
36957
|
}];
|
|
36925
36958
|
});
|
|
36926
|
-
if (enabledScanRules.length === 0) return
|
|
36959
|
+
if (enabledScanRules.length === 0) return null;
|
|
36927
36960
|
const diagnostics = [];
|
|
36928
36961
|
const seen = /* @__PURE__ */ new Set();
|
|
36929
36962
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36935,15 +36968,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36935
36968
|
}
|
|
36936
36969
|
return status === true;
|
|
36937
36970
|
};
|
|
36938
|
-
|
|
36939
|
-
|
|
36940
|
-
|
|
36941
|
-
|
|
36942
|
-
|
|
36943
|
-
|
|
36944
|
-
|
|
36971
|
+
const scanFile = (file) => {
|
|
36972
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
36973
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
36974
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
36975
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
36976
|
+
if (seen.has(key)) continue;
|
|
36977
|
+
seen.add(key);
|
|
36978
|
+
diagnostics.push(diagnostic);
|
|
36979
|
+
}
|
|
36980
|
+
};
|
|
36981
|
+
return {
|
|
36982
|
+
scanFile,
|
|
36983
|
+
diagnostics
|
|
36984
|
+
};
|
|
36985
|
+
};
|
|
36986
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
36987
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
36988
|
+
if (session === null) return [];
|
|
36989
|
+
let filesSinceYield = 0;
|
|
36990
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
36991
|
+
session.scanFile(file);
|
|
36992
|
+
filesSinceYield += 1;
|
|
36993
|
+
if (filesSinceYield >= 16) {
|
|
36994
|
+
filesSinceYield = 0;
|
|
36995
|
+
await yieldToEventLoop();
|
|
36996
|
+
}
|
|
36945
36997
|
}
|
|
36946
|
-
return diagnostics;
|
|
36998
|
+
return session.diagnostics;
|
|
36947
36999
|
};
|
|
36948
37000
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36949
37001
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -37160,6 +37212,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37160
37212
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37161
37213
|
};
|
|
37162
37214
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37215
|
+
const readSystemFacts = () => ({
|
|
37216
|
+
availableCores: os.availableParallelism(),
|
|
37217
|
+
totalMemoryBytes: os.totalmem(),
|
|
37218
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
37219
|
+
});
|
|
37220
|
+
/**
|
|
37221
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
37222
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
37223
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
37224
|
+
* workers that fit in available memory, floored at 1.
|
|
37225
|
+
*
|
|
37226
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
37227
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
37228
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
37229
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
37230
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
37231
|
+
* cap the per-project spawn path lacked.
|
|
37232
|
+
*
|
|
37233
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
37234
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
37235
|
+
*/
|
|
37236
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
37237
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
37238
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
37239
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
37240
|
+
};
|
|
37241
|
+
let availableSlots = -1;
|
|
37242
|
+
const waiters = [];
|
|
37243
|
+
const releaseSlot = () => {
|
|
37244
|
+
const nextWaiter = waiters.shift();
|
|
37245
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
37246
|
+
else availableSlots += 1;
|
|
37247
|
+
};
|
|
37248
|
+
/**
|
|
37249
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
37250
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
37251
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
37252
|
+
* queue and run as slots free.
|
|
37253
|
+
*
|
|
37254
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
37255
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
37256
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
37257
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
37258
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
37259
|
+
*/
|
|
37260
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
37261
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
37262
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
37263
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
37264
|
+
else await new Promise((resolve, reject) => {
|
|
37265
|
+
const waiter = () => {
|
|
37266
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37267
|
+
resolve();
|
|
37268
|
+
};
|
|
37269
|
+
const onAbort = () => {
|
|
37270
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
37271
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
37272
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
37273
|
+
};
|
|
37274
|
+
waiters.push(waiter);
|
|
37275
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37276
|
+
});
|
|
37277
|
+
try {
|
|
37278
|
+
return await task();
|
|
37279
|
+
} finally {
|
|
37280
|
+
releaseSlot();
|
|
37281
|
+
}
|
|
37282
|
+
};
|
|
37163
37283
|
/**
|
|
37164
37284
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37165
37285
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37184,6 +37304,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37184
37304
|
};
|
|
37185
37305
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37186
37306
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37307
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37308
|
+
"react-doctor",
|
|
37309
|
+
"eslint-plugin-react-doctor",
|
|
37310
|
+
"oxlint-plugin-react-doctor"
|
|
37311
|
+
]);
|
|
37187
37312
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37188
37313
|
const inputChunks = [];
|
|
37189
37314
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37450,14 +37575,17 @@ const checkDeadCode = async (options) => {
|
|
|
37450
37575
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37451
37576
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37452
37577
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37453
|
-
const
|
|
37454
|
-
|
|
37455
|
-
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37459
|
-
|
|
37460
|
-
|
|
37578
|
+
const spawnAndRun = () => {
|
|
37579
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37580
|
+
rootDirectory,
|
|
37581
|
+
entryPatterns,
|
|
37582
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37583
|
+
ignorePatterns,
|
|
37584
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37585
|
+
parseConcurrency: options.parseConcurrency
|
|
37586
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37587
|
+
};
|
|
37588
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37461
37589
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37462
37590
|
const diagnostics = [];
|
|
37463
37591
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37486,6 +37614,7 @@ const checkDeadCode = async (options) => {
|
|
|
37486
37614
|
});
|
|
37487
37615
|
}
|
|
37488
37616
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37617
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37489
37618
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37490
37619
|
diagnostics.push({
|
|
37491
37620
|
filePath: "package.json",
|
|
@@ -39219,11 +39348,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39219
39348
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39220
39349
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39221
39350
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
39351
|
+
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`.";
|
|
39222
39352
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39223
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39353
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39224
39354
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39225
|
-
if (!normalizedSummary) return
|
|
39226
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39355
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39356
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39227
39357
|
};
|
|
39228
39358
|
const PLUGIN_CATEGORY_MAP = {
|
|
39229
39359
|
react: "Bugs",
|
|
@@ -39272,7 +39402,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39272
39402
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39273
39403
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39274
39404
|
return {
|
|
39275
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39405
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39276
39406
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39277
39407
|
};
|
|
39278
39408
|
}
|
|
@@ -40621,7 +40751,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40621
40751
|
* diagnostics).
|
|
40622
40752
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
40623
40753
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
40624
|
-
* expo/react-native
|
|
40754
|
+
* expo/react-native), collected synchronously. The heavier
|
|
40755
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
40756
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
40757
|
+
* rather than blocking the event loop before it.
|
|
40625
40758
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40626
40759
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40627
40760
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -40641,7 +40774,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40641
40774
|
* order, so terminal output is identical either way; supply-chain
|
|
40642
40775
|
* rides alongside without a spinner.
|
|
40643
40776
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40644
|
-
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40777
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
40645
40778
|
* byte-identical regardless of which fiber settled first. The
|
|
40646
40779
|
* viewer-permission fiber is joined later, during score-metadata
|
|
40647
40780
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -40702,12 +40835,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40702
40835
|
...checkPnpmHardening(scanDirectory),
|
|
40703
40836
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40704
40837
|
...checkExpoProject(scanDirectory, project),
|
|
40705
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40706
|
-
...checkSecurityScan(scanDirectory, {
|
|
40707
|
-
project,
|
|
40708
|
-
ignoredTags: input.ignoredTags
|
|
40709
|
-
})
|
|
40838
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40710
40839
|
])));
|
|
40840
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40841
|
+
project,
|
|
40842
|
+
ignoredTags: input.ignoredTags
|
|
40843
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40711
40844
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40712
40845
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40713
40846
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40843,9 +40976,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40843
40976
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40844
40977
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40845
40978
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
40979
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40846
40980
|
yield* reporterService.finalize;
|
|
40847
40981
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40848
40982
|
...envCollected,
|
|
40983
|
+
...securityScanCollected,
|
|
40849
40984
|
...supplyChainCollected,
|
|
40850
40985
|
...lintCollected,
|
|
40851
40986
|
...deadCodeCollected
|
|
@@ -40892,7 +41027,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40892
41027
|
scanConcurrency,
|
|
40893
41028
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
40894
41029
|
lintCacheHitFileCount,
|
|
40895
|
-
lintCacheTotalFileCount
|
|
41030
|
+
lintCacheTotalFileCount,
|
|
41031
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40896
41032
|
};
|
|
40897
41033
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40898
41034
|
"inspect.directory": input.directory,
|
|
@@ -42930,6 +43066,8 @@ const METRIC = {
|
|
|
42930
43066
|
scanCheckSkipped: "scan.check_skipped",
|
|
42931
43067
|
baselineDegraded: "baseline.degraded",
|
|
42932
43068
|
ruleFired: "rule.fired",
|
|
43069
|
+
ruleDisabled: "rule.disabled",
|
|
43070
|
+
ruleSuppressed: "rule.suppressed",
|
|
42933
43071
|
lintFailed: "lint.failed",
|
|
42934
43072
|
deadCodeFailed: "deadcode.failed",
|
|
42935
43073
|
scoreUnavailable: "score.unavailable",
|
|
@@ -42942,12 +43080,17 @@ const METRIC = {
|
|
|
42942
43080
|
installWorkflow: "install.workflow",
|
|
42943
43081
|
installAgentHooks: "install.agent_hooks",
|
|
42944
43082
|
installDependency: "install.dependency",
|
|
43083
|
+
ciScaffolded: "ci.scaffolded",
|
|
43084
|
+
ciUpgraded: "ci.upgraded",
|
|
43085
|
+
ciConfigured: "ci.configured",
|
|
42945
43086
|
rulesChanged: "rules.changed",
|
|
42946
43087
|
rulesQueried: "rules.queried",
|
|
42947
43088
|
lspSessionStarted: "lsp.session.started",
|
|
42948
43089
|
lspScanCompleted: "lsp.scan.completed",
|
|
42949
43090
|
lspScanDuration: "lsp.scan.duration",
|
|
42950
|
-
lspScanDiagnostics: "lsp.scan.diagnostics"
|
|
43091
|
+
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43092
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43093
|
+
jsonOutUsed: "json.out_used"
|
|
42951
43094
|
};
|
|
42952
43095
|
//#endregion
|
|
42953
43096
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43264,5 +43407,5 @@ const startLanguageServer = () => {
|
|
|
43264
43407
|
};
|
|
43265
43408
|
//#endregion
|
|
43266
43409
|
export { startLanguageServer };
|
|
43267
|
-
!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]="
|
|
43268
|
-
//# debugId=
|
|
43410
|
+
!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]="1822e36c-fbae-57bf-bdb9-98afebb9a03b")}catch(e){}}();
|
|
43411
|
+
//# debugId=1822e36c-fbae-57bf-bdb9-98afebb9a03b
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.5.8-dev.
|
|
3
|
+
"version": "0.5.8-dev.f028d8b",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"bugs": {
|
|
21
21
|
"url": "https://github.com/millionco/react-doctor/issues"
|
|
22
22
|
},
|
|
23
|
-
"license": "
|
|
23
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
24
24
|
"author": "Million Software, Inc",
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"bin/**",
|
|
35
35
|
"dist/**/*.js",
|
|
36
36
|
"dist/**/*.d.ts",
|
|
37
|
-
"dist/skills/**"
|
|
37
|
+
"dist/skills/**",
|
|
38
|
+
"LICENSE"
|
|
38
39
|
],
|
|
39
40
|
"type": "module",
|
|
40
41
|
"sideEffects": false,
|
|
@@ -59,12 +60,13 @@
|
|
|
59
60
|
"magicast": "^0.5.3",
|
|
60
61
|
"oxlint": ">=1.66.0 <1.67.0",
|
|
61
62
|
"prompts": "^2.4.2",
|
|
62
|
-
"typescript": ">=5.0.4 <
|
|
63
|
+
"typescript": ">=5.0.4 <6",
|
|
63
64
|
"vscode-languageserver": "^9.0.1",
|
|
64
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
65
66
|
"vscode-uri": "^3.1.0",
|
|
66
|
-
"
|
|
67
|
-
"oxlint-plugin-react-doctor": "0.5.8-dev.
|
|
67
|
+
"yaml": "^2.9.0",
|
|
68
|
+
"oxlint-plugin-react-doctor": "0.5.8-dev.f028d8b",
|
|
69
|
+
"deslop-js": "0.5.8"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -72,9 +74,9 @@
|
|
|
72
74
|
"@xterm/headless": "^6.0.0",
|
|
73
75
|
"commander": "^14.0.3",
|
|
74
76
|
"ora": "^9.4.0",
|
|
77
|
+
"@react-doctor/core": "0.5.8",
|
|
75
78
|
"@react-doctor/api": "0.5.8",
|
|
76
|
-
"@react-doctor/language-server": "0.5.8"
|
|
77
|
-
"@react-doctor/core": "0.5.8"
|
|
79
|
+
"@react-doctor/language-server": "0.5.8"
|
|
78
80
|
},
|
|
79
81
|
"engines": {
|
|
80
82
|
"node": "^20.19.0 || >=22.13.0"
|