react-doctor 0.5.8-dev.bd0f465 → 0.5.8-dev.c2af308

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/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
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]="9807b1bf-1e62-5421-ad35-d1f4129c1538")}catch(e){}}();
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]="83529d8f-2ca3-50ba-a3b9-44622f913549")}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";
@@ -7,7 +7,7 @@ import fs from "node:fs";
7
7
  import * as Path from "node:path";
8
8
  import path from "node:path";
9
9
  import * as NodeChildProcess from "node:child_process";
10
- import { spawn, spawnSync } from "node:child_process";
10
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
11
11
  import * as ts from "typescript";
12
12
  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";
13
13
  import * as OS from "node:os";
@@ -33695,6 +33695,9 @@ const SCORE_API_URL = "https://www.react.doctor/api/score";
33695
33695
  const FETCH_TIMEOUT_MS = 1e4;
33696
33696
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33697
33697
  const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33698
+ const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
33699
+ const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
33700
+ const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
33698
33701
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33699
33702
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33700
33703
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -34519,7 +34522,7 @@ const tokenizeRuleList = (ruleList) => {
34519
34522
  const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34520
34523
  const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34521
34524
  const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34522
- 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}\` — change it to \`${ruleId}\`.`;
34525
+ 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}\`).`;
34523
34526
  const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
34524
34527
  const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34525
