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/index.d.ts
CHANGED
|
@@ -9271,6 +9271,15 @@ interface ReactDoctorConfig {
|
|
|
9271
9271
|
* suppresses regardless of sibling content.
|
|
9272
9272
|
*/
|
|
9273
9273
|
rawTextWrapperComponents?: string[];
|
|
9274
|
+
/**
|
|
9275
|
+
* Capitalized identifiers that exist at runtime but aren't imported in
|
|
9276
|
+
* the file that uses them — react-live's `<LiveProvider scope={...}>`,
|
|
9277
|
+
* Storybook globals, MDX live blocks, or an ambient `declare global` in a
|
|
9278
|
+
* separate `.d.ts`. The single-file `jsx-no-undef` rule can't see those
|
|
9279
|
+
* bindings and would report `<DatePicker />` as an undefined JSX component.
|
|
9280
|
+
* List the injected names here and `jsx-no-undef` treats them as known.
|
|
9281
|
+
*/
|
|
9282
|
+
runtimeGlobals?: string[];
|
|
9274
9283
|
/**
|
|
9275
9284
|
* Project-level allowlist of function names that the
|
|
9276
9285
|
* `server-auth-actions` rule treats as an auth check at the top of
|
|
@@ -9726,7 +9735,7 @@ interface InspectResult {
|
|
|
9726
9735
|
* Per-file lint cache outcome: files served from cache, and total files
|
|
9727
9736
|
* considered. Both absent when the cache was off or bypassed (audit mode,
|
|
9728
9737
|
* adopted `extends`, user plugins). The CLI projects these onto the Sentry
|
|
9729
|
-
* wide event as `
|
|
9738
|
+
* wide event as `lint.cacheHitRatio`.
|
|
9730
9739
|
*/
|
|
9731
9740
|
lintCacheHitFileCount?: number | null;
|
|
9732
9741
|
lintCacheTotalFileCount?: number | null;
|
|
@@ -10123,7 +10132,7 @@ declare const summarizeDiagnostics: (diagnostics: Diagnostic[], worstScore?: num
|
|
|
10123
10132
|
* untouched so the loader sees the same plain object either way.
|
|
10124
10133
|
*/
|
|
10125
10134
|
declare const defineConfig: (config: ReactDoctorConfig) => ReactDoctorConfig; //#endregion
|
|
10126
|
-
//#region src/utils/
|
|
10135
|
+
//#region src/utils/detect-ai-training-environment.d.ts
|
|
10127
10136
|
//#endregion
|
|
10128
10137
|
//#region ../api/dist/index.d.ts
|
|
10129
10138
|
//#region src/diagnose.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="3fb133b5-fc1f-519f-8f76-6f1d3bb556ce")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -5996,7 +5996,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
|
|
|
5996
5996
|
* @since 2.0.0
|
|
5997
5997
|
*/
|
|
5998
5998
|
const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
|
|
5999
|
-
const setImmediate = "setImmediate" in globalThis ? (f) => {
|
|
5999
|
+
const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
|
|
6000
6000
|
const timer = globalThis.setImmediate(f);
|
|
6001
6001
|
return () => globalThis.clearImmediate(timer);
|
|
6002
6002
|
} : (f) => {
|
|
@@ -6040,7 +6040,7 @@ var PriorityBuckets = class {
|
|
|
6040
6040
|
var MixedScheduler = class {
|
|
6041
6041
|
executionMode;
|
|
6042
6042
|
setImmediate;
|
|
6043
|
-
constructor(executionMode = "async", setImmediateFn = setImmediate) {
|
|
6043
|
+
constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
|
|
6044
6044
|
this.executionMode = executionMode;
|
|
6045
6045
|
this.setImmediate = setImmediateFn;
|
|
6046
6046
|
}
|
|
@@ -6065,7 +6065,7 @@ var MixedSchedulerDispatcher = class {
|
|
|
6065
6065
|
tasks = /* @__PURE__ */ new PriorityBuckets();
|
|
6066
6066
|
running = void 0;
|
|
6067
6067
|
setImmediate;
|
|
6068
|
-
constructor(setImmediateFn = setImmediate) {
|
|
6068
|
+
constructor(setImmediateFn = setImmediate$1) {
|
|
6069
6069
|
this.setImmediate = setImmediateFn;
|
|
6070
6070
|
}
|
|
6071
6071
|
/**
|
|
@@ -33752,6 +33752,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
|
33752
33752
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33753
33753
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33754
33754
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33755
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33755
33756
|
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33756
33757
|
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33757
33758
|
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
@@ -34998,6 +34999,7 @@ const collectStringSet = (values) => {
|
|
|
34998
34999
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
34999
35000
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35000
35001
|
};
|
|
35002
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35001
35003
|
/**
|
|
35002
35004
|
* Pre-compiles every stateful filter and returns a single
|
|
35003
35005
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35031,11 +35033,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35031
35033
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35032
35034
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35033
35035
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35036
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35034
35037
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35035
35038
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35039
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35036
35040
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35037
35041
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35038
35042
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
35043
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
35044
|
+
const suppress = (diagnostic, source) => {
|
|
35045
|
+
const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
|
|
35046
|
+
const suppressionKey = `${ruleKey}\u0000${source}`;
|
|
35047
|
+
const existing = suppressions.get(suppressionKey);
|
|
35048
|
+
suppressions.set(suppressionKey, existing ? {
|
|
35049
|
+
...existing,
|
|
35050
|
+
count: existing.count + 1
|
|
35051
|
+
} : {
|
|
35052
|
+
rule: ruleKey,
|
|
35053
|
+
source,
|
|
35054
|
+
count: 1
|
|
35055
|
+
});
|
|
35056
|
+
return null;
|
|
35057
|
+
};
|
|
35039
35058
|
const isLibraryFile = (filePath) => {
|
|
35040
35059
|
let cached = libraryFileCache.get(filePath);
|
|
35041
35060
|
if (cached === void 0) {
|
|
@@ -35084,50 +35103,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35084
35103
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35085
35104
|
return false;
|
|
35086
35105
|
};
|
|
35087
|
-
|
|
35088
|
-
if (
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
35097
|
-
|
|
35098
|
-
|
|
35099
|
-
if (
|
|
35100
|
-
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
35105
|
-
|
|
35106
|
-
|
|
35107
|
-
|
|
35108
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35109
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35110
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35111
|
-
}
|
|
35112
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35113
|
-
const lines = getFileLines(current.filePath);
|
|
35114
|
-
if (lines) {
|
|
35115
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35116
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35117
|
-
if (evaluation.isSuppressed) return null;
|
|
35118
|
-
if (evaluation.nearMissHint) current = {
|
|
35119
|
-
...current,
|
|
35120
|
-
suppressionHint: evaluation.nearMissHint
|
|
35121
|
-
};
|
|
35106
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35107
|
+
if (!hasRuntimeGlobals) return false;
|
|
35108
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35109
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35110
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35111
|
+
};
|
|
35112
|
+
return {
|
|
35113
|
+
apply: (diagnostic) => {
|
|
35114
|
+
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35115
|
+
let current = diagnostic;
|
|
35116
|
+
let explicitSeverityOverride;
|
|
35117
|
+
let explicitRuleOverride;
|
|
35118
|
+
if (severityControls) {
|
|
35119
|
+
const { ruleKey, category } = getDiagnosticRuleIdentity(current);
|
|
35120
|
+
explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
|
|
35121
|
+
explicitSeverityOverride = resolveRuleSeverityOverride({
|
|
35122
|
+
ruleKey,
|
|
35123
|
+
category
|
|
35124
|
+
}, severityControls);
|
|
35125
|
+
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
35126
|
+
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
35122
35127
|
}
|
|
35123
|
-
|
|
35124
|
-
|
|
35125
|
-
|
|
35126
|
-
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
35128
|
+
if (explicitRuleOverride === void 0) {
|
|
35129
|
+
if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
|
|
35130
|
+
}
|
|
35131
|
+
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
35132
|
+
if (userConfig) {
|
|
35133
|
+
if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
|
|
35134
|
+
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35135
|
+
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
35136
|
+
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35137
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35138
|
+
}
|
|
35139
|
+
if (respectInlineDisables && current.line > 0) {
|
|
35140
|
+
const lines = getFileLines(current.filePath);
|
|
35141
|
+
if (lines) {
|
|
35142
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35143
|
+
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35144
|
+
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35145
|
+
if (evaluation.nearMissHint) current = {
|
|
35146
|
+
...current,
|
|
35147
|
+
suppressionHint: evaluation.nearMissHint
|
|
35148
|
+
};
|
|
35149
|
+
}
|
|
35150
|
+
}
|
|
35151
|
+
const fileContext = getFileContext(current.filePath);
|
|
35152
|
+
if (fileContext !== "production") current = {
|
|
35153
|
+
...current,
|
|
35154
|
+
fileContext
|
|
35155
|
+
};
|
|
35156
|
+
return current;
|
|
35157
|
+
},
|
|
35158
|
+
summarizeSuppressions: () => [...suppressions.values()]
|
|
35159
|
+
};
|
|
35131
35160
|
};
|
|
35132
35161
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35133
35162
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35369,7 +35398,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35369
35398
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35370
35399
|
return Math.min(Math.floor(requested), 32);
|
|
35371
35400
|
};
|
|
35372
|
-
const readSystemFacts = () => ({
|
|
35401
|
+
const readSystemFacts$1 = () => ({
|
|
35373
35402
|
availableCores: os.availableParallelism(),
|
|
35374
35403
|
totalMemoryBytes: os.totalmem(),
|
|
35375
35404
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -35390,7 +35419,7 @@ const readSystemFacts = () => ({
|
|
|
35390
35419
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35391
35420
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35392
35421
|
*/
|
|
35393
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35422
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35394
35423
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35395
35424
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35396
35425
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -35585,6 +35614,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35585
35614
|
"projects",
|
|
35586
35615
|
"textComponents",
|
|
35587
35616
|
"rawTextWrapperComponents",
|
|
35617
|
+
"runtimeGlobals",
|
|
35588
35618
|
"serverAuthFunctionNames"
|
|
35589
35619
|
];
|
|
35590
35620
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -36923,7 +36953,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36923
36953
|
}
|
|
36924
36954
|
return true;
|
|
36925
36955
|
};
|
|
36926
|
-
const
|
|
36956
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
36957
|
+
setImmediate(resolve);
|
|
36958
|
+
});
|
|
36959
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36927
36960
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36928
36961
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36929
36962
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36938,7 +36971,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36938
36971
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36939
36972
|
}];
|
|
36940
36973
|
});
|
|
36941
|
-
if (enabledScanRules.length === 0) return
|
|
36974
|
+
if (enabledScanRules.length === 0) return null;
|
|
36942
36975
|
const diagnostics = [];
|
|
36943
36976
|
const seen = /* @__PURE__ */ new Set();
|
|
36944
36977
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36950,15 +36983,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36950
36983
|
}
|
|
36951
36984
|
return status === true;
|
|
36952
36985
|
};
|
|
36953
|
-
|
|
36954
|
-
|
|
36955
|
-
|
|
36956
|
-
|
|
36957
|
-
|
|
36958
|
-
|
|
36959
|
-
|
|
36986
|
+
const scanFile = (file) => {
|
|
36987
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
36988
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
36989
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
36990
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
36991
|
+
if (seen.has(key)) continue;
|
|
36992
|
+
seen.add(key);
|
|
36993
|
+
diagnostics.push(diagnostic);
|
|
36994
|
+
}
|
|
36995
|
+
};
|
|
36996
|
+
return {
|
|
36997
|
+
scanFile,
|
|
36998
|
+
diagnostics
|
|
36999
|
+
};
|
|
37000
|
+
};
|
|
37001
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
37002
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
37003
|
+
if (session === null) return [];
|
|
37004
|
+
let filesSinceYield = 0;
|
|
37005
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37006
|
+
session.scanFile(file);
|
|
37007
|
+
filesSinceYield += 1;
|
|
37008
|
+
if (filesSinceYield >= 16) {
|
|
37009
|
+
filesSinceYield = 0;
|
|
37010
|
+
await yieldToEventLoop();
|
|
37011
|
+
}
|
|
36960
37012
|
}
|
|
36961
|
-
return diagnostics;
|
|
37013
|
+
return session.diagnostics;
|
|
36962
37014
|
};
|
|
36963
37015
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36964
37016
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -37175,6 +37227,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37175
37227
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37176
37228
|
};
|
|
37177
37229
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37230
|
+
const readSystemFacts = () => ({
|
|
37231
|
+
availableCores: os.availableParallelism(),
|
|
37232
|
+
totalMemoryBytes: os.totalmem(),
|
|
37233
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
37234
|
+
});
|
|
37235
|
+
/**
|
|
37236
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
37237
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
37238
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
37239
|
+
* workers that fit in available memory, floored at 1.
|
|
37240
|
+
*
|
|
37241
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
37242
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
37243
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
37244
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
37245
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
37246
|
+
* cap the per-project spawn path lacked.
|
|
37247
|
+
*
|
|
37248
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
37249
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
37250
|
+
*/
|
|
37251
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
37252
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
37253
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
37254
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
37255
|
+
};
|
|
37256
|
+
let availableSlots = -1;
|
|
37257
|
+
const waiters = [];
|
|
37258
|
+
const releaseSlot = () => {
|
|
37259
|
+
const nextWaiter = waiters.shift();
|
|
37260
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
37261
|
+
else availableSlots += 1;
|
|
37262
|
+
};
|
|
37263
|
+
/**
|
|
37264
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
37265
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
37266
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
37267
|
+
* queue and run as slots free.
|
|
37268
|
+
*
|
|
37269
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
37270
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
37271
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
37272
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
37273
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
37274
|
+
*/
|
|
37275
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
37276
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
37277
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
37278
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
37279
|
+
else await new Promise((resolve, reject) => {
|
|
37280
|
+
const waiter = () => {
|
|
37281
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37282
|
+
resolve();
|
|
37283
|
+
};
|
|
37284
|
+
const onAbort = () => {
|
|
37285
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
37286
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
37287
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
37288
|
+
};
|
|
37289
|
+
waiters.push(waiter);
|
|
37290
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37291
|
+
});
|
|
37292
|
+
try {
|
|
37293
|
+
return await task();
|
|
37294
|
+
} finally {
|
|
37295
|
+
releaseSlot();
|
|
37296
|
+
}
|
|
37297
|
+
};
|
|
37178
37298
|
/**
|
|
37179
37299
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37180
37300
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37199,6 +37319,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37199
37319
|
};
|
|
37200
37320
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37201
37321
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37322
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37323
|
+
"react-doctor",
|
|
37324
|
+
"eslint-plugin-react-doctor",
|
|
37325
|
+
"oxlint-plugin-react-doctor"
|
|
37326
|
+
]);
|
|
37202
37327
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37203
37328
|
const inputChunks = [];
|
|
37204
37329
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37465,14 +37590,17 @@ const checkDeadCode = async (options) => {
|
|
|
37465
37590
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37466
37591
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37467
37592
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37468
|
-
const
|
|
37469
|
-
|
|
37470
|
-
|
|
37471
|
-
|
|
37472
|
-
|
|
37473
|
-
|
|
37474
|
-
|
|
37475
|
-
|
|
37593
|
+
const spawnAndRun = () => {
|
|
37594
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37595
|
+
rootDirectory,
|
|
37596
|
+
entryPatterns,
|
|
37597
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37598
|
+
ignorePatterns,
|
|
37599
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37600
|
+
parseConcurrency: options.parseConcurrency
|
|
37601
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37602
|
+
};
|
|
37603
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37476
37604
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37477
37605
|
const diagnostics = [];
|
|
37478
37606
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37501,6 +37629,7 @@ const checkDeadCode = async (options) => {
|
|
|
37501
37629
|
});
|
|
37502
37630
|
}
|
|
37503
37631
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37632
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37504
37633
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37505
37634
|
diagnostics.push({
|
|
37506
37635
|
filePath: "package.json",
|
|
@@ -39234,11 +39363,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39234
39363
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39235
39364
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39236
39365
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
39366
|
+
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`.";
|
|
39237
39367
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39238
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39368
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39239
39369
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39240
|
-
if (!normalizedSummary) return
|
|
39241
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39370
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39371
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39242
39372
|
};
|
|
39243
39373
|
const PLUGIN_CATEGORY_MAP = {
|
|
39244
39374
|
react: "Bugs",
|
|
@@ -39287,7 +39417,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39287
39417
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39288
39418
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39289
39419
|
return {
|
|
39290
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39420
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39291
39421
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39292
39422
|
};
|
|
39293
39423
|
}
|
|
@@ -40636,7 +40766,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40636
40766
|
* diagnostics).
|
|
40637
40767
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
40638
40768
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
40639
|
-
* expo/react-native
|
|
40769
|
+
* expo/react-native), collected synchronously. The heavier
|
|
40770
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
40771
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
40772
|
+
* rather than blocking the event loop before it.
|
|
40640
40773
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40641
40774
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40642
40775
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -40656,7 +40789,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40656
40789
|
* order, so terminal output is identical either way; supply-chain
|
|
40657
40790
|
* rides alongside without a spinner.
|
|
40658
40791
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40659
|
-
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40792
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
40660
40793
|
* byte-identical regardless of which fiber settled first. The
|
|
40661
40794
|
* viewer-permission fiber is joined later, during score-metadata
|
|
40662
40795
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -40717,12 +40850,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40717
40850
|
...checkPnpmHardening(scanDirectory),
|
|
40718
40851
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40719
40852
|
...checkExpoProject(scanDirectory, project),
|
|
40720
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40721
|
-
...checkSecurityScan(scanDirectory, {
|
|
40722
|
-
project,
|
|
40723
|
-
ignoredTags: input.ignoredTags
|
|
40724
|
-
})
|
|
40853
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40725
40854
|
])));
|
|
40855
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40856
|
+
project,
|
|
40857
|
+
ignoredTags: input.ignoredTags
|
|
40858
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40726
40859
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40727
40860
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40728
40861
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40858,9 +40991,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40858
40991
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40859
40992
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40860
40993
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
40994
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40861
40995
|
yield* reporterService.finalize;
|
|
40862
40996
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40863
40997
|
...envCollected,
|
|
40998
|
+
...securityScanCollected,
|
|
40864
40999
|
...supplyChainCollected,
|
|
40865
41000
|
...lintCollected,
|
|
40866
41001
|
...deadCodeCollected
|
|
@@ -40907,7 +41042,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40907
41042
|
scanConcurrency,
|
|
40908
41043
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
40909
41044
|
lintCacheHitFileCount,
|
|
40910
|
-
lintCacheTotalFileCount
|
|
41045
|
+
lintCacheTotalFileCount,
|
|
41046
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40911
41047
|
};
|
|
40912
41048
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40913
41049
|
"inspect.directory": input.directory,
|
|
@@ -41312,6 +41448,61 @@ const clearAutoSuppressionCaches = () => {};
|
|
|
41312
41448
|
* untouched so the loader sees the same plain object either way.
|
|
41313
41449
|
*/
|
|
41314
41450
|
const defineConfig = (config) => config;
|
|
41451
|
+
const AI_TRAINING_ENV_VARS = [
|
|
41452
|
+
["HF_DATASETS_CACHE", "huggingface"],
|
|
41453
|
+
["HF_HOME", "huggingface"],
|
|
41454
|
+
["HUGGINGFACE_HUB_CACHE", "huggingface"],
|
|
41455
|
+
["SPACE_ID", "huggingface-spaces"],
|
|
41456
|
+
["CUDA_VISIBLE_DEVICES", "cuda"],
|
|
41457
|
+
["NVIDIA_VISIBLE_DEVICES", "nvidia"],
|
|
41458
|
+
["ROCR_VISIBLE_DEVICES", "rocm"],
|
|
41459
|
+
["TPU_NAME", "google-tpu"],
|
|
41460
|
+
["WANDB_RUN_ID", "wandb"],
|
|
41461
|
+
["WANDB_SWEEP_ID", "wandb"],
|
|
41462
|
+
["MLFLOW_RUN_ID", "mlflow"],
|
|
41463
|
+
["MLFLOW_TRACKING_URI", "mlflow"],
|
|
41464
|
+
["COMET_EXPERIMENT_KEY", "comet"],
|
|
41465
|
+
["NEPTUNE_RUN_ID", "neptune"],
|
|
41466
|
+
["CLEARML_TASK_ID", "clearml"],
|
|
41467
|
+
["DVC_STAGE", "dvc"],
|
|
41468
|
+
["RAY_WORKER_PROCESS", "ray"],
|
|
41469
|
+
["RAY_ADDRESS", "ray"],
|
|
41470
|
+
["MUJOCO_GL", "mujoco"],
|
|
41471
|
+
["MUJOCO_PATH", "mujoco"],
|
|
41472
|
+
["GYM_DISABLE_ENV_CHECKER", "gymnasium"],
|
|
41473
|
+
["SM_TRAINING_ENV", "sagemaker"],
|
|
41474
|
+
["TRAINING_JOB_ARN", "sagemaker"],
|
|
41475
|
+
["SAGEMAKER_BASE_DIR", "sagemaker"],
|
|
41476
|
+
["AZUREML_RUN_ID", "azure-ml"],
|
|
41477
|
+
["AZURE_ML_MODEL_DIR", "azure-ml"],
|
|
41478
|
+
["CLOUD_ML_PROJECT_ID", "vertex-ai"],
|
|
41479
|
+
["VERTEX_AI_LOG_LEVEL", "vertex-ai"],
|
|
41480
|
+
["DET_MASTER", "determined-ai"],
|
|
41481
|
+
["LIGHTNING_USER_ID", "lightning-ai"],
|
|
41482
|
+
["FLYTE_INTERNAL_EXECUTION_ID", "flyte"],
|
|
41483
|
+
["ARGO_WORKFLOW_NAME", "argo-workflows"],
|
|
41484
|
+
["KFP_POD_NAME", "kubeflow-pipelines"],
|
|
41485
|
+
["KAGGLE_KERNEL_RUN_TYPE", "kaggle"],
|
|
41486
|
+
["COLAB_BACKEND_VERSION", "google-colab"],
|
|
41487
|
+
["DATABRICKS_RUNTIME_VERSION", "databricks"],
|
|
41488
|
+
["DAYTONA_WS_ID", "daytona"],
|
|
41489
|
+
["DAYTONA_WS_NAME", "daytona"],
|
|
41490
|
+
["E2B_SANDBOX_ID", "e2b"],
|
|
41491
|
+
["MODAL_FUNCTION_ID", "modal"],
|
|
41492
|
+
["MODAL_TASK_ID", "modal"],
|
|
41493
|
+
["RUNPOD_POD_ID", "runpod"],
|
|
41494
|
+
["REPLICATE_USERNAME", "replicate"],
|
|
41495
|
+
["VAST_CONTAINERLABEL", "vast-ai"],
|
|
41496
|
+
["HARBOR_URL", "harbor"],
|
|
41497
|
+
["HARBOR_HOSTNAME", "harbor"],
|
|
41498
|
+
["SWE_BENCH_TASK", "swe-bench"],
|
|
41499
|
+
["SWEBENCH_TASK", "swe-bench"],
|
|
41500
|
+
["SWE_AGENT_MODEL", "swe-agent"]
|
|
41501
|
+
];
|
|
41502
|
+
const detectAiTrainingEnvironment = () => {
|
|
41503
|
+
for (const [envVar, label] of AI_TRAINING_ENV_VARS) if (process.env[envVar]) return label;
|
|
41504
|
+
return null;
|
|
41505
|
+
};
|
|
41315
41506
|
const unionValues = (baseValues, overrideValues) => [...new Set([...baseValues, ...overrideValues])];
|
|
41316
41507
|
const mergeIgnores = (baseIgnore, overrideIgnore) => {
|
|
41317
41508
|
const mergedIgnore = {
|
|
@@ -41356,6 +41547,12 @@ const mergeReactDoctorConfigs = (baseConfig, overrideConfig) => {
|
|
|
41356
41547
|
};
|
|
41357
41548
|
//#endregion
|
|
41358
41549
|
//#region ../api/dist/index.js
|
|
41550
|
+
let didWarnAiTraining = false;
|
|
41551
|
+
const warnIfAiTrainingEnvironment = () => {
|
|
41552
|
+
if (didWarnAiTraining || detectAiTrainingEnvironment() === null) return;
|
|
41553
|
+
didWarnAiTraining = true;
|
|
41554
|
+
console.warn("[react-doctor] Use in an AI or ML pipeline requires written permission under the react-doctor license. Contact founders@million.dev to request access.");
|
|
41555
|
+
};
|
|
41359
41556
|
const buildDiagnoseLayer = (config, configOverrideTarget) => {
|
|
41360
41557
|
const configLayer = configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
|
|
41361
41558
|
config,
|
|
@@ -41393,6 +41590,7 @@ const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
|
|
|
41393
41590
|
};
|
|
41394
41591
|
};
|
|
41395
41592
|
const diagnoseDirectory = async (directory, options) => {
|
|
41593
|
+
warnIfAiTrainingEnvironment();
|
|
41396
41594
|
const startTime = globalThis.performance.now();
|
|
41397
41595
|
const scanTarget = await resolveScanTarget(directory);
|
|
41398
41596
|
return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(buildInspectProgram(scanTarget, options).pipe(provide(buildDiagnoseLayer(scanTarget.userConfig)), provide(layerOtlp)))), globalThis.performance.now() - startTime);
|
|
@@ -41439,6 +41637,7 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
|
|
|
41439
41637
|
}
|
|
41440
41638
|
};
|
|
41441
41639
|
const diagnoseProjectBatch = async (input) => {
|
|
41640
|
+
warnIfAiTrainingEnvironment();
|
|
41442
41641
|
const startTime = globalThis.performance.now();
|
|
41443
41642
|
const { projects, concurrency, config: batchConfig, ...baseOptions } = input;
|
|
41444
41643
|
const projectResults = await mapWithConcurrency(projects, concurrency ?? 4, (projectDefinition) => diagnoseProject(projectDefinition, baseOptions, batchConfig));
|
|
@@ -41483,4 +41682,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41483
41682
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41484
41683
|
|
|
41485
41684
|
//# sourceMappingURL=index.js.map
|
|
41486
|
-
//# debugId=
|
|
41685
|
+
//# debugId=3fb133b5-fc1f-519f-8f76-6f1d3bb556ce
|