34528
  const candidates = [{
@@ -34999,6 +35002,7 @@ const collectStringSet = (values) => {
34999
35002
  if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
35000
35003
  return new Set(values.filter((value) => typeof value === "string"));
35001
35004
  };
35005
+ const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
35002
35006
  /**
35003
35007
  * Pre-compiles every stateful filter and returns a single
35004
35008
  * `apply(diagnostic)` closure that runs (in order):
@@ -35032,11 +35036,28 @@ const buildDiagnosticPipeline = (input) => {
35032
35036
  const compiledOverrides = compileIgnoreOverrides(userConfig);
35033
35037
  const textComponentNames = collectStringSet(userConfig?.textComponents);
35034
35038
  const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
35039
+ const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
35035
35040
  const hasTextComponents = textComponentNames.size > 0;
35036
35041
  const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
35042
+ const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
35037
35043
  const fileLinesCache = /* @__PURE__ */ new Map();
35038
35044
  const fileContextCache = /* @__PURE__ */ new Map();
35039
35045
  const libraryFileCache = /* @__PURE__ */ new Map();
35046
+ const suppressions = /* @__PURE__ */ new Map();
35047
+ const suppress = (diagnostic, source) => {
35048
+ const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
35049
+ const suppressionKey = `${ruleKey}\u0000${source}`;
35050
+ const existing = suppressions.get(suppressionKey);
35051
+ suppressions.set(suppressionKey, existing ? {
35052
+ ...existing,
35053
+ count: existing.count + 1
35054
+ } : {
35055
+ rule: ruleKey,
35056
+ source,
35057
+ count: 1
35058
+ });
35059
+ return null;
35060
+ };
35040
35061
  const isLibraryFile = (filePath) => {
35041
35062
  let cached = libraryFileCache.get(filePath);
35042
35063
  if (cached === void 0) {
@@ -35085,50 +35106,60 @@ const buildDiagnosticPipeline = (input) => {
35085
35106
  if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
35086
35107
  return false;
35087
35108
  };
35088
- return { apply: (diagnostic) => {
35089
- if (shouldAutoSuppress(diagnostic)) return null;
35090
- let current = diagnostic;
35091
- let explicitSeverityOverride;
35092
- let explicitRuleOverride;
35093
- if (severityControls) {
35094
- const { ruleKey, category } = getDiagnosticRuleIdentity(current);
35095
- explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
35096
- explicitSeverityOverride = resolveRuleSeverityOverride({
35097
- ruleKey,
35098
- category
35099
- }, severityControls);
35100
- if (explicitSeverityOverride === "off") return null;
35101
- if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
35102
- }
35103
- if (explicitRuleOverride === void 0) {
35104
- if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
35105
- }
35106
- if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
35107
- if (userConfig) {
35108
- if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return null;
35109
- if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35110
- if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
35111
- if (isRnRawTextSuppressedByConfig(current)) return null;
35112
- }
35113
- if (respectInlineDisables && current.line > 0) {
35114
- const lines = getFileLines(current.filePath);
35115
- if (lines) {
35116
- const ruleIdentifier = `${current.plugin}/${current.rule}`;
35117
- const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35118
- if (evaluation.isSuppressed) return null;
35119
- if (evaluation.nearMissHint) current = {
35120
- ...current,
35121
- suppressionHint: evaluation.nearMissHint
35122
- };
35109
+ const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
35110
+ if (!hasRuntimeGlobals) return false;
35111
+ if (diagnostic.rule !== "jsx-no-undef") return false;
35112
+ const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
35113
+ return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
35114
+ };
35115
+ return {
35116
+ apply: (diagnostic) => {
35117
+ if (shouldAutoSuppress(diagnostic)) return null;
35118
+ let current = diagnostic;
35119
+ let explicitSeverityOverride;
35120
+ let explicitRuleOverride;
35121
+ if (severityControls) {
35122
+ const { ruleKey, category } = getDiagnosticRuleIdentity(current);
35123
+ explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
35124
+ explicitSeverityOverride = resolveRuleSeverityOverride({
35125
+ ruleKey,
35126
+ category
35127
+ }, severityControls);
35128
+ if (explicitSeverityOverride === "off") return suppress(current, "config");
35129
+ if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
35123
35130
  }
35124
- }
35125
- const fileContext = getFileContext(current.filePath);
35126
- if (fileContext !== "production") current = {
35127
- ...current,
35128
- fileContext
35129
- };
35130
- return current;
35131
- } };
35131
+ if (explicitRuleOverride === void 0) {
35132
+ if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
35133
+ }
35134
+ if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
35135
+ if (userConfig) {
35136
+ if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
35137
+ if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35138
+ if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
35139
+ if (isRnRawTextSuppressedByConfig(current)) return null;
35140
+ if (isJsxNoUndefSuppressedByConfig(current)) return null;
35141
+ }
35142
+ if (respectInlineDisables && current.line > 0) {
35143
+ const lines = getFileLines(current.filePath);
35144
+ if (lines) {
35145
+ const ruleIdentifier = `${current.plugin}/${current.rule}`;
35146
+ const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35147
+ if (evaluation.isSuppressed) return suppress(current, "inline");
35148
+ if (evaluation.nearMissHint) current = {
35149
+ ...current,
35150
+ suppressionHint: evaluation.nearMissHint
35151
+ };
35152
+ }
35153
+ }
35154
+ const fileContext = getFileContext(current.filePath);
35155
+ if (fileContext !== "production") current = {
35156
+ ...current,
35157
+ fileContext
35158
+ };
35159
+ return current;
35160
+ },
35161
+ summarizeSuppressions: () => [...suppressions.values()]
35162
+ };
35132
35163
  };
35133
35164
  const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
35134
35165
  var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
@@ -35432,13 +35463,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
35432
35463
  * without recompiling react-doctor. Tests override via
35433
35464
  * `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
35434
35465
  */
35435
- var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
35436
- const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
35437
- if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
35438
- const parsed = Number(raw);
35439
- if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
35440
- return parsed;
35441
- } }) {};
35466
+ var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
35442
35467
  /**
35443
35468
  * Hard cap on combined stdout+stderr bytes per oxlint batch. The
35444
35469
  * subprocess gets SIGKILL'd if it produces more; the recovery path
@@ -35586,6 +35611,7 @@ const STRING_ARRAY_FIELD_NAMES = [
35586
35611
  "projects",
35587
35612
  "textComponents",
35588
35613
  "rawTextWrapperComponents",
35614
+ "runtimeGlobals",
35589
35615
  "serverAuthFunctionNames"
35590
35616
  ];
35591
35617
  const SURFACE_CONTROL_FIELD_NAMES = [
@@ -37290,6 +37316,11 @@ const toCanonicalPath = (filePath) => {
37290
37316
  };
37291
37317
  const DEAD_CODE_PLUGIN = "deslop";
37292
37318
  const DEAD_CODE_CATEGORY = "Maintainability";
37319
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
37320
+ "react-doctor",
37321
+ "eslint-plugin-react-doctor",
37322
+ "oxlint-plugin-react-doctor"
37323
+ ]);
37293
37324
  const DEAD_CODE_WORKER_SCRIPT = `
37294
37325
  const inputChunks = [];
37295
37326
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -37595,6 +37626,7 @@ const checkDeadCode = async (options) => {
37595
37626
  });
37596
37627
  }
37597
37628
  for (const unusedDependency of result.unusedDependencies) {
37629
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
37598
37630
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
37599
37631
  diagnostics.push({
37600
37632
  filePath: "package.json",
@@ -37773,7 +37805,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37773
37805
  return yield* tryPromise({
37774
37806
  try: (signal) => checkDeadCode({
37775
37807
  rootDirectory: input.rootDirectory,
37776
- userConfig: input.userConfig,
37777
37808
  parseConcurrency: input.parseConcurrency,
37778
37809
  workerTimeoutMs: input.workerTimeoutMs,
37779
37810
  abortSignal: signal
@@ -37896,6 +37927,11 @@ const isSafeGitRevision = (candidate) => {
37896
37927
  if (candidate.includes("..") || candidate.includes("@{")) return false;
37897
37928
  return SAFE_GIT_REVISION_PATTERN.test(candidate);
37898
37929
  };
37930
+ const resolveSpawnArgsLengthCap = () => {
37931
+ if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
37932
+ if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
37933
+ return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
37934
+ };
37899
37935
  /**
37900
37936
  * Splits a git revision range into its endpoints: three-dot `A...B`
37901
37937
  * (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
@@ -37980,7 +38016,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
37980
38016
  return scoped(gen(function* () {
37981
38017
  if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37982
38018
  const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37983
- if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
38019
+ const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
38020
+ if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
37984
38021
  const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37985
38022
  cwd: input.directory,
37986
38023
  env: input.env,
@@ -38149,6 +38186,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38149
38186
  if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
38150
38187
  const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
38151
38188
  if (baseBranch === null) return null;
38189
+ if (!isSafeGitRevision(baseBranch)) return null;
38152
38190
  if (explicitBaseBranch !== void 0) {
38153
38191
  if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
38154
38192
  }
@@ -38499,7 +38537,7 @@ const normalizeConfigForHash = (config) => {
38499
38537
  if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
38500
38538
  return clone;
38501
38539
  };
38502
- 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(" ").update(input.tsconfigContent ?? "").digest("hex");
38540
+ 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");
38503
38541
  /**
38504
38542
  * Loads a plugin module via the local require resolver and extracts
38505
38543
  * `(name, ruleNames)` from either `module.exports.meta + rules` or
@@ -38777,24 +38815,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38777
38815
  updatedAtMs: Date.now(),
38778
38816
  files: Object.fromEntries(cappedEntries)
38779
38817
  };
38780
- const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
38781
- const prunedRulesets = {};
38782
- for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
38783
38818
  atomicWriteJson(cacheFilePath, {
38784
38819
  version: 1,
38785
- rulesets: prunedRulesets
38820
+ rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
38786
38821
  });
38787
38822
  }
38788
38823
  };
38789
38824
  };
38790
38825
  const bundledRequire = createRequire(import.meta.url);
38826
+ const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
38827
+ const resolveChildNodeVersion = (nodeBinaryPath) => {
38828
+ if (nodeBinaryPath === process.execPath) return process.version;
38829
+ const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
38830
+ if (cachedVersion !== void 0) return cachedVersion;
38831
+ let version;
38832
+ try {
38833
+ version = execFileSync(nodeBinaryPath, ["--version"], {
38834
+ encoding: "utf8",
38835
+ timeout: NODE_VERSION_PROBE_TIMEOUT_MS
38836
+ }).trim();
38837
+ } catch {
38838
+ version = `unknown:${nodeBinaryPath}`;
38839
+ }
38840
+ childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
38841
+ return version;
38842
+ };
38791
38843
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
38792
38844
  "oxlint/package.json",
38793
38845
  "oxlint-plugin-react-doctor/package.json",
38794
38846
  "eslint-plugin-react-hooks/package.json"
38795
38847
  ];
38796
- const resolveOxlintToolchainVersions = () => {
38797
- const versions = [`node=${process.version}`];
38848
+ const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
38849
+ const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
38798
38850
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
38799
38851
  const packageJson = bundledRequire(specifier);
38800
38852
  const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
@@ -39328,11 +39380,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
39328
39380
  const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
39329
39381
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
39330
39382
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
39383
+ 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`.";
39331
39384
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
39332
- const buildReactCompilerMessage = (reasonSummary) => {
39385
+ const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
39333
39386
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
39334
- if (!normalizedSummary) return REACT_COMPILER_GENERIC_MESSAGE;
39335
- return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${REACT_COMPILER_ACTION}`;
39387
+ if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
39388
+ return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
39336
39389
  };
39337
39390
  const PLUGIN_CATEGORY_MAP = {
39338
39391
  react: "Bugs",
@@ -39381,7 +39434,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
39381
39434
  const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
39382
39435
  const reasonDetail = reasonDetailLines.join("\n").trim();
39383
39436
  return {
39384
- message: buildReactCompilerMessage(reasonSummary.trim()),
39437
+ message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
39385
39438
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
39386
39439
  };
39387
39440
  }
@@ -39649,21 +39702,27 @@ const spawnLintBatches = async (input) => {
39649
39702
  const allDiagnostics = [];
39650
39703
  const droppedFiles = [];
39651
39704
  let firstDropReason = null;
39652
- const splitDeadlineMs = Date.now() + splitTotalBudgetMs;
39653
- const spawnLintBatch = async (batch, depth) => {
39705
+ const spawnLintBatch = async (batch, depth, splitBudget) => {
39654
39706
  const batchArgs = [...baseArgs, ...batch];
39655
39707
  try {
39656
39708
  return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
39657
39709
  } catch (error) {
39658
39710
  if (!isSplittableReactDoctorError(error)) throw error;
39659
- const splitBudgetExhausted = Date.now() >= splitDeadlineMs || depth >= splitMaxDepth;
39660
- if (batch.length <= 1 || splitBudgetExhausted) {
39711
+ splitBudget.deadlineMs ??= Date.now() + splitTotalBudgetMs;
39712
+ const isBudgetElapsed = Date.now() >= splitBudget.deadlineMs;
39713
+ const isDepthCapReached = depth >= splitMaxDepth;
39714
+ if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
39661
39715
  droppedFiles.push(...batch);
39662
- if (firstDropReason === null) firstDropReason = splitBudgetExhausted && batch.length > 1 ? `${error.message} (split budget exhausted after ${splitMaxDepth} levels / ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s)` : error.message;
39716
+ if (firstDropReason === null) {
39717
+ let limitHint = "";
39718
+ if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
39719
+ else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
39720
+ firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
39721
+ }
39663
39722
  return [];
39664
39723
  }
39665
39724
  const splitIndex = Math.ceil(batch.length / 2);
39666
- return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
39725
+ return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, splitBudget), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, splitBudget)];
39667
39726
  }
39668
39727
  };
39669
39728
  let startedFileCount = 0;
@@ -39680,7 +39739,7 @@ const spawnLintBatches = async (input) => {
39680
39739
  try {
39681
39740
  const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
39682
39741
  startedFileCount += batch.length;
39683
- const batchDiagnostics = await spawnLintBatch(batch, 0);
39742
+ const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
39684
39743
  scannedFileCount += batch.length;
39685
39744
  if (onFileProgress) {
39686
39745
  displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
@@ -39754,7 +39813,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
39754
39813
  if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39755
39814
  const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39756
39815
  if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39757
- return Path.join(os.tmpdir(), "react-doctor-cache", projectCacheSubdir(projectDirectory));
39816
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39817
+ };
39818
+ const resolveUserCacheScope = () => {
39819
+ try {
39820
+ return String(process.getuid?.() ?? os.userInfo().username);
39821
+ } catch {
39822
+ return "shared";
39823
+ }
39758
39824
  };
39759
39825
  const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
39760
39826
  /**
@@ -39922,7 +39988,7 @@ const runOxlint = async (options) => {
39922
39988
  extendsPaths: [],
39923
39989
  ruleSelection: "cacheable"
39924
39990
  }),
39925
- toolchainVersions: resolveOxlintToolchainVersions(),
39991
+ toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
39926
39992
  ignorePatterns: combinedPatterns,
39927
39993
  tsconfigContent
39928
39994
  });
@@ -39936,7 +40002,7 @@ const runOxlint = async (options) => {
39936
40002
  missFiles.push(candidateFile);
39937
40003
  continue;
39938
40004
  }
39939
- const cacheKey = `${candidateFile.replaceAll("\\", "/")}${contentHash}`;
40005
+ const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
39940
40006
  cacheKeyByFile.set(candidateFile, cacheKey);
39941
40007
  const cachedDiagnostics = cache.lookup(cacheKey);
39942
40008
  if (cachedDiagnostics === null) missFiles.push(candidateFile);
@@ -40252,7 +40318,7 @@ const ScoreApiResponseSchema = Struct({
40252
40318
  })))
40253
40319
  });
40254
40320
  const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
40255
- const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
40321
+ const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
40256
40322
  const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
40257
40323
  const describeFailure = (error) => {
40258
40324
  if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
@@ -40556,7 +40622,10 @@ const ALERT_SEVERITY_RANK = {
40556
40622
  medium: 2,
40557
40623
  low: 1
40558
40624
  };
40559
- const severityRank = (severity) => ALERT_SEVERITY_RANK[severity.toLowerCase()] ?? 0;
40625
+ const severityRank = (severity) => {
40626
+ const normalized = severity.toLowerCase();
40627
+ return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
40628
+ };
40560
40629
  const displaySeverity = (severity) => {
40561
40630
  const normalized = sanitizeTerminalText(severity.toLowerCase());
40562
40631
  return normalized === "middle" ? "medium" : normalized;
@@ -40573,7 +40642,7 @@ const ALERT_TYPE_LABELS = {
40573
40642
  typosquat: "possible typosquat"
40574
40643
  };
40575
40644
  const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
40576
- const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type] ?? sanitizeTerminalText(humanizeAlertType(type));
40645
+ const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
40577
40646
  const summarizeAlertNote = (note) => {
40578
40647
  const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
40579
40648
  const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
@@ -40668,7 +40737,10 @@ const checkSupplyChain = (input) => gen(function* () {
40668
40737
  * outage.
40669
40738
  */
40670
40739
  var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
40671
- static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(checkSupplyChain(input).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
40740
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
40741
+ ...input,
40742
+ totalTimeoutMs
40743
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
40672
40744
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
40673
40745
  };
40674
40746
  const getObjectProperty = (value, propertyName) => {
@@ -40816,10 +40888,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40816
40888
  ...checkExpoProject(scanDirectory, project),
40817
40889
  ...checkReactNativeProject(scanDirectory, project)
40818
40890
  ])));
40819
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
40891
+ const securityScanFailedRef = yield* make$13(false);
40892
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
40820
40893
  project,
40821
40894
  ignoredTags: input.ignoredTags
40822
- })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
40895
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
40823
40896
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
40824
40897
  const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
40825
40898
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
@@ -40857,7 +40930,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40857
40930
  const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
40858
40931
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
40859
40932
  rootDirectory: scanDirectory,
40860
- userConfig: resolvedConfig.config,
40861
40933
  parseConcurrency: deadCodeParseConcurrency,
40862
40934
  workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
40863
40935
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
@@ -40985,6 +41057,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40985
41057
  metadata: scoreMetadata
40986
41058
  });
40987
41059
  const lintPartialFailures = yield* get$2(partialFailuresRef);
41060
+ const securityScanFailed = yield* get$2(securityScanFailedRef);
40988
41061
  return {
40989
41062
  project,
40990
41063
  userConfig: resolvedConfig.config,
@@ -41005,8 +41078,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41005
41078
  scanElapsedMilliseconds,
41006
41079
  scanConcurrency,
41007
41080
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
41081
+ securityScanFailed,
41008
41082
  lintCacheHitFileCount,
41009
- lintCacheTotalFileCount
41083
+ lintCacheTotalFileCount,
41084
+ suppressedRuleCounts: transform.summarizeSuppressions()
41010
41085
  };
41011
41086
  }).pipe(withSpan("runInspect", { attributes: {
41012
41087
  "inspect.directory": input.directory,
@@ -41645,4 +41720,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41645
41720
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41646
41721
 
41647
41722
  //# sourceMappingURL=index.js.map
41648
- //# debugId=9807b1bf-1e62-5421-ad35-d1f4129c1538
41723
+ //# debugId=83529d8f-2ca3-50ba-a3b9-44622f913549