react-doctor 0.9.3 → 0.9.4-dev.25dbf6d

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.js CHANGED
@@ -1,16 +1,16 @@
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]="0c9da9c7-4847-5807-983e-a57b7eec97cd")}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]="6d2e43de-ae97-5213-b1a7-a9905ce4b1d5")}catch(e){}}();
3
3
  import { i as __toESM$1, r as __require, t as __commonJSMin$1 } from "./chunk-ChKA3wi6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_DOCTOR_RULE_REGISTRY, REACT_DOCTOR_SCAN_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor/core";
6
6
  import * as Crypto from "node:crypto";
7
7
  import crypto, { createHash } from "node:crypto";
8
- import * as NFS from "node:fs";
9
- import fs from "node:fs";
10
8
  import * as Path from "node:path";
11
9
  import path from "node:path";
10
+ import * as NFS from "node:fs";
11
+ import fs from "node:fs";
12
12
  import * as NodeChildProcess from "node:child_process";
13
- import { execFileSync, spawn, spawnSync } from "node:child_process";
13
+ import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
14
14
  import { ResolverFactory } from "oxc-resolver";
15
15
  import ts from "typescript";
16
16
  import * as NodeUrl from "node:url";
@@ -3621,7 +3621,7 @@ const flatMap$5 = /* @__PURE__ */ dual(2, (self, f) => isNone(self) ? none() : f
3621
3621
  * @category filtering
3622
3622
  * @since 2.0.0
3623
3623
  */
3624
- const filter = /* @__PURE__ */ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some(self.value) : none());
3624
+ const filter$1 = /* @__PURE__ */ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some(self.value) : none());
3625
3625
  //#endregion
3626
3626
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Result.js
3627
3627
  /**
@@ -15556,7 +15556,7 @@ function transformOptional(f) {
15556
15556
  */
15557
15557
  function withDefault$1(defaultValue) {
15558
15558
  return new Getter((o) => {
15559
- const filtered = filter(o, isNotUndefined);
15559
+ const filtered = filter$1(o, isNotUndefined);
15560
15560
  return isSome(filtered) ? succeed$2(filtered) : mapEager(defaultValue, some);
15561
15561
  });
15562
15562
  }
@@ -19404,6 +19404,10 @@ var JsonReportProjectEntryV3 = class extends Class("JsonReportProjectEntryV3")({
19404
19404
  scannedFileCount: optional(Number$1),
19405
19405
  elapsedMilliseconds: Number$1
19406
19406
  }) {};
19407
+ var JsonReportSkippedProject = class extends Class("JsonReportSkippedProject")({
19408
+ directory: String$1,
19409
+ reason: Literal("max-duration")
19410
+ }) {};
19407
19411
  /** Original full, diff, and staged report contract. */
19408
19412
  var JsonReportV1 = class extends Class("JsonReportV1")({
19409
19413
  schemaVersion: Literal(1),
@@ -19432,6 +19436,7 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
19432
19436
  reactDetected: optional(Boolean$1),
19433
19437
  diff: NullOr(JsonReportDiffInfo),
19434
19438
  projects: ArraySchema(JsonReportProjectEntry),
19439
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19435
19440
  diagnostics: ArraySchema(Diagnostic),
19436
19441
  summary: JsonReportSummary,
19437
19442
  elapsedMilliseconds: Number$1,
@@ -19460,6 +19465,7 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
19460
19465
  diff: NullOr(JsonReportDiffInfo),
19461
19466
  baseline: JsonReportBaseline,
19462
19467
  projects: ArraySchema(JsonReportProjectEntry),
19468
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19463
19469
  diagnostics: ArraySchema(Diagnostic),
19464
19470
  summary: JsonReportSummary,
19465
19471
  elapsedMilliseconds: Number$1,
@@ -19476,6 +19482,7 @@ var JsonReportV3 = class extends Class("JsonReportV3")({
19476
19482
  diff: NullOr(JsonReportDiffInfo),
19477
19483
  baseline: optional(JsonReportBaseline),
19478
19484
  projects: ArraySchema(JsonReportProjectEntryV3),
19485
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19479
19486
  diagnostics: ArraySchema(JsonReportDiagnosticV3),
19480
19487
  summary: JsonReportSummary,
19481
19488
  elapsedMilliseconds: Number$1,
@@ -53458,6 +53465,47 @@ const flattenArray = (self) => transformPull$1(self, (pull) => {
53458
53465
  }));
53459
53466
  });
53460
53467
  /**
53468
+ * Filters arrays of elements emitted by a channel, applying the filter
53469
+ * to each element within the arrays and only emitting non-empty filtered arrays.
53470
+ *
53471
+ * **Example** (Filtering array output)
53472
+ *
53473
+ * ```ts
53474
+ * import { Array, Channel } from "effect"
53475
+ *
53476
+ * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty
53477
+ *
53478
+ * // Create a channel that outputs arrays of mixed data
53479
+ * const arrayChannel = Channel.fromIterable([
53480
+ * Array.make(1, 2, 3, 4, 5),
53481
+ * Array.make(6, 7, 8, 9, 10),
53482
+ * Array.make(11, 12, 13, 14, 15)
53483
+ * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
53484
+ *
53485
+ * // Filter arrays to keep only even numbers
53486
+ * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0)
53487
+ * // Outputs: [2, 4], [6, 8, 10], [12, 14]
53488
+ * // Note: Only non-empty filtered arrays are emitted
53489
+ *
53490
+ * // Arrays that would become empty after filtering are discarded entirely
53491
+ * const oddChannel = Channel.fromIterable([
53492
+ * Array.make(1, 3, 5),
53493
+ * Array.make(2, 4),
53494
+ * Array.make(7, 9)
53495
+ * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
53496
+ * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0)
53497
+ * // Outputs: [2, 4] (the arrays [1,3,5] and [7,9] are discarded)
53498
+ * ```
53499
+ *
53500
+ * @category filtering
53501
+ * @since 4.0.0
53502
+ */
53503
+ const filterArray = /* @__PURE__ */ dual(2, (self, predicate) => transformPull$1(self, (pull) => succeed$2(flatMap$2(pull, function loop(arr) {
53504
+ const passes = [];
53505
+ for (let i = 0; i < arr.length; i++) if (predicate(arr[i])) passes.push(arr[i]);
53506
+ return isReadonlyArrayNonEmpty(passes) ? succeed$2(passes) : flatMap$2(pull, loop);
53507
+ }))));
53508
+ /**
53461
53509
  * Filters and maps each element inside emitted non-empty arrays using a
53462
53510
  * `Filter`.
53463
53511
  *
@@ -55095,6 +55143,30 @@ const concat = /* @__PURE__ */ dual(2, (self, that) => flatten(fromArray([self,
55095
55143
  */
55096
55144
  const merge$1 = /* @__PURE__ */ dual((args) => isStream(args[0]) && isStream(args[1]), (self, that, options) => fromChannel(merge$2(toChannel(self), toChannel(that), options)));
55097
55145
  /**
55146
+ * Filters a stream to the elements that satisfy a predicate.
55147
+ *
55148
+ * **Example** (Filtering stream values)
55149
+ *
55150
+ * ```ts
55151
+ * import { Console, Effect, Stream } from "effect"
55152
+ *
55153
+ * const program = Effect.gen(function*() {
55154
+ * const stream = Stream.make(1, 2, 3, 4).pipe(
55155
+ * Stream.filter((n) => n % 2 === 0)
55156
+ * )
55157
+ * const values = yield* Stream.runCollect(stream)
55158
+ * yield* Console.log(values)
55159
+ * })
55160
+ *
55161
+ * Effect.runPromise(program)
55162
+ * // Output: [ 2, 4 ]
55163
+ * ```
55164
+ *
55165
+ * @category filtering
55166
+ * @since 2.0.0
55167
+ */
55168
+ const filter = /* @__PURE__ */ dual(2, (self, predicate) => fromChannel(filterArray(toChannel(self), predicate)));
55169
+ /**
55098
55170
  * Filters and maps stream elements in one pass using a `Filter`.
55099
55171
  *
55100
55172
  * @category filtering
@@ -61878,6 +61950,7 @@ const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
61878
61950
  const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
61879
61951
  const MINIFIED_SNIFF_BYTES = 65536;
61880
61952
  const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
61953
+ const GIT_CHECK_ATTR_MAX_BUFFER_BYTES = GIT_LS_FILES_MAX_BUFFER_BYTES;
61881
61954
  const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
61882
61955
  const IGNORED_DIRECTORIES = new Set([
61883
61956
  ".angular",
@@ -62163,13 +62236,9 @@ const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
62163
62236
  const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
62164
62237
  const ABORT_EXIT_CODES = new Set([134, 3221226505]);
62165
62238
  const OXLINT_OOM_RESCUE_BUDGET_MS = 6e4;
62166
- const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
62167
- const LINT_PHASE_TIMEOUT_MS = 3e5;
62168
- const SCAN_TOTAL_DEADLINE_MS = 9e5;
62169
62239
  const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
62170
62240
  const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
62171
62241
  const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
62172
- const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
62173
62242
  const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
62174
62243
  const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
62175
62244
  const REACT_SERVER_DOM_PACKAGES = [
@@ -62216,7 +62285,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
62216
62285
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
62217
62286
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
62218
62287
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
62219
- const CORE_PACKAGE_VERSION = "0.9.3";
62288
+ const CORE_PACKAGE_VERSION = "0.9.4";
62220
62289
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
62221
62290
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
62222
62291
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -62229,17 +62298,20 @@ const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
62229
62298
  const isErrnoException = (error) => error instanceof Error && "code" in error;
62230
62299
  const IGNORABLE_READDIR_ERROR_CODES = new Set([
62231
62300
  "EACCES",
62301
+ "EBUSY",
62232
62302
  "EPERM",
62233
62303
  "ENOENT",
62234
62304
  "ENOTDIR",
62235
62305
  "EINVAL",
62236
62306
  "ELOOP",
62237
- "ENAMETOOLONG"
62307
+ "ENAMETOOLONG",
62308
+ "ETIMEDOUT",
62309
+ "UNKNOWN"
62238
62310
  ]);
62239
62311
  const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
62240
62312
  const readDirectoryEntries = (directoryPath) => {
62241
62313
  try {
62242
- return NFS.readdirSync(directoryPath, { withFileTypes: true });
62314
+ return fs.readdirSync(directoryPath, { withFileTypes: true });
62243
62315
  } catch (error) {
62244
62316
  if (isIgnorableReaddirError(error)) return [];
62245
62317
  throw error;
@@ -62247,14 +62319,14 @@ const readDirectoryEntries = (directoryPath) => {
62247
62319
  };
62248
62320
  const isFile = (filePath) => {
62249
62321
  try {
62250
- return NFS.statSync(filePath).isFile();
62322
+ return fs.statSync(filePath).isFile();
62251
62323
  } catch {
62252
62324
  return false;
62253
62325
  }
62254
62326
  };
62255
62327
  const isDirectory = (directoryPath) => {
62256
62328
  try {
62257
- return NFS.statSync(directoryPath).isDirectory();
62329
+ return fs.statSync(directoryPath).isDirectory();
62258
62330
  } catch {
62259
62331
  return false;
62260
62332
  }
@@ -62264,6 +62336,66 @@ const isPlainObject = (value) => {
62264
62336
  const prototype = Object.getPrototypeOf(value);
62265
62337
  return prototype === null || prototype === Object.prototype;
62266
62338
  };
62339
+ const LINGUIST_ATTRIBUTES = ["linguist-generated", "linguist-vendored"];
62340
+ const DISABLED_ATTRIBUTE_VALUES = new Set([
62341
+ "unspecified",
62342
+ "unset",
62343
+ "false",
62344
+ "0",
62345
+ "off",
62346
+ "no"
62347
+ ]);
62348
+ const buildInput = (relativePaths) => `${relativePaths.join("\0")}\0`;
62349
+ const parseIgnoredPaths = (output) => {
62350
+ const ignoredPaths = /* @__PURE__ */ new Set();
62351
+ const fields = output.split("\0");
62352
+ for (let fieldIndex = 0; fieldIndex + 2 < fields.length; fieldIndex += 3) {
62353
+ const filePath = fields[fieldIndex];
62354
+ const value = fields[fieldIndex + 2]?.toLowerCase();
62355
+ if (filePath && value && !DISABLED_ATTRIBUTE_VALUES.has(value)) ignoredPaths.add(filePath);
62356
+ }
62357
+ return ignoredPaths;
62358
+ };
62359
+ const collectGitLinguistIgnoredPaths = (rootDirectory, relativePaths) => {
62360
+ if (relativePaths.length === 0) return /* @__PURE__ */ new Set();
62361
+ const result = spawnSync("git", [
62362
+ "check-attr",
62363
+ "-z",
62364
+ "--stdin",
62365
+ ...LINGUIST_ATTRIBUTES
62366
+ ], {
62367
+ cwd: rootDirectory,
62368
+ encoding: "utf-8",
62369
+ input: buildInput(relativePaths),
62370
+ maxBuffer: GIT_CHECK_ATTR_MAX_BUFFER_BYTES
62371
+ });
62372
+ if (result.error || result.status !== 0) return /* @__PURE__ */ new Set();
62373
+ return parseIgnoredPaths(result.stdout);
62374
+ };
62375
+ const collectGitLinguistIgnoredPathsCooperative = (rootDirectory, relativePaths, signal) => {
62376
+ if (relativePaths.length === 0) return Promise.resolve(/* @__PURE__ */ new Set());
62377
+ return new Promise((resolve, reject) => {
62378
+ signal?.throwIfAborted();
62379
+ execFile("git", [
62380
+ "check-attr",
62381
+ "-z",
62382
+ "--stdin",
62383
+ ...LINGUIST_ATTRIBUTES
62384
+ ], {
62385
+ cwd: rootDirectory,
62386
+ encoding: "utf-8",
62387
+ killSignal: "SIGKILL",
62388
+ maxBuffer: GIT_CHECK_ATTR_MAX_BUFFER_BYTES,
62389
+ signal
62390
+ }, (error, stdout) => {
62391
+ if (signal?.aborted) {
62392
+ reject(signal.reason);
62393
+ return;
62394
+ }
62395
+ resolve(error ? /* @__PURE__ */ new Set() : parseIgnoredPaths(stdout));
62396
+ }).stdin?.end(buildInput(relativePaths));
62397
+ });
62398
+ };
62267
62399
  const lastSourceMappingUrl = (fileContent) => {
62268
62400
  const lastReference = [...fileContent.matchAll(/\/\/# sourceMappingURL=(\S+)/g)].at(-1);
62269
62401
  return lastReference ? lastReference[1] : null;
@@ -62368,6 +62500,9 @@ function* walkSourceTreeFiles(rootDirectory) {
62368
62500
  }
62369
62501
  }
62370
62502
  }
62503
+ const yieldToEventLoop = () => new Promise((resolve) => {
62504
+ setImmediate(resolve);
62505
+ });
62371
62506
  const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
62372
62507
  const entries = [];
62373
62508
  for (const relativePath of relativePaths) {
@@ -62381,23 +62516,55 @@ const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
62381
62516
  }
62382
62517
  return entries;
62383
62518
  };
62384
- const gitLsFiles = (rootDirectory, flags) => {
62519
+ const collectSizedSourceFilesCooperative = async (rootDirectory, relativePaths, signal) => {
62520
+ const entries = [];
62521
+ let sliceStartedAt = performance.now();
62522
+ for (const relativePath of relativePaths) {
62523
+ signal?.throwIfAborted();
62524
+ const absolutePath = Path.resolve(rootDirectory, relativePath);
62525
+ const sizeBytes = statSourceFileSize(absolutePath);
62526
+ if (!isLargeMinifiedFile(absolutePath, sizeBytes)) entries.push({
62527
+ path: relativePath,
62528
+ sizeBytes: sizeBytes ?? 0
62529
+ });
62530
+ if (performance.now() - sliceStartedAt >= 12) {
62531
+ await yieldToEventLoop();
62532
+ sliceStartedAt = performance.now();
62533
+ }
62534
+ }
62535
+ return entries;
62536
+ };
62537
+ const parseGitSourceFilePaths = (output) => {
62538
+ const trackedPaths = /* @__PURE__ */ new Set();
62539
+ const untrackedPaths = [];
62540
+ for (const entry of output.split("\0")) {
62541
+ if (entry.length === 0) continue;
62542
+ const trackedPath = /^[0-7]{6} [0-9a-f]+ [0-3]\t([\s\S]+)$/.exec(entry)?.[1];
62543
+ if (trackedPath === void 0) untrackedPaths.push(entry);
62544
+ else trackedPaths.add(trackedPath);
62545
+ }
62546
+ return {
62547
+ trackedPaths: [...trackedPaths],
62548
+ untrackedPaths
62549
+ };
62550
+ };
62551
+ const listGitSourceFilePaths = (rootDirectory) => {
62385
62552
  const result = spawnSync("git", [
62386
62553
  "ls-files",
62387
62554
  "-z",
62388
- ...flags
62555
+ "--stage",
62556
+ "--others",
62557
+ "--exclude-standard"
62389
62558
  ], {
62390
62559
  cwd: rootDirectory,
62391
62560
  encoding: "utf-8",
62392
62561
  maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
62393
62562
  });
62394
62563
  if (result.error || result.status !== 0) return null;
62395
- return result.stdout.split("\0").filter((filePath) => filePath.length > 0);
62564
+ return parseGitSourceFilePaths(result.stdout);
62396
62565
  };
62397
- const listSourceFilesViaGit = (rootDirectory) => {
62398
- const trackedPaths = gitLsFiles(rootDirectory, ["--cached"]);
62399
- const untrackedPaths = gitLsFiles(rootDirectory, ["--others", "--exclude-standard"]);
62400
- if (trackedPaths === null || untrackedPaths === null) return null;
62566
+ const collectGitCandidateSourceFilePaths = (rootDirectory, paths) => {
62567
+ const { trackedPaths, untrackedPaths } = paths;
62401
62568
  const emitDuplicateJsPaths = collectTypeScriptEmitDuplicateJsPaths({
62402
62569
  trackedPaths: new Set(trackedPaths),
62403
62570
  untrackedPaths,
@@ -62405,12 +62572,65 @@ const listSourceFilesViaGit = (rootDirectory) => {
62405
62572
  });
62406
62573
  return [...trackedPaths, ...untrackedPaths].filter((filePath) => isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath) && !emitDuplicateJsPaths.has(filePath));
62407
62574
  };
62575
+ const filterGitSourceFilePaths = (rootDirectory, paths) => {
62576
+ const candidatePaths = collectGitCandidateSourceFilePaths(rootDirectory, paths);
62577
+ const linguistIgnoredPaths = collectGitLinguistIgnoredPaths(rootDirectory, candidatePaths);
62578
+ return candidatePaths.filter((filePath) => !linguistIgnoredPaths.has(filePath));
62579
+ };
62580
+ const listSourceFilesViaGit = (rootDirectory) => {
62581
+ const paths = listGitSourceFilePaths(rootDirectory);
62582
+ return paths === null ? null : filterGitSourceFilePaths(rootDirectory, paths);
62583
+ };
62584
+ const listSourceFilesViaGitCooperative = async (rootDirectory, signal) => {
62585
+ const paths = await new Promise((resolve, reject) => {
62586
+ signal?.throwIfAborted();
62587
+ execFile("git", [
62588
+ "ls-files",
62589
+ "-z",
62590
+ "--stage",
62591
+ "--others",
62592
+ "--exclude-standard"
62593
+ ], {
62594
+ cwd: rootDirectory,
62595
+ encoding: "utf-8",
62596
+ killSignal: "SIGKILL",
62597
+ maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES,
62598
+ signal
62599
+ }, (error, stdout) => {
62600
+ if (signal?.aborted) {
62601
+ reject(signal.reason);
62602
+ return;
62603
+ }
62604
+ resolve(error ? null : parseGitSourceFilePaths(stdout));
62605
+ });
62606
+ });
62607
+ if (paths === null) return null;
62608
+ const candidatePaths = collectGitCandidateSourceFilePaths(rootDirectory, paths);
62609
+ const linguistIgnoredPaths = await collectGitLinguistIgnoredPathsCooperative(rootDirectory, candidatePaths, signal);
62610
+ return candidatePaths.filter((filePath) => !linguistIgnoredPaths.has(filePath));
62611
+ };
62408
62612
  const listSourceFilesViaFilesystem = (rootDirectory) => {
62409
62613
  const filePaths = [];
62410
62614
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
62411
62615
  return filePaths;
62412
62616
  };
62413
- const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
62617
+ const listSourceFilesViaFilesystemCooperative = async (rootDirectory, signal) => {
62618
+ const filePaths = [];
62619
+ let sliceStartedAt = performance.now();
62620
+ for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
62621
+ signal?.throwIfAborted();
62622
+ if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
62623
+ if (performance.now() - sliceStartedAt >= 12) {
62624
+ await yieldToEventLoop();
62625
+ sliceStartedAt = performance.now();
62626
+ }
62627
+ }
62628
+ return filePaths;
62629
+ };
62630
+ const listSourceFilePaths = (rootDirectory) => (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort();
62631
+ const listSourceFilePathsCooperative = async (rootDirectory, signal) => (await listSourceFilesViaGitCooperative(rootDirectory, signal) ?? await listSourceFilesViaFilesystemCooperative(rootDirectory, signal)).sort();
62632
+ const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilePaths(rootDirectory));
62633
+ const listSourceFilesCooperative = async (rootDirectory, signal) => (await collectSizedSourceFilesCooperative(rootDirectory, await listSourceFilePathsCooperative(rootDirectory, signal), signal)).map((entry) => entry.path);
62414
62634
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
62415
62635
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
62416
62636
  const UTF8_BOM_CHARACTER = "";
@@ -62536,7 +62756,12 @@ const mergeCompilerOptions = (inherited, current) => {
62536
62756
  };
62537
62757
  };
62538
62758
  const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
62539
- const realPath = NFS.realpathSync.native(tsConfigPath);
62759
+ let realPath;
62760
+ try {
62761
+ realPath = NFS.realpathSync.native(tsConfigPath);
62762
+ } catch {
62763
+ return null;
62764
+ }
62540
62765
  if (visitedPaths.has(realPath)) return null;
62541
62766
  const tsConfig = readTsConfig(realPath);
62542
62767
  if (!tsConfig) return null;
@@ -63064,27 +63289,30 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
63064
63289
  }
63065
63290
  return null;
63066
63291
  };
63067
- const configExpressionMayDefineProperty = (expression, propertyName, analysis) => {
63292
+ const configExpressionMayDefineProperty = (expression, propertyName, analysis, visitedExpressions = /* @__PURE__ */ new Set()) => {
63068
63293
  let resolvedExpression = expression;
63069
63294
  while (ts.isParenthesizedExpression(resolvedExpression) || ts.isAsExpression(resolvedExpression) || ts.isTypeAssertionExpression(resolvedExpression) || ts.isSatisfiesExpression(resolvedExpression) || ts.isNonNullExpression(resolvedExpression)) resolvedExpression = resolvedExpression.expression;
63295
+ if (visitedExpressions.has(resolvedExpression)) return true;
63296
+ const nextVisitedExpressions = new Set(visitedExpressions);
63297
+ nextVisitedExpressions.add(resolvedExpression);
63070
63298
  if (ts.isIdentifier(resolvedExpression)) {
63071
63299
  if (analysis.localBindings.has(resolvedExpression.text)) {
63072
63300
  const localReference = analysis.localBindings.get(resolvedExpression.text);
63073
- return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis) : true;
63301
+ return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis, nextVisitedExpressions) : true;
63074
63302
  }
63075
63303
  const scopedBinding = getScopedConfigBinding(resolvedExpression);
63076
- if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis) : true;
63304
+ if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis, nextVisitedExpressions) : true;
63077
63305
  const topLevelBinding = getTopLevelBinding(analysis.sourceFile, resolvedExpression.text);
63078
- return topLevelBinding && ts.isExpression(topLevelBinding) ? configExpressionMayDefineProperty(topLevelBinding, propertyName, analysis) : true;
63306
+ return topLevelBinding && ts.isExpression(topLevelBinding) ? configExpressionMayDefineProperty(topLevelBinding, propertyName, analysis, nextVisitedExpressions) : true;
63079
63307
  }
63080
63308
  if (ts.isConditionalExpression(resolvedExpression)) {
63081
- if (isStaticallyTruthyConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis);
63082
- if (isStaticallyDisabledConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis);
63083
- return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis) || configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis);
63309
+ if (isStaticallyTruthyConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis, nextVisitedExpressions);
63310
+ if (isStaticallyDisabledConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis, nextVisitedExpressions);
63311
+ return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis, nextVisitedExpressions) || configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis, nextVisitedExpressions);
63084
63312
  }
63085
63313
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return true;
63086
63314
  return resolvedExpression.properties.some((property) => {
63087
- if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
63315
+ if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis, nextVisitedExpressions);
63088
63316
  if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
63089
63317
  const staticPropertyName = getStaticPropertyName$2(property.name);
63090
63318
  return staticPropertyName === null || staticPropertyName === propertyName;
@@ -63160,7 +63388,13 @@ const analyzeConfigModuleExport = (filePath, exportName, allowCompilerTransform,
63160
63388
  if (visitedModules.has(moduleVisitKey)) return false;
63161
63389
  const nextVisitedModules = new Set(visitedModules);
63162
63390
  nextVisitedModules.add(moduleVisitKey);
63163
- return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, NFS.readFileSync(filePath, "utf-8")), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
63391
+ let sourceText;
63392
+ try {
63393
+ sourceText = NFS.readFileSync(filePath, "utf-8");
63394
+ } catch {
63395
+ return false;
63396
+ }
63397
+ return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, sourceText), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
63164
63398
  };
63165
63399
  const analyzeConfigSourceFileExport = (sourceFile, filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = [], argumentAnalysis) => {
63166
63400
  const analysis = {
@@ -63988,13 +64222,7 @@ const hasReactDependency = (packageJson) => {
63988
64222
  };
63989
64223
  return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
63990
64224
  };
63991
- const getPreactVersion = (packageJson) => {
63992
- return {
63993
- ...packageJson.peerDependencies,
63994
- ...packageJson.dependencies,
63995
- ...packageJson.devDependencies
63996
- }.preact ?? null;
63997
- };
64225
+ const getPreactVersion = (packageJson) => getDependencySpec(packageJson, "preact");
63998
64226
  const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, sourceDirectory = rootDirectory, sourcePackageJson = rootPackageJson, version }) => {
63999
64227
  if (version === null || !isCatalogReference(version)) return version;
64000
64228
  const catalogName = extractCatalogName(version);
@@ -64739,7 +64967,16 @@ const getBrowserslistProtectionRequirement = (directory) => {
64739
64967
  if (!queries) return void 0;
64740
64968
  let requirement;
64741
64969
  let hasUnsupportedTarget = false;
64742
- for (const browserTarget of (0, import_browserslist.default)(queries)) {
64970
+ const previousIgnoreOldData = process.env.BROWSERSLIST_IGNORE_OLD_DATA;
64971
+ let browserTargets = [];
64972
+ try {
64973
+ process.env.BROWSERSLIST_IGNORE_OLD_DATA = "true";
64974
+ browserTargets = (0, import_browserslist.default)(queries);
64975
+ } finally {
64976
+ if (previousIgnoreOldData === void 0) delete process.env.BROWSERSLIST_IGNORE_OLD_DATA;
64977
+ else process.env.BROWSERSLIST_IGNORE_OLD_DATA = previousIgnoreOldData;
64978
+ }
64979
+ for (const browserTarget of browserTargets) {
64743
64980
  const browserRequirement = getBrowserProtectionRequirement(browserTarget);
64744
64981
  if (browserRequirement === "unsupported") hasUnsupportedTarget = true;
64745
64982
  if (browserRequirement === "noreferrer") requirement = "noreferrer";
@@ -64849,9 +65086,9 @@ const clearProjectCache = () => {
64849
65086
  * framework-agnostic rules. Throws only when the directory has nothing
64850
65087
  * to scan (no enclosing project and no source files of its own).
64851
65088
  */
64852
- const discoverProjectWithoutPackageJson = (directory) => {
64853
- const sourceFileCount = countSourceFiles(directory);
64854
- const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
65089
+ const discoverProjectWithoutPackageJson = (directory, options) => {
65090
+ const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
65091
+ const hasOwnTsConfig = isFile(Path.join(directory, "tsconfig.json"));
64855
65092
  const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
64856
65093
  const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
64857
65094
  if (enclosingProject !== null) return {
@@ -64917,12 +65154,15 @@ const discoverProjectWithoutPackageJson = (directory) => {
64917
65154
  sourceFileCount
64918
65155
  };
64919
65156
  };
64920
- const discoverProject = (directory) => {
65157
+ const discoverProject = (directory, options = {}) => {
64921
65158
  const cached = cachedProjectInfos.get(directory);
64922
- if (cached !== void 0) return cached;
65159
+ if (cached !== void 0) return options.sourceFileCount === void 0 ? cached : {
65160
+ ...cached,
65161
+ sourceFileCount: options.sourceFileCount
65162
+ };
64923
65163
  const packageJsonPath = Path.join(directory, "package.json");
64924
65164
  if (!isFile(packageJsonPath)) {
64925
- const synthesized = discoverProjectWithoutPackageJson(directory);
65165
+ const synthesized = discoverProjectWithoutPackageJson(directory, options);
64926
65166
  cachedProjectInfos.set(directory, synthesized);
64927
65167
  return synthesized;
64928
65168
  }
@@ -64984,8 +65224,8 @@ const discoverProject = (directory) => {
64984
65224
  const tailwindVersion = tailwindcss.version;
64985
65225
  const zodVersion = zod.version;
64986
65226
  const projectName = packageJson.name ?? Path.basename(directory);
64987
- const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
64988
- const sourceFileCount = countSourceFiles(directory);
65227
+ const hasTypeScript = isFile(Path.join(directory, "tsconfig.json"));
65228
+ const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
64989
65229
  const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
64990
65230
  const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
64991
65231
  rootDirectory: directory,
@@ -65114,6 +65354,144 @@ const discoverProject = (directory) => {
65114
65354
  * single source file has nothing to analyze.
65115
65355
  */
65116
65356
  const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
65357
+ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
65358
+ "effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
65359
+ "effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
65360
+ "effect/no-derived-state": "react-doctor/no-derived-state",
65361
+ "effect/no-event-handler": "react-doctor/no-event-handler",
65362
+ "effect/no-initialize-state": "react-doctor/no-initialize-state",
65363
+ "effect/no-pass-data-to-parent": "react-doctor/no-pass-data-to-parent",
65364
+ "effect/no-pass-live-state-to-parent": "react-doctor/no-pass-live-state-to-parent",
65365
+ "effect/no-reset-all-state-on-prop-change": "react-doctor/no-reset-all-state-on-prop-change",
65366
+ "jsx-a11y/alt-text": "react-doctor/alt-text",
65367
+ "jsx-a11y/anchor-ambiguous-text": "react-doctor/anchor-ambiguous-text",
65368
+ "jsx-a11y/anchor-has-content": "react-doctor/anchor-has-content",
65369
+ "jsx-a11y/anchor-is-valid": "react-doctor/anchor-is-valid",
65370
+ "jsx-a11y/aria-activedescendant-has-tabindex": "react-doctor/aria-activedescendant-has-tabindex",
65371
+ "jsx-a11y/aria-props": "react-doctor/aria-props",
65372
+ "jsx-a11y/aria-proptypes": "react-doctor/aria-proptypes",
65373
+ "jsx-a11y/aria-role": "react-doctor/aria-role",
65374
+ "jsx-a11y/aria-unsupported-elements": "react-doctor/aria-unsupported-elements",
65375
+ "jsx-a11y/autocomplete-valid": "react-doctor/autocomplete-valid",
65376
+ "jsx-a11y/click-events-have-key-events": "react-doctor/click-events-have-key-events",
65377
+ "jsx-a11y/control-has-associated-label": "react-doctor/control-has-associated-label",
65378
+ "jsx-a11y/heading-has-content": "react-doctor/heading-has-content",
65379
+ "jsx-a11y/html-has-lang": "react-doctor/html-has-lang",
65380
+ "jsx-a11y/iframe-has-title": "react-doctor/iframe-has-title",
65381
+ "jsx-a11y/img-redundant-alt": "react-doctor/img-redundant-alt",
65382
+ "jsx-a11y/interactive-supports-focus": "react-doctor/interactive-supports-focus",
65383
+ "jsx-a11y/label-has-associated-control": "react-doctor/label-has-associated-control",
65384
+ "jsx-a11y/lang": "react-doctor/lang",
65385
+ "jsx-a11y/media-has-caption": "react-doctor/media-has-caption",
65386
+ "jsx-a11y/mouse-events-have-key-events": "react-doctor/mouse-events-have-key-events",
65387
+ "jsx-a11y/no-access-key": "react-doctor/no-access-key",
65388
+ "jsx-a11y/no-aria-hidden-on-focusable": "react-doctor/no-aria-hidden-on-focusable",
65389
+ "jsx-a11y/no-autofocus": "react-doctor/no-autofocus",
65390
+ "jsx-a11y/no-distracting-elements": "react-doctor/no-distracting-elements",
65391
+ "jsx-a11y/no-interactive-element-to-noninteractive-role": "react-doctor/no-interactive-element-to-noninteractive-role",
65392
+ "jsx-a11y/no-noninteractive-element-interactions": "react-doctor/no-noninteractive-element-interactions",
65393
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": "react-doctor/no-noninteractive-element-to-interactive-role",
65394
+ "jsx-a11y/no-noninteractive-tabindex": "react-doctor/no-noninteractive-tabindex",
65395
+ "jsx-a11y/no-redundant-roles": "react-doctor/no-redundant-roles",
65396
+ "jsx-a11y/no-static-element-interactions": "react-doctor/no-static-element-interactions",
65397
+ "jsx-a11y/prefer-tag-over-role": "react-doctor/prefer-tag-over-role",
65398
+ "jsx-a11y/role-has-required-aria-props": "react-doctor/role-has-required-aria-props",
65399
+ "jsx-a11y/role-supports-aria-props": "react-doctor/role-supports-aria-props",
65400
+ "jsx-a11y/scope": "react-doctor/scope",
65401
+ "jsx-a11y/tabindex-no-positive": "react-doctor/tabindex-no-positive",
65402
+ "react-hooks/exhaustive-deps": "react-doctor/exhaustive-deps",
65403
+ "react-hooks/rules-of-hooks": "react-doctor/rules-of-hooks",
65404
+ "react-perf/jsx-no-jsx-as-prop": "react-doctor/jsx-no-jsx-as-prop",
65405
+ "react-perf/jsx-no-new-array-as-prop": "react-doctor/jsx-no-new-array-as-prop",
65406
+ "react-perf/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
65407
+ "react-perf/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
65408
+ "react-refresh/only-export-components": "react-doctor/only-export-components",
65409
+ "react/button-has-type": "react-doctor/button-has-type",
65410
+ "react/checked-requires-onchange-or-readonly": "react-doctor/checked-requires-onchange-or-readonly",
65411
+ "react/display-name": "react-doctor/display-name",
65412
+ "react/exhaustive-deps": "react-doctor/exhaustive-deps",
65413
+ "react/forbid-component-props": "react-doctor/forbid-component-props",
65414
+ "react/forbid-dom-props": "react-doctor/forbid-dom-props",
65415
+ "react/forbid-elements": "react-doctor/forbid-elements",
65416
+ "react/forward-ref-uses-ref": "react-doctor/forward-ref-uses-ref",
65417
+ "react/hook-use-state": "react-doctor/hook-use-state",
65418
+ "react/iframe-missing-sandbox": "react-doctor/iframe-missing-sandbox",
65419
+ "react/jsx-boolean-value": "react-doctor/jsx-boolean-value",
65420
+ "react/jsx-curly-brace-presence": "react-doctor/jsx-curly-brace-presence",
65421
+ "react/jsx-filename-extension": "react-doctor/jsx-filename-extension",
65422
+ "react/jsx-fragments": "react-doctor/jsx-fragments",
65423
+ "react/jsx-handler-names": "react-doctor/jsx-handler-names",
65424
+ "react/jsx-key": "react-doctor/jsx-key",
65425
+ "react/jsx-max-depth": "react-doctor/jsx-max-depth",
65426
+ "react/jsx-no-comment-textnodes": "react-doctor/jsx-no-comment-textnodes",
65427
+ "react/jsx-no-constructed-context-values": "react-doctor/jsx-no-constructed-context-values",
65428
+ "react/jsx-no-duplicate-props": "react-doctor/jsx-no-duplicate-props",
65429
+ "react/jsx-no-jsx-as-prop": "react-doctor/jsx-no-jsx-as-prop",
65430
+ "react/jsx-no-new-array-as-prop": "react-doctor/jsx-no-new-array-as-prop",
65431
+ "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
65432
+ "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
65433
+ "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
65434
+ "react/jsx-no-undef": "react-doctor/jsx-no-undef",
65435
+ "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
65436
+ "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
65437
+ "react/jsx-props-no-spread-multi": "react-doctor/jsx-props-no-spread-multi",
65438
+ "react/jsx-props-no-spreading": "react-doctor/jsx-props-no-spreading",
65439
+ "react/no-array-index-key": "react-doctor/no-array-index-key",
65440
+ "react/no-children-prop": "react-doctor/no-children-prop",
65441
+ "react/no-clone-element": "react-doctor/no-clone-element",
65442
+ "react/no-danger": "react-doctor/no-danger",
65443
+ "react/no-danger-with-children": "react-doctor/no-danger-with-children",
65444
+ "react/no-did-mount-set-state": "react-doctor/no-did-mount-set-state",
65445
+ "react/no-did-update-set-state": "react-doctor/no-did-update-set-state",
65446
+ "react/no-direct-mutation-state": "react-doctor/no-direct-mutation-state",
65447
+ "react/no-find-dom-node": "react-doctor/no-find-dom-node",
65448
+ "react/no-is-mounted": "react-doctor/no-is-mounted",
65449
+ "react/no-multi-comp": "react-doctor/no-multi-comp",
65450
+ "react/no-namespace": "react-doctor/no-namespace",
65451
+ "react/no-react-children": "react-doctor/no-react-children",
65452
+ "react/no-redundant-should-component-update": "react-doctor/no-redundant-should-component-update",
65453
+ "react/no-render-return-value": "react-doctor/no-render-return-value",
65454
+ "react/no-set-state": "react-doctor/no-set-state",
65455
+ "react/no-string-refs": "react-doctor/no-string-refs",
65456
+ "react/no-this-in-sfc": "react-doctor/no-this-in-sfc",
65457
+ "react/no-unescaped-entities": "react-doctor/no-unescaped-entities",
65458
+ "react/no-unknown-property": "react-doctor/no-unknown-property",
65459
+ "react/no-unsafe": "react-doctor/no-unsafe",
65460
+ "react/no-unstable-nested-components": "react-doctor/no-unstable-nested-components",
65461
+ "react/no-will-update-set-state": "react-doctor/no-will-update-set-state",
65462
+ "react/only-export-components": "react-doctor/only-export-components",
65463
+ "react/prefer-es6-class": "react-doctor/prefer-es6-class",
65464
+ "react/prefer-function-component": "react-doctor/prefer-function-component",
65465
+ "react/react-in-jsx-scope": "react-doctor/react-in-jsx-scope",
65466
+ "react/require-render-return": "react-doctor/require-render-return",
65467
+ "react/rules-of-hooks": "react-doctor/rules-of-hooks",
65468
+ "react/self-closing-comp": "react-doctor/self-closing-comp",
65469
+ "react/state-in-constructor": "react-doctor/state-in-constructor",
65470
+ "react/style-prop-object": "react-doctor/style-prop-object",
65471
+ "react/void-dom-elements-no-children": "react-doctor/void-dom-elements-no-children"
65472
+ };
65473
+ const NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS = /* @__PURE__ */ new Map();
65474
+ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY)) {
65475
+ const aliases = NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(nativeRuleKey) ?? [];
65476
+ aliases.push(legacyRuleKey);
65477
+ NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
65478
+ }
65479
+ const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
65480
+ const canonicalizeRuleKey = (ruleKey) => {
65481
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
65482
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
65483
+ };
65484
+ const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
65485
+ const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
65486
+ const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
65487
+ const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
65488
+ if (canonicalCandidate === canonicalTarget) return true;
65489
+ return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
65490
+ };
65491
+ const getEquivalentRuleKeys = (ruleKey) => {
65492
+ const nativeRuleKey = canonicalizeRuleKey(ruleKey);
65493
+ return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
65494
+ };
65117
65495
  const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
65118
65496
  var InvalidGlobPatternError = class extends Error {
65119
65497
  pattern;
@@ -65167,6 +65545,11 @@ const warnConfigIssue = (message) => {
65167
65545
  };
65168
65546
  const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
65169
65547
  const collectStringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
65548
+ const hasMatchingRuleOverride = (ruleIds, ruleIdentifier) => {
65549
+ if (ruleIds.size === 0) return true;
65550
+ for (const ruleId of ruleIds) if (isSameRuleKey(ruleId, ruleIdentifier)) return true;
65551
+ return false;
65552
+ };
65170
65553
  const validateOverrideEntry = (entry, index) => {
65171
65554
  if (!isPlainObject(entry)) {
65172
65555
  warnConfigIssue(`ignore.overrides[${index}] must be an object with { files, rules }; ignoring this entry.`);
@@ -65207,7 +65590,7 @@ const isDiagnosticIgnoredByOverrides = (diagnostic, rootDirectory, overrides) =>
65207
65590
  if (overrides.length === 0) return false;
65208
65591
  const relativeFilePath = toRelativePath(diagnostic.filePath, rootDirectory);
65209
65592
  const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
65210
- return overrides.some((override) => override.filePatterns.some((pattern) => pattern.test(relativeFilePath)) && (override.ruleIds.size === 0 || override.ruleIds.has(ruleIdentifier)));
65593
+ return overrides.some((override) => override.filePatterns.some((pattern) => pattern.test(relativeFilePath)) && hasMatchingRuleOverride(override.ruleIds, ruleIdentifier));
65211
65594
  };
65212
65595
  const SEVERITY_FOR_OVERRIDE = {
65213
65596
  error: "error",
@@ -65242,144 +65625,6 @@ const buildRuleSeverityControls = (config) => {
65242
65625
  const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
65243
65626
  const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
65244
65627
  const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
65245
- const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
65246
- "effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
65247
- "effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
65248
- "effect/no-derived-state": "react-doctor/no-derived-state",
65249
- "effect/no-event-handler": "react-doctor/no-event-handler",
65250
- "effect/no-initialize-state": "react-doctor/no-initialize-state",
65251
- "effect/no-pass-data-to-parent": "react-doctor/no-pass-data-to-parent",
65252
- "effect/no-pass-live-state-to-parent": "react-doctor/no-pass-live-state-to-parent",
65253
- "effect/no-reset-all-state-on-prop-change": "react-doctor/no-reset-all-state-on-prop-change",
65254
- "jsx-a11y/alt-text": "react-doctor/alt-text",
65255
- "jsx-a11y/anchor-ambiguous-text": "react-doctor/anchor-ambiguous-text",
65256
- "jsx-a11y/anchor-has-content": "react-doctor/anchor-has-content",
65257
- "jsx-a11y/anchor-is-valid": "react-doctor/anchor-is-valid",
65258
- "jsx-a11y/aria-activedescendant-has-tabindex": "react-doctor/aria-activedescendant-has-tabindex",
65259
- "jsx-a11y/aria-props": "react-doctor/aria-props",
65260
- "jsx-a11y/aria-proptypes": "react-doctor/aria-proptypes",
65261
- "jsx-a11y/aria-role": "react-doctor/aria-role",
65262
- "jsx-a11y/aria-unsupported-elements": "react-doctor/aria-unsupported-elements",
65263
- "jsx-a11y/autocomplete-valid": "react-doctor/autocomplete-valid",
65264
- "jsx-a11y/click-events-have-key-events": "react-doctor/click-events-have-key-events",
65265
- "jsx-a11y/control-has-associated-label": "react-doctor/control-has-associated-label",
65266
- "jsx-a11y/heading-has-content": "react-doctor/heading-has-content",
65267
- "jsx-a11y/html-has-lang": "react-doctor/html-has-lang",
65268
- "jsx-a11y/iframe-has-title": "react-doctor/iframe-has-title",
65269
- "jsx-a11y/img-redundant-alt": "react-doctor/img-redundant-alt",
65270
- "jsx-a11y/interactive-supports-focus": "react-doctor/interactive-supports-focus",
65271
- "jsx-a11y/label-has-associated-control": "react-doctor/label-has-associated-control",
65272
- "jsx-a11y/lang": "react-doctor/lang",
65273
- "jsx-a11y/media-has-caption": "react-doctor/media-has-caption",
65274
- "jsx-a11y/mouse-events-have-key-events": "react-doctor/mouse-events-have-key-events",
65275
- "jsx-a11y/no-access-key": "react-doctor/no-access-key",
65276
- "jsx-a11y/no-aria-hidden-on-focusable": "react-doctor/no-aria-hidden-on-focusable",
65277
- "jsx-a11y/no-autofocus": "react-doctor/no-autofocus",
65278
- "jsx-a11y/no-distracting-elements": "react-doctor/no-distracting-elements",
65279
- "jsx-a11y/no-interactive-element-to-noninteractive-role": "react-doctor/no-interactive-element-to-noninteractive-role",
65280
- "jsx-a11y/no-noninteractive-element-interactions": "react-doctor/no-noninteractive-element-interactions",
65281
- "jsx-a11y/no-noninteractive-element-to-interactive-role": "react-doctor/no-noninteractive-element-to-interactive-role",
65282
- "jsx-a11y/no-noninteractive-tabindex": "react-doctor/no-noninteractive-tabindex",
65283
- "jsx-a11y/no-redundant-roles": "react-doctor/no-redundant-roles",
65284
- "jsx-a11y/no-static-element-interactions": "react-doctor/no-static-element-interactions",
65285
- "jsx-a11y/prefer-tag-over-role": "react-doctor/prefer-tag-over-role",
65286
- "jsx-a11y/role-has-required-aria-props": "react-doctor/role-has-required-aria-props",
65287
- "jsx-a11y/role-supports-aria-props": "react-doctor/role-supports-aria-props",
65288
- "jsx-a11y/scope": "react-doctor/scope",
65289
- "jsx-a11y/tabindex-no-positive": "react-doctor/tabindex-no-positive",
65290
- "react-hooks/exhaustive-deps": "react-doctor/exhaustive-deps",
65291
- "react-hooks/rules-of-hooks": "react-doctor/rules-of-hooks",
65292
- "react-perf/jsx-no-jsx-as-prop": "react-doctor/jsx-no-jsx-as-prop",
65293
- "react-perf/jsx-no-new-array-as-prop": "react-doctor/jsx-no-new-array-as-prop",
65294
- "react-perf/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
65295
- "react-perf/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
65296
- "react-refresh/only-export-components": "react-doctor/only-export-components",
65297
- "react/button-has-type": "react-doctor/button-has-type",
65298
- "react/checked-requires-onchange-or-readonly": "react-doctor/checked-requires-onchange-or-readonly",
65299
- "react/display-name": "react-doctor/display-name",
65300
- "react/exhaustive-deps": "react-doctor/exhaustive-deps",
65301
- "react/forbid-component-props": "react-doctor/forbid-component-props",
65302
- "react/forbid-dom-props": "react-doctor/forbid-dom-props",
65303
- "react/forbid-elements": "react-doctor/forbid-elements",
65304
- "react/forward-ref-uses-ref": "react-doctor/forward-ref-uses-ref",
65305
- "react/hook-use-state": "react-doctor/hook-use-state",
65306
- "react/iframe-missing-sandbox": "react-doctor/iframe-missing-sandbox",
65307
- "react/jsx-boolean-value": "react-doctor/jsx-boolean-value",
65308
- "react/jsx-curly-brace-presence": "react-doctor/jsx-curly-brace-presence",
65309
- "react/jsx-filename-extension": "react-doctor/jsx-filename-extension",
65310
- "react/jsx-fragments": "react-doctor/jsx-fragments",
65311
- "react/jsx-handler-names": "react-doctor/jsx-handler-names",
65312
- "react/jsx-key": "react-doctor/jsx-key",
65313
- "react/jsx-max-depth": "react-doctor/jsx-max-depth",
65314
- "react/jsx-no-comment-textnodes": "react-doctor/jsx-no-comment-textnodes",
65315
- "react/jsx-no-constructed-context-values": "react-doctor/jsx-no-constructed-context-values",
65316
- "react/jsx-no-duplicate-props": "react-doctor/jsx-no-duplicate-props",
65317
- "react/jsx-no-jsx-as-prop": "react-doctor/jsx-no-jsx-as-prop",
65318
- "react/jsx-no-new-array-as-prop": "react-doctor/jsx-no-new-array-as-prop",
65319
- "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
65320
- "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
65321
- "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
65322
- "react/jsx-no-undef": "react-doctor/jsx-no-undef",
65323
- "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
65324
- "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
65325
- "react/jsx-props-no-spread-multi": "react-doctor/jsx-props-no-spread-multi",
65326
- "react/jsx-props-no-spreading": "react-doctor/jsx-props-no-spreading",
65327
- "react/no-array-index-key": "react-doctor/no-array-index-key",
65328
- "react/no-children-prop": "react-doctor/no-children-prop",
65329
- "react/no-clone-element": "react-doctor/no-clone-element",
65330
- "react/no-danger": "react-doctor/no-danger",
65331
- "react/no-danger-with-children": "react-doctor/no-danger-with-children",
65332
- "react/no-did-mount-set-state": "react-doctor/no-did-mount-set-state",
65333
- "react/no-did-update-set-state": "react-doctor/no-did-update-set-state",
65334
- "react/no-direct-mutation-state": "react-doctor/no-direct-mutation-state",
65335
- "react/no-find-dom-node": "react-doctor/no-find-dom-node",
65336
- "react/no-is-mounted": "react-doctor/no-is-mounted",
65337
- "react/no-multi-comp": "react-doctor/no-multi-comp",
65338
- "react/no-namespace": "react-doctor/no-namespace",
65339
- "react/no-react-children": "react-doctor/no-react-children",
65340
- "react/no-redundant-should-component-update": "react-doctor/no-redundant-should-component-update",
65341
- "react/no-render-return-value": "react-doctor/no-render-return-value",
65342
- "react/no-set-state": "react-doctor/no-set-state",
65343
- "react/no-string-refs": "react-doctor/no-string-refs",
65344
- "react/no-this-in-sfc": "react-doctor/no-this-in-sfc",
65345
- "react/no-unescaped-entities": "react-doctor/no-unescaped-entities",
65346
- "react/no-unknown-property": "react-doctor/no-unknown-property",
65347
- "react/no-unsafe": "react-doctor/no-unsafe",
65348
- "react/no-unstable-nested-components": "react-doctor/no-unstable-nested-components",
65349
- "react/no-will-update-set-state": "react-doctor/no-will-update-set-state",
65350
- "react/only-export-components": "react-doctor/only-export-components",
65351
- "react/prefer-es6-class": "react-doctor/prefer-es6-class",
65352
- "react/prefer-function-component": "react-doctor/prefer-function-component",
65353
- "react/react-in-jsx-scope": "react-doctor/react-in-jsx-scope",
65354
- "react/require-render-return": "react-doctor/require-render-return",
65355
- "react/rules-of-hooks": "react-doctor/rules-of-hooks",
65356
- "react/self-closing-comp": "react-doctor/self-closing-comp",
65357
- "react/state-in-constructor": "react-doctor/state-in-constructor",
65358
- "react/style-prop-object": "react-doctor/style-prop-object",
65359
- "react/void-dom-elements-no-children": "react-doctor/void-dom-elements-no-children"
65360
- };
65361
- const NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS = /* @__PURE__ */ new Map();
65362
- for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY)) {
65363
- const aliases = NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(nativeRuleKey) ?? [];
65364
- aliases.push(legacyRuleKey);
65365
- NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
65366
- }
65367
- const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
65368
- const canonicalizeRuleKey = (ruleKey) => {
65369
- const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
65370
- return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
65371
- };
65372
- const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
65373
- const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
65374
- const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
65375
- const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
65376
- if (canonicalCandidate === canonicalTarget) return true;
65377
- return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
65378
- };
65379
- const getEquivalentRuleKeys = (ruleKey) => {
65380
- const nativeRuleKey = canonicalizeRuleKey(ruleKey);
65381
- return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
65382
- };
65383
65628
  const stripDescriptionTail = (ruleList) => {
65384
65629
  const descriptionMatch = ruleList.match(/(?:^|\s)--\s/);
65385
65630
  if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
@@ -66030,12 +66275,8 @@ const buildDiagnosticPipeline = (input) => {
66030
66275
  if (rule.tags.includes("migration-hint")) return false;
66031
66276
  return getFileContext(diagnostic.filePath) !== "production";
66032
66277
  };
66033
- const isRuleIgnored = (ruleIdentifier) => {
66034
- for (const ignored of ignoredRules) if (isSameRuleKey(ignored, ruleIdentifier)) return true;
66035
- return false;
66036
- };
66037
- const isAppOnlyRule = (ruleIdentifier) => {
66038
- for (const appOnlyRuleKey of APP_ONLY_RULE_KEYS) if (isSameRuleKey(appOnlyRuleKey, ruleIdentifier)) return true;
66278
+ const matchesRuleKey = (ruleIdentifier, candidates) => {
66279
+ for (const candidate of candidates) if (isSameRuleKey(candidate, ruleIdentifier)) return true;
66039
66280
  return false;
66040
66281
  };
66041
66282
  const isRnRawTextSuppressedByConfig = (diagnostic) => {
@@ -66077,12 +66318,13 @@ const buildDiagnosticPipeline = (input) => {
66077
66318
  if (explicitSeverityOverride === "off") return suppress(current, "config");
66078
66319
  if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
66079
66320
  }
66321
+ const ruleIdentifier = `${current.plugin}/${current.rule}`;
66080
66322
  if (explicitRuleOverride === void 0) {
66081
- if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
66323
+ if (matchesRuleKey(ruleIdentifier, APP_ONLY_RULE_KEYS) && isLibraryFile(current.filePath)) return null;
66082
66324
  }
66083
66325
  if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
66084
66326
  if (userConfig) {
66085
- if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
66327
+ if (matchesRuleKey(ruleIdentifier, ignoredRules)) return suppress(current, "config");
66086
66328
  if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
66087
66329
  if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
66088
66330
  if (isRnRawTextSuppressedByConfig(current)) return null;
@@ -66091,7 +66333,6 @@ const buildDiagnosticPipeline = (input) => {
66091
66333
  if (respectInlineDisables && current.line > 0) {
66092
66334
  const lines = getFileLines(current.filePath);
66093
66335
  if (lines) {
66094
- const ruleIdentifier = `${current.plugin}/${current.rule}`;
66095
66336
  const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
66096
66337
  if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
66097
66338
  if (evaluation.nearMissHint) current = {
@@ -66367,7 +66608,8 @@ const readSystemFacts$1 = () => ({
66367
66608
  /**
66368
66609
  * Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
66369
66610
  * the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
66370
- * memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66611
+ * memory, and the measured automatic parallel-efficiency ceiling, then clamped
66612
+ * to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66371
66613
  *
66372
66614
  * `os.availableParallelism()` already respects cgroup CPU quotas, so the core
66373
66615
  * term needs no help. Available memory is `os.totalmem()` floored by the cgroup
@@ -66383,7 +66625,7 @@ const readSystemFacts$1 = () => ({
66383
66625
  const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
66384
66626
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
66385
66627
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
66386
- return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
66628
+ return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers, 10));
66387
66629
  };
66388
66630
  const resolveLintBatchOrdering = () => {
66389
66631
  return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
@@ -66396,24 +66638,22 @@ const resolveLintBatchOrdering = () => {
66396
66638
  */
66397
66639
  var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
66398
66640
  /**
66399
- * Effect-side cap on the lint phase. The env var lets CI / eval runners
66400
- * raise the phase budget for slow large repos without recompiling.
66641
+ * Optional Effect-side cap on the lint phase. Unbounded unless explicitly
66642
+ * configured through the env var or a test layer.
66401
66643
  * Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
66402
66644
  */
66403
- var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
66645
+ var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", null) }) {};
66404
66646
  /**
66405
- * Effect-side cap on the dead-code phase, sitting above the in-worker
66406
- * timeout as a runtime-independent backstop. The env var raises it for
66407
- * type-heavy projects; tests override via
66647
+ * Optional Effect-side cap on the dead-code phase. Unbounded unless explicitly
66648
+ * configured through the env var or a test layer. Tests override via
66408
66649
  * `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
66409
66650
  */
66410
- var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
66651
+ var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", null) }) {};
66411
66652
  /**
66412
- * Overall scan deadline backstop, bounding everything the per-phase
66413
- * timeouts don't (wedged git / IO). The env var raises it for very
66414
- * large repos; tests override via `Layer.succeed(ScanDeadlineMs, ...)`.
66653
+ * Optional overall scan deadline. Unbounded unless explicitly configured
66654
+ * through the env var or a test layer.
66415
66655
  */
66416
- var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
66656
+ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", null) }) {};
66417
66657
  /**
66418
66658
  * Wall-clock budget for the supply-chain check when it runs on a background
66419
66659
  * fiber overlapping the lint pass. Reads from the env var on startup so the
@@ -66486,14 +66726,14 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
66486
66726
  /**
66487
66727
  * How the full-scan lint pass plans its file batches. `"cost"` (the default)
66488
66728
  * builds size-balanced LPT batches (`planLintBatches`): the same mandatory
66489
- * batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
66490
- * an even share of files AND bytes, so no 100-file chunk is a straggler while
66729
+ * batch count as greedy chunking, but every batch gets an even share of files
66730
+ * AND bytes, so no full chunk is a straggler while
66491
66731
  * the remainder-batch worker idles — and the heavy files are SPREAD across
66492
66732
  * batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
66493
66733
  * lacked (it packed the heaviest files into one wave-1 straggler batch,
66494
66734
  * measurably regressing size-skewed repos, which is why it never earned the
66495
66735
  * default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
66496
- * rollback hatch to plain greedy 100-file chunking in discovery order. Tests
66736
+ * rollback hatch to plain greedy fixed-size chunking in discovery order. Tests
66497
66737
  * override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
66498
66738
  * never reach this — they pass user-scoped `includePaths` that skip discovery
66499
66739
  * and stay in arrival order; only the full-scan branch reads it.
@@ -66588,6 +66828,7 @@ const DEFAULT_SURFACE_EXCLUDED_TAGS = {
66588
66828
  score: ["design"],
66589
66829
  ciFailure: ["design"]
66590
66830
  };
66831
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
66591
66832
  const VALID_RULE_SEVERITIES = [
66592
66833
  "error",
66593
66834
  "warn",
@@ -66630,7 +66871,6 @@ const SURFACE_CONTROL_FIELD_NAMES = [
66630
66871
  ];
66631
66872
  const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
66632
66873
  const SEVERITY_FIELD_NAMES = ["rules", "categories"];
66633
- const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
66634
66874
  const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
66635
66875
  const isRuleSeverity = (value) => typeof value === "string" && VALID_RULE_SEVERITIES.includes(value);
66636
66876
  const coerceMaybeBooleanString = (fieldName, value) => {
@@ -66657,8 +66897,45 @@ const validateStringArrayField = (fieldName, value) => {
66657
66897
  return false;
66658
66898
  });
66659
66899
  };
66900
+ const validateIgnoreOverride = (rawOverride, index) => {
66901
+ const fieldName = `ignore.overrides[${index}]`;
66902
+ if (!isRecord(rawOverride)) {
66903
+ warnConfigIssue(`config field "${fieldName}" must be an object (got ${typeof rawOverride}); ignoring this override.`);
66904
+ return;
66905
+ }
66906
+ const files = validateStringArrayField(`${fieldName}.files`, rawOverride.files);
66907
+ if (files === void 0) return void 0;
66908
+ const validatedOverride = { files };
66909
+ if (rawOverride.rules !== void 0) {
66910
+ const rules = validateStringArrayField(`${fieldName}.rules`, rawOverride.rules);
66911
+ if (rules !== void 0) validatedOverride.rules = rules;
66912
+ }
66913
+ return validatedOverride;
66914
+ };
66915
+ const validateIgnoreField = (rawIgnore) => {
66916
+ if (!isRecord(rawIgnore)) {
66917
+ warnConfigIssue(`config field "ignore" must be an object (got ${typeof rawIgnore}); ignoring this field.`);
66918
+ return;
66919
+ }
66920
+ const validatedIgnore = {};
66921
+ for (const fieldName of [
66922
+ "rules",
66923
+ "files",
66924
+ "tags"
66925
+ ]) {
66926
+ if (rawIgnore[fieldName] === void 0) continue;
66927
+ const validatedValues = validateStringArrayField(`ignore.${fieldName}`, rawIgnore[fieldName]);
66928
+ if (validatedValues !== void 0) validatedIgnore[fieldName] = validatedValues;
66929
+ }
66930
+ if (rawIgnore.overrides !== void 0) if (!Array.isArray(rawIgnore.overrides)) warnConfigIssue(`config field "ignore.overrides" must be an array (got ${typeof rawIgnore.overrides}); ignoring this field.`);
66931
+ else validatedIgnore.overrides = rawIgnore.overrides.flatMap((rawOverride, index) => {
66932
+ const validatedOverride = validateIgnoreOverride(rawOverride, index);
66933
+ return validatedOverride ? [validatedOverride] : [];
66934
+ });
66935
+ return validatedIgnore;
66936
+ };
66660
66937
  const validateSurfaceControls = (surface, rawControls) => {
66661
- if (!isPlainObject$1(rawControls)) {
66938
+ if (!isRecord(rawControls)) {
66662
66939
  warnConfigIssue(`config field "surfaces.${surface}" must be an object (got ${typeof rawControls}); ignoring this surface.`);
66663
66940
  return;
66664
66941
  }
@@ -66682,7 +66959,7 @@ const validateSurfaceControls = (surface, rawControls) => {
66682
66959
  return validatedSurfaceControls;
66683
66960
  };
66684
66961
  const validateSurfacesField = (rawSurfaces) => {
66685
- if (!isPlainObject$1(rawSurfaces)) {
66962
+ if (!isRecord(rawSurfaces)) {
66686
66963
  warnConfigIssue(`config field "surfaces" must be an object (got ${typeof rawSurfaces}); ignoring this field.`);
66687
66964
  return;
66688
66965
  }
@@ -66698,7 +66975,7 @@ const validateSurfacesField = (rawSurfaces) => {
66698
66975
  return validated;
66699
66976
  };
66700
66977
  const validateSeverityMap = (fieldName, rawMap, keysAreCategories = false) => {
66701
- if (!isPlainObject$1(rawMap)) {
66978
+ if (!isRecord(rawMap)) {
66702
66979
  warnConfigIssue(`config field "${fieldName}" must be an object (got ${typeof rawMap}); ignoring this field.`);
66703
66980
  return;
66704
66981
  }
@@ -66732,6 +67009,7 @@ const validateConfigTypes = (config) => {
66732
67009
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
66733
67010
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
66734
67011
  for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
67012
+ applyFieldValidator(config, validated, "ignore", validateIgnoreField);
66735
67013
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
66736
67014
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
66737
67015
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -67031,6 +67309,10 @@ const dedupeRelatedDiagnostics = (diagnostics) => {
67031
67309
  }
67032
67310
  return uniqueDiagnostics;
67033
67311
  };
67312
+ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
67313
+ const relativePath = Path.relative(parentAbsolutePath, childAbsolutePath);
67314
+ return Boolean(relativePath) && !relativePath.startsWith("..") && !Path.isAbsolute(relativePath);
67315
+ };
67034
67316
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
67035
67317
  const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
67036
67318
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -68392,7 +68674,7 @@ const collectScriptMotionEvidence = (sourceFile, typeChecker, program) => {
68392
68674
  if (calleeEvidence.isReducedMotionHook && isExpressionValueConsumed(node)) evidence.hasReducedMotionHandling = true;
68393
68675
  }
68394
68676
  if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
68395
- const tagEvidence = ts.isJsxNamespacedName(node.tagName) ? EMPTY_MOTION_EXPRESSION_EVIDENCE : resolveMotionExpressionEvidence(node.tagName, typeChecker, program);
68677
+ const tagEvidence = ts.isIdentifier(node.tagName) || ts.isPropertyAccessExpression(node.tagName) ? resolveMotionExpressionEvidence(node.tagName, typeChecker, program) : EMPTY_MOTION_EXPRESSION_EVIDENCE;
68396
68678
  if (tagEvidence.isMotionComponent || tagEvidence.isMotionComponentFactory) evidence.hasMotionUse = true;
68397
68679
  if (tagEvidence.isMotionConfig && jsxElementHasReducedMotionConfiguration(node.attributes, typeChecker)) evidence.hasReducedMotionHandling = true;
68398
68680
  }
@@ -68607,7 +68889,7 @@ const readScannedFile = (candidate) => {
68607
68889
  return null;
68608
68890
  }
68609
68891
  };
68610
- function* collectSecurityScanFiles(rootDirectory) {
68892
+ function* collectSecurityScanFiles(rootDirectory, excludedDirectories = /* @__PURE__ */ new Set()) {
68611
68893
  const priorityCandidates = [];
68612
68894
  const artifactCandidates = [];
68613
68895
  const otherCandidates = [];
@@ -68622,7 +68904,7 @@ function* collectSecurityScanFiles(rootDirectory) {
68622
68904
  for (const entry of readDirectoryEntries(current.absolutePath)) {
68623
68905
  const absolutePath = Path.join(current.absolutePath, entry.name);
68624
68906
  if (entry.isDirectory()) {
68625
- if (!SKIPPED_DIRECTORY_NAMES.has(entry.name)) stack.push({
68907
+ if (!SKIPPED_DIRECTORY_NAMES.has(entry.name) && !excludedDirectories.has(Path.resolve(absolutePath))) stack.push({
68626
68908
  absolutePath,
68627
68909
  depth: current.depth + 1
68628
68910
  });
@@ -68803,10 +69085,8 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhe
68803
69085
  }
68804
69086
  return true;
68805
69087
  };
69088
+ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
68806
69089
  const shouldEnableRuleByDefaultStatus = (options) => options.defaultEnabled !== false || options.hasExplicitOverride === true || options.includeTagDefaults && options.hasIncludedTags;
68807
- const yieldToEventLoop = () => new Promise((resolve) => {
68808
- setImmediate(resolve);
68809
- });
68810
69090
  const createSecurityScanSession = (rootDirectory, options) => {
68811
69091
  const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
68812
69092
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
@@ -68859,19 +69139,35 @@ const createSecurityScanSession = (rootDirectory, options) => {
68859
69139
  };
68860
69140
  };
68861
69141
  const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
69142
+ const throwIfScanAborted = () => {
69143
+ options.signal?.throwIfAborted();
69144
+ };
69145
+ const didReachDeadline = () => {
69146
+ const hasDeadlineElapsed = options.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(options.deadlineEpochMs) === 0;
69147
+ if (hasDeadlineElapsed) options.onDeadlineExceeded?.();
69148
+ return hasDeadlineElapsed;
69149
+ };
69150
+ throwIfScanAborted();
68862
69151
  const session = createSecurityScanSession(rootDirectory, options);
68863
69152
  if (session === null) return [];
69153
+ if (didReachDeadline()) return [];
68864
69154
  let sliceStartedAt = performance.now();
68865
- for (const file of collectSecurityScanFiles(rootDirectory)) {
69155
+ for (const file of collectSecurityScanFiles(rootDirectory, options.excludedDirectories)) {
69156
+ throwIfScanAborted();
69157
+ if (didReachDeadline()) return session.diagnostics;
68866
69158
  if (file === null) {
68867
69159
  if (performance.now() - sliceStartedAt >= 12) {
68868
69160
  await yieldToEventLoop();
69161
+ throwIfScanAborted();
69162
+ if (didReachDeadline()) return session.diagnostics;
68869
69163
  sliceStartedAt = performance.now();
68870
69164
  }
68871
69165
  continue;
68872
69166
  }
68873
69167
  for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
68874
69168
  await yieldToEventLoop();
69169
+ throwIfScanAborted();
69170
+ if (didReachDeadline()) return session.diagnostics;
68875
69171
  sliceStartedAt = performance.now();
68876
69172
  }
68877
69173
  }
@@ -69034,25 +69330,18 @@ const collectIgnorePatterns = (rootDirectory) => {
69034
69330
  cachedPatternsByRoot.set(rootDirectory, patterns);
69035
69331
  return patterns;
69036
69332
  };
69037
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
69038
- const KNIP_JSON_FILENAME = "knip.json";
69039
- const readJsonFileSafe = (filePath) => {
69040
- let rawContents;
69041
- try {
69042
- rawContents = fs.readFileSync(filePath, "utf-8");
69043
- } catch {
69044
- return null;
69045
- }
69333
+ const failOpenReadJson = (filePath, fallback) => {
69046
69334
  try {
69047
- return JSON.parse(rawContents);
69335
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
69048
69336
  } catch {
69049
- return null;
69337
+ return fallback;
69050
69338
  }
69051
69339
  };
69340
+ const KNIP_JSON_FILENAME = "knip.json";
69052
69341
  const readKnipConfig = (rootDirectory) => {
69053
- const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
69342
+ const knipJson = failOpenReadJson(path.join(rootDirectory, KNIP_JSON_FILENAME), null);
69054
69343
  if (isRecord(knipJson)) return knipJson;
69055
- const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
69344
+ const packageJson = failOpenReadJson(path.join(rootDirectory, "package.json"), null);
69056
69345
  const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
69057
69346
  return isRecord(packageKnipConfig) ? packageKnipConfig : null;
69058
69347
  };
@@ -69100,13 +69389,6 @@ const atomicWriteJson = (filePath, value) => {
69100
69389
  return;
69101
69390
  }
69102
69391
  };
69103
- const failOpenReadJson = (filePath, fallback) => {
69104
- try {
69105
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
69106
- } catch {
69107
- return fallback;
69108
- }
69109
- };
69110
69392
  const hashFileContents = (filePath) => {
69111
69393
  try {
69112
69394
  return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
@@ -69785,7 +70067,6 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
69785
70067
  return true;
69786
70068
  };
69787
70069
  const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
69788
- const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
69789
70070
  /**
69790
70071
  * Budget for the dead-code phase, scaled to the work. deslop's graph build is
69791
70072
  * CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
@@ -69793,25 +70074,28 @@ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs
69793
70074
  * tipped over by any concurrent load — silently dropping every dead-code
69794
70075
  * finding. Scaling the budget with file count (and inversely with the core
69795
70076
  * share when overlapped) lets the pass complete, while the ceiling still
69796
- * reclaims a genuinely wedged worker. Returns the in-worker SIGKILL deadline
69797
- * and the Effect-side phase backstop that sits a margin above it.
70077
+ * reclaims a genuinely wedged worker.
69798
70078
  */
69799
70079
  const resolveDeadCodeTimeout = (input) => {
69800
70080
  const coreShareFactor = Math.max(1, input.fullConcurrency / Math.max(1, input.deadCodeConcurrency));
69801
- const workerTimeoutMs = Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor)));
69802
- return {
69803
- workerTimeoutMs,
69804
- phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
69805
- };
70081
+ return { workerTimeoutMs: Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor))) };
69806
70082
  };
69807
- const resolveLintIncludePaths = (rootDirectory, userConfig) => {
70083
+ const resolveLintIncludePaths = (rootDirectory, userConfig, sourceFiles) => {
69808
70084
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
69809
70085
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
69810
- return listSourceFiles(rootDirectory).filter((filePath) => {
70086
+ return (sourceFiles ?? listSourceFiles(rootDirectory)).filter((filePath) => {
69811
70087
  if (!isLintableSourceFile(filePath)) return false;
69812
70088
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
69813
70089
  });
69814
70090
  };
70091
+ const filterPathsOutsideDirectories = (input) => {
70092
+ const excludedPrefixes = input.excludedDirectories.map((excludedDirectory) => Path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, input.rootDirectory)).map((excludedDirectory) => Path.relative(input.rootDirectory, excludedDirectory).replaceAll("\\", "/")).sort((left, right) => left.length - right.length).filter((candidatePrefix, index, prefixes) => !prefixes.slice(0, index).some((prefix) => candidatePrefix.startsWith(`${prefix}/`)));
70093
+ if (excludedPrefixes.length === 0) return [...input.relativePaths];
70094
+ return input.relativePaths.filter((relativePath) => {
70095
+ const normalizedRelativePath = relativePath.replaceAll("\\", "/").replace(/^\.\//, "");
70096
+ return !excludedPrefixes.some((excludedPrefix) => normalizedRelativePath === excludedPrefix || normalizedRelativePath.startsWith(`${excludedPrefix}/`));
70097
+ });
70098
+ };
69815
70099
  var Config = class Config extends Service()("react-doctor/Config") {
69816
70100
  static layerNode = effect(Config, gen(function* () {
69817
70101
  const cache = yield* make$3({
@@ -69873,6 +70157,7 @@ var Files = class Files extends Service()("react-doctor/Files") {
69873
70157
  static layerNode = succeed$3(Files, Files.of({
69874
70158
  readLines: (input) => sync(() => createNodeReadFileLinesSync(input.rootDirectory)(input.filePath)),
69875
70159
  listSourceFiles: (rootDirectory) => sync(() => listSourceFiles(rootDirectory)),
70160
+ listSourceFilesCooperative: (input) => promise(() => listSourceFilesCooperative(input.rootDirectory, input.signal)),
69876
70161
  isFile: (filePath) => sync(() => isFile(filePath)),
69877
70162
  isDirectory: (filePath) => sync(() => isDirectory(filePath))
69878
70163
  }));
@@ -69899,6 +70184,16 @@ var Files = class Files extends Service()("react-doctor/Files") {
69899
70184
  }
69900
70185
  return files;
69901
70186
  }),
70187
+ listSourceFilesCooperative: (input) => sync(() => {
70188
+ const rootDirectory = input.rootDirectory;
70189
+ const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
70190
+ const files = [];
70191
+ for (const absolute of tree.keys()) {
70192
+ if (!absolute.startsWith(prefix)) continue;
70193
+ files.push(absolute.slice(prefix.length));
70194
+ }
70195
+ return files;
70196
+ }),
69902
70197
  isFile: (filePath) => sync(() => tree.has(filePath)),
69903
70198
  isDirectory: (filePath) => sync(() => {
69904
70199
  const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
@@ -70363,7 +70658,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
70363
70658
  changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
70364
70659
  isCurrentChanges: false
70365
70660
  };
70366
- }).pipe(withSpan("Git.diffSelection")),
70661
+ }).pipe(catchReasons("ReactDoctorError", { GitInvocationFailed: () => succeed$2(null) }, (_reason, error) => fail$4(error)), withSpan("Git.diffSelection")),
70367
70662
  stagedFilePaths: (directory) => runGit(directory, [
70368
70663
  "diff",
70369
70664
  "--no-ext-diff",
@@ -70474,7 +70769,7 @@ const batchIncludePaths = (baseArgs, includePaths) => {
70474
70769
  for (const filePath of includePaths) {
70475
70770
  const entryLength = filePath.length + 1;
70476
70771
  const exceedsArgLength = currentBatch.length > 0 && currentBatchLength + entryLength > 24e3;
70477
- const exceedsFileCount = currentBatch.length >= 100;
70772
+ const exceedsFileCount = currentBatch.length >= 200;
70478
70773
  if (exceedsArgLength || exceedsFileCount) {
70479
70774
  batches.push(currentBatch);
70480
70775
  currentBatch = [];
@@ -70579,6 +70874,33 @@ const detectUserLintConfigPaths = (rootDirectory) => {
70579
70874
  return [];
70580
70875
  };
70581
70876
  const DISABLE_DIRECTIVE_PATTERN = /(eslint|oxlint)-disable/;
70877
+ const neutralizedFileLeases = /* @__PURE__ */ new Map();
70878
+ const activeNeutralizationInitializations = /* @__PURE__ */ new Set();
70879
+ const directoriesOverlap = (left, right) => {
70880
+ const isWithin = (parent, candidate) => {
70881
+ const relativePath = Path.relative(parent, candidate);
70882
+ return relativePath.length === 0 || !relativePath.startsWith(`..${Path.sep}`) && relativePath !== ".." && !Path.isAbsolute(relativePath);
70883
+ };
70884
+ return isWithin(left, right) || isWithin(right, left);
70885
+ };
70886
+ const withOverlappingInitializationLock = async (rootDirectory, operation) => {
70887
+ const blockers = [...activeNeutralizationInitializations].filter((initialization) => directoriesOverlap(initialization.rootDirectory, rootDirectory)).map((initialization) => initialization.done);
70888
+ let release;
70889
+ const initialization = {
70890
+ rootDirectory,
70891
+ done: new Promise((resolve) => {
70892
+ release = resolve;
70893
+ })
70894
+ };
70895
+ activeNeutralizationInitializations.add(initialization);
70896
+ await Promise.all(blockers);
70897
+ try {
70898
+ return await operation();
70899
+ } finally {
70900
+ activeNeutralizationInitializations.delete(initialization);
70901
+ release();
70902
+ }
70903
+ };
70582
70904
  const findFilesWithDisableDirectivesViaGit = async (rootDirectory, includePaths) => {
70583
70905
  const program = gen(function* () {
70584
70906
  return yield* (yield* Git).grep({
@@ -70621,34 +70943,84 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
70621
70943
  };
70622
70944
  const findFilesWithDisableDirectives = async (rootDirectory, includePaths) => await findFilesWithDisableDirectivesViaGit(rootDirectory, includePaths) ?? findFilesWithDisableDirectivesViaFilesystem(rootDirectory, includePaths);
70623
70945
  const neutralizeContent = (content) => content.replaceAll("eslint-disable", "eslint_disable").replaceAll("oxlint-disable", "oxlint_disable");
70624
- const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
70625
- const filePaths = await findFilesWithDisableDirectives(rootDirectory, includePaths);
70626
- const originalContents = /* @__PURE__ */ new Map();
70946
+ const collectNeutralizationCandidatePaths = (rootDirectory, discoveredRelativePaths, includePaths) => {
70947
+ const resolvedRootDirectory = NFS.realpathSync(rootDirectory);
70948
+ const requestedPaths = includePaths && includePaths.length > 0 ? new Set(includePaths.flatMap((includePath) => {
70949
+ try {
70950
+ return [NFS.realpathSync(Path.resolve(resolvedRootDirectory, includePath))];
70951
+ } catch {
70952
+ return [];
70953
+ }
70954
+ })) : null;
70955
+ const candidatePaths = /* @__PURE__ */ new Set();
70956
+ for (const relativePath of discoveredRelativePaths) try {
70957
+ candidatePaths.add(NFS.realpathSync(Path.resolve(resolvedRootDirectory, relativePath)));
70958
+ } catch {
70959
+ continue;
70960
+ }
70961
+ for (const leasedPath of neutralizedFileLeases.keys()) {
70962
+ const relativePath = Path.relative(resolvedRootDirectory, leasedPath);
70963
+ if (!(relativePath.length === 0 || !relativePath.startsWith(`..${Path.sep}`) && relativePath !== ".." && !Path.isAbsolute(relativePath)) || requestedPaths && !requestedPaths.has(leasedPath)) continue;
70964
+ candidatePaths.add(leasedPath);
70965
+ }
70966
+ return candidatePaths;
70967
+ };
70968
+ const neutralizeDisableDirectives = async (rootDirectory, includePaths, options = {}) => {
70969
+ const resolvedRootDirectory = NFS.realpathSync(rootDirectory);
70970
+ const leasedPaths = /* @__PURE__ */ new Set();
70627
70971
  let isRestored = false;
70628
70972
  const restore = () => {
70629
70973
  if (isRestored) return;
70630
70974
  isRestored = true;
70631
- for (const [absolutePath, originalContent] of originalContents) try {
70632
- NFS.writeFileSync(absolutePath, originalContent);
70633
- } catch (error) {
70634
- process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
70975
+ for (const absolutePath of leasedPaths) {
70976
+ const lease = neutralizedFileLeases.get(absolutePath);
70977
+ if (!lease) continue;
70978
+ lease.referenceCount -= 1;
70979
+ if (lease.referenceCount > 0) continue;
70980
+ neutralizedFileLeases.delete(absolutePath);
70981
+ try {
70982
+ NFS.writeFileSync(absolutePath, lease.originalContent);
70983
+ } catch (error) {
70984
+ process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
70985
+ }
70635
70986
  }
70636
70987
  };
70637
70988
  const onExit = () => restore();
70638
70989
  process.once("exit", onExit);
70639
- for (const relativePath of filePaths) {
70640
- const absolutePath = Path.join(rootDirectory, relativePath);
70990
+ const leaseCandidatePath = (absolutePath) => {
70991
+ if (leasedPaths.has(absolutePath)) return;
70992
+ const existingLease = neutralizedFileLeases.get(absolutePath);
70993
+ if (existingLease) {
70994
+ existingLease.referenceCount += 1;
70995
+ leasedPaths.add(absolutePath);
70996
+ return;
70997
+ }
70641
70998
  let originalContent;
70642
70999
  try {
70643
71000
  originalContent = NFS.readFileSync(absolutePath, "utf-8");
70644
71001
  } catch {
70645
- continue;
71002
+ return;
70646
71003
  }
70647
71004
  const neutralizedContent = neutralizeContent(originalContent);
70648
71005
  if (neutralizedContent !== originalContent) {
70649
- originalContents.set(absolutePath, originalContent);
70650
71006
  NFS.writeFileSync(absolutePath, neutralizedContent);
71007
+ neutralizedFileLeases.set(absolutePath, {
71008
+ originalContent,
71009
+ referenceCount: 1
71010
+ });
71011
+ leasedPaths.add(absolutePath);
70651
71012
  }
71013
+ };
71014
+ try {
71015
+ await withOverlappingInitializationLock(resolvedRootDirectory, async () => {
71016
+ for (const absolutePath of collectNeutralizationCandidatePaths(resolvedRootDirectory, [], includePaths)) if (neutralizedFileLeases.has(absolutePath)) leaseCandidatePath(absolutePath);
71017
+ const discoveredRelativePaths = await (options.findFiles ?? findFilesWithDisableDirectives)(resolvedRootDirectory, includePaths);
71018
+ for (const absolutePath of collectNeutralizationCandidatePaths(resolvedRootDirectory, discoveredRelativePaths, includePaths)) leaseCandidatePath(absolutePath);
71019
+ });
71020
+ } catch (error) {
71021
+ restore();
71022
+ process.removeListener("exit", onExit);
71023
+ throw error;
70652
71024
  }
70653
71025
  return () => {
70654
71026
  restore();
@@ -70821,7 +71193,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
70821
71193
  }
70822
71194
  return enabled;
70823
71195
  };
70824
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, runtimeGlobals, unpluginAutoImportGlobalScopes, serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
71196
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, runtimeGlobals, unpluginAutoImportGlobalScopes, serverAuthFunctionNames, projectIndexModuleSources, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
70825
71197
  const hasIncludedTags = includedTags.size > 0;
70826
71198
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
70827
71199
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
@@ -70885,7 +71257,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
70885
71257
  }))
70886
71258
  } : {},
70887
71259
  ...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
70888
- ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
71260
+ ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {},
71261
+ ...projectIndexModuleSources !== void 0 ? { projectIndexModuleSources: [...projectIndexModuleSources] } : {}
70889
71262
  } },
70890
71263
  rules: {
70891
71264
  ...reactCompilerRules,
@@ -71134,11 +71507,17 @@ const collectUnpluginAutoImportGlobalScopes = ({ rootDirectory, candidateFiles }
71134
71507
  return scopes.some((scope) => scope.names.length > 0) ? scopes : [];
71135
71508
  };
71136
71509
  const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
71510
+ const decodeDiagnostic = (raw) => {
71511
+ const diagnostic = validateDiagnostic$1(raw);
71512
+ return {
71513
+ ...diagnostic,
71514
+ relatedLocations: diagnostic.relatedLocations?.map((location) => ({ ...location }))
71515
+ };
71516
+ };
71137
71517
  const decodeFileDiagnostics = (raw) => {
71138
71518
  if (!Array.isArray(raw)) return null;
71139
71519
  try {
71140
- for (const entry of raw) validateDiagnostic$1(entry);
71141
- return raw;
71520
+ return raw.map((entry) => decodeDiagnostic(entry));
71142
71521
  } catch {
71143
71522
  return null;
71144
71523
  }
@@ -72370,6 +72749,12 @@ const buildProfiledNodeArguments = (input) => [
72370
72749
  ...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
72371
72750
  ...input.argumentsList
72372
72751
  ];
72752
+ const lowerChildProcessPriority = (processId) => {
72753
+ if (processId === void 0) return;
72754
+ try {
72755
+ OS.setPriority(processId, OS.constants.priority.PRIORITY_BELOW_NORMAL);
72756
+ } catch {}
72757
+ };
72373
72758
  const SANITIZED_ENV = buildOxlintChildEnv(process.env);
72374
72759
  /**
72375
72760
  * Spawn one oxlint subprocess with hard ceilings on wall time and
@@ -72408,6 +72793,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
72408
72793
  "pipe"
72409
72794
  ]
72410
72795
  });
72796
+ lowerChildProcessPriority(child.pid);
72411
72797
  const onAbort = () => {
72412
72798
  child.kill("SIGKILL");
72413
72799
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
@@ -72530,7 +72916,7 @@ const isParallelismRelatedSpawnError = (error) => {
72530
72916
  * loop with a slimmer config in that case.
72531
72917
  */
72532
72918
  const spawnLintBatches = async (input) => {
72533
- const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
72919
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 9, signal, deadlineEpochMs } = input;
72534
72920
  const resolveSourcePath = (filePath) => {
72535
72921
  const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
72536
72922
  return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
@@ -72538,6 +72924,12 @@ const spawnLintBatches = async (input) => {
72538
72924
  const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
72539
72925
  let rescueDeadlineEpochMs;
72540
72926
  const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
72927
+ const resolveSpawnTimeoutMs = () => {
72928
+ const deadlineBudgets = [deadlineEpochMs, rescueDeadlineEpochMs].filter((deadline) => deadline !== void 0).map(remainingDeadlineBudgetMs);
72929
+ if (deadlineBudgets.length === 0) return spawnTimeoutMs;
72930
+ const remainingDeadlineMs = Math.min(...deadlineBudgets);
72931
+ return spawnTimeoutMs === void 0 ? remainingDeadlineMs : Math.min(spawnTimeoutMs, remainingDeadlineMs);
72932
+ };
72541
72933
  const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
72542
72934
  const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
72543
72935
  const allDiagnostics = [];
@@ -72555,12 +72947,13 @@ const spawnLintBatches = async (input) => {
72555
72947
  const batchArgs = [...baseArgs, ...batch];
72556
72948
  try {
72557
72949
  const spawnBatch = () => {
72558
- if (isPastDeadline()) {
72950
+ const effectiveSpawnTimeoutMs = resolveSpawnTimeoutMs();
72951
+ if (effectiveSpawnTimeoutMs === 0) {
72559
72952
  deadlineSkippedFiles.push(...batch);
72560
72953
  batchState.deadlineSkippedFileCount += batch.length;
72561
72954
  return Promise.resolve(null);
72562
72955
  }
72563
- return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal, () => {
72956
+ return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, effectiveSpawnTimeoutMs, outputMaxBytes, signal, () => {
72564
72957
  if (batchState.didStart) return;
72565
72958
  batchState.didStart = true;
72566
72959
  startedFileCount += batchState.initialFileCount;
@@ -72715,11 +73108,36 @@ const validateRuleRegistration = () => {
72715
73108
  ].filter((entry) => entry !== null).join("; ");
72716
73109
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
72717
73110
  };
73111
+ const PROJECT_INDEX_MODULE_SOURCES = [
73112
+ "next/og",
73113
+ "@vercel/og",
73114
+ "satori",
73115
+ "remotion"
73116
+ ];
73117
+ const collectProjectIndexModuleSources = async (rootDirectory, candidateFiles) => {
73118
+ const foundModuleSources = /* @__PURE__ */ new Set();
73119
+ let sliceStartedAt = performance.now();
73120
+ for (const candidateFile of candidateFiles) {
73121
+ let sourceBuffer;
73122
+ try {
73123
+ sourceBuffer = NFS.readFileSync(Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile));
73124
+ } catch {
73125
+ continue;
73126
+ }
73127
+ for (const moduleSource of PROJECT_INDEX_MODULE_SOURCES) if (sourceBuffer.includes(`"${moduleSource}"`) || sourceBuffer.includes(`'${moduleSource}'`)) foundModuleSources.add(moduleSource);
73128
+ if (foundModuleSources.size === PROJECT_INDEX_MODULE_SOURCES.length) break;
73129
+ if (performance.now() - sliceStartedAt >= 12) {
73130
+ await yieldToEventLoop();
73131
+ sliceStartedAt = performance.now();
73132
+ }
73133
+ }
73134
+ return [...foundModuleSources].sort();
73135
+ };
72718
73136
  /**
72719
73137
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
72720
- * `batchIncludePaths` greedily fills 100-file batches and leaves a small
72721
- * remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
72722
- * is set by whichever 100-file chunk happened to collect the most work while
73138
+ * `batchIncludePaths` greedily fills fixed-size batches and leaves a small
73139
+ * remainder batch, so the parallel pool's wall clock is set by whichever
73140
+ * full chunk happened to collect the most work while
72723
73141
  * the remainder's worker idles), this planner keeps the SAME mandatory batch
72724
73142
  * count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
72725
73143
  * perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
@@ -72748,7 +73166,7 @@ const validateRuleRegistration = () => {
72748
73166
  */
72749
73167
  const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
72750
73168
  if (files.length === 0) return [];
72751
- const batchCount = Math.ceil(files.length / 100);
73169
+ const batchCount = Math.ceil(files.length / 200);
72752
73170
  const maxFilesPerBatch = Math.ceil(files.length / batchCount);
72753
73171
  const baseArgsLengthChars = estimateArgsLength(baseArgs);
72754
73172
  const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
@@ -73277,6 +73695,7 @@ const runOxlint = async (options) => {
73277
73695
  await yieldToEventLoop();
73278
73696
  const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
73279
73697
  const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
73698
+ const projectIndexModuleSources = includePaths === void 0 && options.deadlineEpochMs === void 0 ? await collectProjectIndexModuleSources(rootDirectory, candidateFiles) : void 0;
73280
73699
  unpluginAutoImportGlobalScopes = collectUnpluginAutoImportGlobalScopes({
73281
73700
  rootDirectory,
73282
73701
  candidateFiles
@@ -73299,6 +73718,7 @@ const runOxlint = async (options) => {
73299
73718
  runtimeGlobals,
73300
73719
  unpluginAutoImportGlobalScopes,
73301
73720
  serverAuthFunctionNames,
73721
+ projectIndexModuleSources,
73302
73722
  severityControls,
73303
73723
  userPlugins,
73304
73724
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
@@ -73766,10 +74186,10 @@ const translateProjectInfoError = (cause, directory) => {
73766
74186
  }) });
73767
74187
  };
73768
74188
  var Project = class Project extends Service()("react-doctor/Project") {
73769
- static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (directory) {
74189
+ static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (input) {
73770
74190
  return yield* try_({
73771
- try: () => discoverProject(directory),
73772
- catch: (cause) => translateProjectInfoError(cause, directory)
74191
+ try: () => discoverProject(input.directory, { sourceFileCount: input.sourceFileCount }),
74192
+ catch: (cause) => translateProjectInfoError(cause, input.directory)
73773
74193
  });
73774
74194
  }) }));
73775
74195
  static layerOf = (projectInfo) => succeed$3(Project, Project.of({ discover: () => succeed$2(projectInfo) }));
@@ -74354,9 +74774,10 @@ const checkSupplyChain = (input) => gen(function* () {
74354
74774
  * outage.
74355
74775
  */
74356
74776
  var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
74357
- static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
74358
- ...input,
74359
- totalTimeoutMs
74777
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (configuredTimeoutMs) => checkSupplyChain({
74778
+ rootDirectory: input.rootDirectory,
74779
+ userConfig: input.userConfig,
74780
+ totalTimeoutMs: input.timeoutMs ?? configuredTimeoutMs
74360
74781
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
74361
74782
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
74362
74783
  };
@@ -74391,10 +74812,6 @@ const NO_HOOKS = {
74391
74812
  beforeLint: () => void_,
74392
74813
  afterLint: () => void_
74393
74814
  };
74394
- const filterMapNullable = (transform) => fromPredicateOption((value) => {
74395
- const result = transform(value);
74396
- return result === null ? none() : some(result);
74397
- });
74398
74815
  const fileReader = (filesService, rootDirectory) => (filePath) => {
74399
74816
  const lines = runSync(filesService.readLines({
74400
74817
  filePath,
@@ -74418,7 +74835,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
74418
74835
  * The GitHub viewer-permission lookup is forked onto a background
74419
74836
  * fiber here and joined late (it feeds score metadata, not
74420
74837
  * diagnostics).
74421
- * 2. beforeLint hook (e.g. CLI renders the project-detection block)
74838
+ * 2. beforeLint hook (e.g. CLI records project telemetry)
74422
74839
  * 3. environment checks (reduced-motion + pnpm hardening +
74423
74840
  * expo/react-native), collected synchronously. The heavier
74424
74841
  * content-regex security scan is forked instead (like supply-chain
@@ -74431,13 +74848,11 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
74431
74848
  * `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
74432
74849
  * socket can't drag out its join; on timeout it fails open to no
74433
74850
  * diagnostics — the same outcome class as a Socket outage.
74434
- * 5. Linter.run runs; DeadCode.run runs concurrently (forked child
74435
- * fiber) ONLY when the memory gate has headroom to run the 8 GB
74436
- * dead-code child alongside the oxlint workers — or when overlap is
74437
- * forced via REACT_DOCTOR_DEAD_CODE_OVERLAP. Otherwise dead-code
74438
- * runs sequentially after lint, exactly as it did pre-overlap. The
74439
- * fiber is joined (or interrupted, SIGKILLing its worker, on lint
74440
- * failure) before diagnostics are concatenated. The afterLint hook
74851
+ * 5. Linter.run runs; DeadCode.run forks only when
74852
+ * REACT_DOCTOR_DEAD_CODE_OVERLAP=on. The default "auto" and explicit
74853
+ * "off" paths stay sequential. The fiber is joined (or interrupted,
74854
+ * SIGKILLing its worker, on lint failure) before diagnostics are
74855
+ * concatenated. The afterLint hook
74441
74856
  * fires between lint and dead-code. Progress spinner labels AND the
74442
74857
  * final diagnostic / score order stay independent of execution
74443
74858
  * order, so terminal output is identical either way; supply-chain
@@ -74472,8 +74887,28 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74472
74887
  const partialFailuresRef = yield* LintPartialFailures;
74473
74888
  const resolvedConfig = yield* configService.resolve(input.directory);
74474
74889
  const scanDirectory = resolvedConfig.resolvedDirectory;
74475
- const project = yield* projectService.discover(scanDirectory);
74890
+ const excludedProjectDirectories = (input.excludedProjectDirectories ?? []).map((excludedDirectory) => Path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, scanDirectory));
74891
+ const excludedProjectRelativePaths = excludedProjectDirectories.map((excludedDirectory) => Path.relative(scanDirectory, excludedDirectory).replaceAll("\\", "/"));
74892
+ const isExcludedProjectDiagnostic = (diagnostic) => {
74893
+ const relativeFilePath = toNormalizedRelativePath(diagnostic.filePath, scanDirectory);
74894
+ return excludedProjectRelativePaths.some((excludedRelativePath) => relativeFilePath === excludedRelativePath || relativeFilePath.startsWith(`${excludedRelativePath}/`));
74895
+ };
74896
+ const precomputedSourceFiles = input.suppressScanSummary === true || excludedProjectDirectories.length > 0 ? yield* filesService.listSourceFilesCooperative({
74897
+ rootDirectory: scanDirectory,
74898
+ signal: input.signal
74899
+ }) : null;
74900
+ const includedPrecomputedSourceFiles = precomputedSourceFiles !== null && excludedProjectDirectories.length > 0 ? filterPathsOutsideDirectories({
74901
+ rootDirectory: scanDirectory,
74902
+ relativePaths: precomputedSourceFiles,
74903
+ excludedDirectories: excludedProjectDirectories
74904
+ }) : precomputedSourceFiles;
74905
+ const sourceFileCount = excludedProjectDirectories.length > 0 ? includedPrecomputedSourceFiles?.length : input.precomputedSourceFileCount ?? includedPrecomputedSourceFiles?.length;
74906
+ const project = yield* projectService.discover({
74907
+ directory: scanDirectory,
74908
+ sourceFileCount
74909
+ });
74476
74910
  if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
74911
+ const deadCodeSourceFileCount = input.retainExcludedProjectDeadCodeDiagnostics === true && precomputedSourceFiles !== null ? precomputedSourceFiles.length : project.sourceFileCount;
74477
74912
  const [repo, sha, defaultBranch] = yield* all([
74478
74913
  gitService.githubRepo(scanDirectory).pipe(orElseSucceed(() => null)),
74479
74914
  gitService.headSha(scanDirectory).pipe(orElseSucceed(() => null)),
@@ -74484,11 +74919,16 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74484
74919
  directory: scanDirectory,
74485
74920
  repo
74486
74921
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
74487
- const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
74488
- const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74922
+ let lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, includedPrecomputedSourceFiles ?? void 0);
74923
+ if (excludedProjectDirectories.length > 0) lintIncludePaths = filterPathsOutsideDirectories({
74924
+ rootDirectory: scanDirectory,
74925
+ relativePaths: lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory)),
74926
+ excludedDirectories: excludedProjectDirectories
74927
+ });
74928
+ const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74489
74929
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
74490
74930
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
74491
- yield* beforeLint(project, lintIncludePaths ?? void 0);
74931
+ yield* beforeLint(project);
74492
74932
  const isDiffMode = input.includePaths.length > 0;
74493
74933
  const showWarnings = input.warnings ?? resolvedConfig.config?.warnings ?? true;
74494
74934
  const transform = buildDiagnosticPipeline({
@@ -74498,7 +74938,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74498
74938
  respectInlineDisables: input.respectInlineDisables,
74499
74939
  showWarnings
74500
74940
  });
74501
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
74941
+ const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(fromPredicateOption((diagnostic) => {
74942
+ const filteredDiagnostic = transform.apply(diagnostic);
74943
+ return filteredDiagnostic === null ? none() : some(filteredDiagnostic);
74944
+ })));
74502
74945
  const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
74503
74946
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
74504
74947
  ...checkReducedMotion(scanDirectory),
@@ -74508,17 +74951,31 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74508
74951
  ...checkReactNativeProject(scanDirectory, project)
74509
74952
  ])));
74510
74953
  const securityScanFailedRef = yield* make$13(false);
74511
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
74954
+ let didSecurityScanReachDeadline = false;
74955
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise((signal) => checkSecurityScanCooperative(scanDirectory, {
74512
74956
  project,
74513
74957
  ignoredTags: input.ignoredTags,
74514
74958
  includedTags: input.includedTags,
74515
- includeTagDefaults: input.includeTagDefaults
74959
+ includeTagDefaults: input.includeTagDefaults,
74960
+ excludedDirectories: new Set(excludedProjectDirectories),
74961
+ deadlineEpochMs: input.deadlineEpochMs,
74962
+ signal,
74963
+ onDeadlineExceeded: () => {
74964
+ didSecurityScanReachDeadline = true;
74965
+ }
74516
74966
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
74967
+ const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74968
+ const capOptionalToDeadline = (phaseTimeoutMs) => {
74969
+ if (input.deadlineEpochMs === void 0) return phaseTimeoutMs;
74970
+ const remainingBudgetMs = remainingDeadlineBudgetMs(input.deadlineEpochMs);
74971
+ return phaseTimeoutMs === null ? remainingBudgetMs : Math.min(phaseTimeoutMs, remainingBudgetMs);
74972
+ };
74517
74973
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
74518
- const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
74974
+ const supplyChainOverlapTimeout = capToDeadline(yield* SupplyChainOverlapTimeoutMs);
74519
74975
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
74520
74976
  rootDirectory: scanDirectory,
74521
- userConfig: resolvedConfig.config
74977
+ userConfig: resolvedConfig.config,
74978
+ timeoutMs: supplyChainOverlapTimeout
74522
74979
  }))).pipe(map$3((diagnostics) => ({
74523
74980
  diagnostics,
74524
74981
  timedOut: false
@@ -74542,46 +74999,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74542
74999
  const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
74543
75000
  const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
74544
75001
  const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
74545
- const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
74546
75002
  const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
74547
- const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74548
75003
  const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
74549
75004
  const deadCodeOverlapMode = yield* DeadCodeOverlap;
74550
75005
  const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
74551
75006
  const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
74552
75007
  const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
74553
- const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
74554
- rootDirectory: scanDirectory,
74555
- parseConcurrency: deadCodeParseConcurrency,
74556
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
74557
- onCacheOutcome: (didHitCache) => {
74558
- deadCodeCacheHit = didHitCache;
74559
- },
74560
- onSummaryCacheStats: (stats) => {
74561
- deadCodeSummaryCacheHits = stats.hits;
74562
- deadCodeSummaryCacheMisses = stats.misses;
74563
- }
74564
- }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
74565
- yield* set(deadCodeFailure, {
74566
- didFail: true,
74567
- reason: error.message
74568
- });
74569
- return empty$4;
74570
- })))))).pipe(timeoutOption(deadCodeTimeout.phaseTimeoutMs), flatMap$2(match$3({
74571
- onNone: () => set(deadCodeFailure, {
74572
- didFail: true,
74573
- reason: `Dead-code analysis exceeded ${Math.round(deadCodeTimeout.phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
74574
- }).pipe(as([])),
74575
- onSome: succeed$2
74576
- })));
75008
+ const buildCollectDeadCode = (deadCodeTimeout) => {
75009
+ const collectDeadCode = runCollect(applyPerElementPipeline(deadCodeService.run({
75010
+ rootDirectory: scanDirectory,
75011
+ parseConcurrency: deadCodeParseConcurrency,
75012
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
75013
+ onCacheOutcome: (didHitCache) => {
75014
+ deadCodeCacheHit = didHitCache;
75015
+ },
75016
+ onSummaryCacheStats: (stats) => {
75017
+ deadCodeSummaryCacheHits = stats.hits;
75018
+ deadCodeSummaryCacheMisses = stats.misses;
75019
+ }
75020
+ }).pipe(filter((diagnostic) => input.retainExcludedProjectDeadCodeDiagnostics === true || !isExcludedProjectDiagnostic(diagnostic)), catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
75021
+ yield* set(deadCodeFailure, {
75022
+ didFail: true,
75023
+ reason: error.message
75024
+ });
75025
+ return empty$4;
75026
+ }))))));
75027
+ const phaseTimeoutMs = deadCodeTimeout.phaseTimeoutMs;
75028
+ if (phaseTimeoutMs === null) return collectDeadCode;
75029
+ return collectDeadCode.pipe(timeoutOption(phaseTimeoutMs), flatMap$2(match$3({
75030
+ onNone: () => set(deadCodeFailure, {
75031
+ didFail: true,
75032
+ reason: `Dead-code analysis exceeded ${Math.round(phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
75033
+ }).pipe(as([])),
75034
+ onSome: succeed$2
75035
+ })));
75036
+ };
74577
75037
  const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
74578
- sourceFileCount: project.sourceFileCount,
75038
+ sourceFileCount: deadCodeSourceFileCount,
74579
75039
  deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
74580
75040
  fullConcurrency: scanConcurrency
74581
75041
  });
74582
75042
  const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
74583
75043
  workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
74584
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
75044
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74585
75045
  })) : null;
74586
75046
  const scanProgress = yield* progressService.start("Scanning...");
74587
75047
  const scanStartTime = Date.now();
@@ -74632,7 +75092,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74632
75092
  });
74633
75093
  return empty$4;
74634
75094
  }))));
74635
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
75095
+ const collectLintDiagnostics = runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream));
75096
+ const lintCollected = dedupeRelatedDiagnostics(yield* lintPhaseTimeoutMs === null ? collectLintDiagnostics : collectLintDiagnostics.pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74636
75097
  onNone: () => set(lintFailure, {
74637
75098
  didFail: true,
74638
75099
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -74662,13 +75123,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74662
75123
  } else {
74663
75124
  yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
74664
75125
  const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
74665
- sourceFileCount: totalFileCount,
75126
+ sourceFileCount: input.retainExcludedProjectDeadCodeDiagnostics === true ? deadCodeSourceFileCount : totalFileCount,
74666
75127
  deadCodeConcurrency: scanConcurrency,
74667
75128
  fullConcurrency: scanConcurrency
74668
75129
  });
74669
75130
  deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
74670
75131
  workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
74671
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
75132
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74672
75133
  });
74673
75134
  }
74674
75135
  const deadCodeFailureState = lintFailureState.didFail ? {
@@ -74712,7 +75173,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74712
75173
  metadata: scoreMetadata
74713
75174
  });
74714
75175
  const lintPartialFailures = yield* get$2(partialFailuresRef);
74715
- const securityScanFailed = yield* get$2(securityScanFailedRef);
75176
+ const didSecurityScanFail = yield* get$2(securityScanFailedRef);
75177
+ const securityScanFailed = didSecurityScanFail || didSecurityScanReachDeadline;
75178
+ let securityScanFailureReason = null;
75179
+ if (didSecurityScanReachDeadline) securityScanFailureReason = "Security scan reached the max scan duration; findings collected before the deadline were preserved.";
75180
+ else if (didSecurityScanFail) securityScanFailureReason = "Security scan failed and was skipped.";
74716
75181
  return {
74717
75182
  project,
74718
75183
  userConfig: resolvedConfig.config,
@@ -74735,6 +75200,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74735
75200
  scanConcurrency,
74736
75201
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
74737
75202
  securityScanFailed,
75203
+ securityScanFailureReason,
74738
75204
  lintCacheHitFileCount,
74739
75205
  lintCacheTotalFileCount,
74740
75206
  lintSidecarReplayedFileCount,
@@ -74750,7 +75216,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74750
75216
  "inspect.runDeadCode": input.runDeadCode,
74751
75217
  "inspect.isCi": input.isCi,
74752
75218
  "inspect.scoreSurface": input.scoreSurface ?? "score"
74753
- } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })))));
75219
+ } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => {
75220
+ if (scanDeadlineMs === null) return scanProgram;
75221
+ return scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })));
75222
+ }));
74754
75223
  const parseNodeVersion = (versionString) => {
74755
75224
  const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
74756
75225
  return {
@@ -74851,16 +75320,6 @@ const resolveNodeForOxlint = () => {
74851
75320
  });
74852
75321
  });
74853
75322
  /**
74854
- * Zip-Slip defense: relative paths come from git (`diff --name-only`), which
74855
- * normalizes during ordinary adds, but a crafted index/pack/symlinked tree can
74856
- * smuggle `..` segments that escape the temp root. Resolve against the temp dir
74857
- * and reject anything that lands outside before writing.
74858
- */
74859
- const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
74860
- const relative = path.relative(parentAbsolutePath, childAbsolutePath);
74861
- return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
74862
- };
74863
- /**
74864
75323
  * Writes a set of source files (supplied by `readContent` — e.g.
74865
75324
  * `git show <ref>:<path>` for a baseline tree, or `git show :<path>` for the
74866
75325
  * index) into a temp tree that mirrors the project layout, copying the
@@ -74896,7 +75355,7 @@ const materializeSourceTree = (input) => gen(function* () {
74896
75355
  const sourcePath = path.join(input.directory, subdirectory, configFilename);
74897
75356
  const targetPath = path.resolve(resolvedTempDirectory, subdirectory, configFilename);
74898
75357
  if (!isPathInsideDirectory(targetPath, resolvedTempDirectory)) continue;
74899
- if (fs.existsSync(sourcePath) && !fs.existsSync(targetPath)) {
75358
+ if (isFile(sourcePath) && !fs.existsSync(targetPath)) {
74900
75359
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
74901
75360
  fs.cpSync(sourcePath, targetPath);
74902
75361
  }
@@ -75135,6 +75594,7 @@ const buildJsonReport = (input) => {
75135
75594
  directory: input.directory,
75136
75595
  diff: toJsonDiff(input.diff),
75137
75596
  projects,
75597
+ ...input.skippedProjects && input.skippedProjects.length > 0 ? { skippedProjects: [...input.skippedProjects] } : {},
75138
75598
  diagnostics: flattenedDiagnostics,
75139
75599
  summary,
75140
75600
  elapsedMilliseconds: input.totalElapsedMilliseconds,
@@ -75159,7 +75619,7 @@ const buildSkippedChecks = (input) => {
75159
75619
  else if (input.lintPartialFailures.length > 0) skippedCheckReasons["lint:partial"] = input.lintPartialFailures.join("; ");
75160
75620
  if (input.didDeadCodeFail && input.deadCodeFailureReason !== null) skippedCheckReasons["dead-code"] = input.deadCodeFailureReason;
75161
75621
  if (input.supplyChainOverlapTimedOut) skippedCheckReasons["supply-chain"] = "Supply-chain analysis timed out and was skipped.";
75162
- if (input.securityScanFailed) skippedCheckReasons["security-scan"] = "Security scan failed and was skipped.";
75622
+ if (input.securityScanFailed) skippedCheckReasons["security-scan"] = input.securityScanFailureReason ?? "Security scan failed and was skipped.";
75163
75623
  return {
75164
75624
  skippedChecks,
75165
75625
  skippedCheckReasons
@@ -75194,14 +75654,6 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
75194
75654
  };
75195
75655
  }).pipe(provide(Git.layerNode)));
75196
75656
  const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
75197
- /**
75198
- * Back-compat alias: the streaming pipeline holds its caches in
75199
- * per-pipeline closures that are garbage-collected when the pipeline
75200
- * goes out of scope, so there is nothing to clear at module scope. The
75201
- * public CLI's `clearCaches()` still calls this for symmetry with the
75202
- * other `clear*` helpers.
75203
- */
75204
- const clearAutoSuppressionCaches = () => {};
75205
75657
  const createLintPhaseAbortError = () => new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) });
75206
75658
  const createOxlintSpawnSlots = (concurrency) => createWorkerSlots({
75207
75659
  slotCount: resolveScanConcurrency(concurrency),
@@ -75214,71 +75666,47 @@ const createOxlintSpawnSlots = (concurrency) => createWorkerSlots({
75214
75666
  * untouched so the loader sees the same plain object either way.
75215
75667
  */
75216
75668
  const defineConfig = (config) => config;
75217
- const AI_TRAINING_ENV_VARS = [
75218
- ["HF_DATASETS_CACHE", "huggingface"],
75219
- ["HF_HOME", "huggingface"],
75220
- ["HUGGINGFACE_HUB_CACHE", "huggingface"],
75221
- ["SPACE_ID", "huggingface-spaces"],
75222
- ["CUDA_VISIBLE_DEVICES", "cuda"],
75223
- ["NVIDIA_VISIBLE_DEVICES", "nvidia"],
75224
- ["ROCR_VISIBLE_DEVICES", "rocm"],
75225
- ["TPU_NAME", "google-tpu"],
75669
+ const AI_TRAINING_BY_ENVIRONMENT_VARIABLE = [
75226
75670
  ["WANDB_RUN_ID", "wandb"],
75227
75671
  ["WANDB_SWEEP_ID", "wandb"],
75228
75672
  ["MLFLOW_RUN_ID", "mlflow"],
75229
- ["MLFLOW_TRACKING_URI", "mlflow"],
75230
75673
  ["COMET_EXPERIMENT_KEY", "comet"],
75231
75674
  ["NEPTUNE_RUN_ID", "neptune"],
75232
75675
  ["CLEARML_TASK_ID", "clearml"],
75233
75676
  ["DVC_STAGE", "dvc"],
75234
75677
  ["RAY_WORKER_PROCESS", "ray"],
75235
- ["RAY_ADDRESS", "ray"],
75236
- ["MUJOCO_GL", "mujoco"],
75237
- ["MUJOCO_PATH", "mujoco"],
75238
- ["GYM_DISABLE_ENV_CHECKER", "gymnasium"],
75239
75678
  ["SM_TRAINING_ENV", "sagemaker"],
75240
75679
  ["TRAINING_JOB_ARN", "sagemaker"],
75241
- ["SAGEMAKER_BASE_DIR", "sagemaker"],
75242
75680
  ["AZUREML_RUN_ID", "azure-ml"],
75243
- ["AZURE_ML_MODEL_DIR", "azure-ml"],
75244
- ["CLOUD_ML_PROJECT_ID", "vertex-ai"],
75245
- ["VERTEX_AI_LOG_LEVEL", "vertex-ai"],
75246
- ["DET_MASTER", "determined-ai"],
75247
- ["LIGHTNING_USER_ID", "lightning-ai"],
75248
75681
  ["FLYTE_INTERNAL_EXECUTION_ID", "flyte"],
75249
- ["ARGO_WORKFLOW_NAME", "argo-workflows"],
75250
75682
  ["KFP_POD_NAME", "kubeflow-pipelines"],
75251
75683
  ["KAGGLE_KERNEL_RUN_TYPE", "kaggle"],
75252
75684
  ["COLAB_BACKEND_VERSION", "google-colab"],
75253
- ["DATABRICKS_RUNTIME_VERSION", "databricks"],
75254
75685
  ["DAYTONA_WS_ID", "daytona"],
75255
- ["DAYTONA_WS_NAME", "daytona"],
75256
75686
  ["E2B_SANDBOX_ID", "e2b"],
75257
75687
  ["MODAL_FUNCTION_ID", "modal"],
75258
75688
  ["MODAL_TASK_ID", "modal"],
75259
75689
  ["RUNPOD_POD_ID", "runpod"],
75260
- ["REPLICATE_USERNAME", "replicate"],
75261
75690
  ["VAST_CONTAINERLABEL", "vast-ai"],
75262
- ["HARBOR_URL", "harbor"],
75263
- ["HARBOR_HOSTNAME", "harbor"],
75264
75691
  ["SWE_BENCH_TASK", "swe-bench"],
75265
75692
  ["SWEBENCH_TASK", "swe-bench"],
75266
75693
  ["SWE_AGENT_MODEL", "swe-agent"]
75267
75694
  ];
75268
- const detectAiTrainingEnvironment = () => {
75269
- for (const [envVar, label] of AI_TRAINING_ENV_VARS) if (process.env[envVar]) return label;
75695
+ const detectAiTrainingEnvironment = (environment = process.env) => {
75696
+ for (const [environmentVariable, label] of AI_TRAINING_BY_ENVIRONMENT_VARIABLE) if (environment[environmentVariable]?.trim()) return label;
75270
75697
  return null;
75271
75698
  };
75272
- const unionValues = (baseValues, overrideValues) => [...new Set([...baseValues, ...overrideValues])];
75699
+ const stringValues = (values) => Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
75700
+ const unionValues = (baseValues, overrideValues) => [...new Set([...stringValues(baseValues), ...stringValues(overrideValues)])];
75273
75701
  const mergeIgnores = (baseIgnore, overrideIgnore) => {
75274
75702
  const mergedIgnore = {
75275
75703
  ...baseIgnore,
75276
75704
  ...overrideIgnore
75277
75705
  };
75278
- if (baseIgnore.rules && overrideIgnore.rules) mergedIgnore.rules = unionValues(baseIgnore.rules, overrideIgnore.rules);
75279
- if (baseIgnore.files && overrideIgnore.files) mergedIgnore.files = unionValues(baseIgnore.files, overrideIgnore.files);
75280
- if (baseIgnore.tags && overrideIgnore.tags) mergedIgnore.tags = unionValues(baseIgnore.tags, overrideIgnore.tags);
75281
- if (baseIgnore.overrides && overrideIgnore.overrides) mergedIgnore.overrides = [...baseIgnore.overrides, ...overrideIgnore.overrides];
75706
+ if (baseIgnore.rules !== void 0 || overrideIgnore.rules !== void 0) mergedIgnore.rules = unionValues(baseIgnore.rules, overrideIgnore.rules);
75707
+ if (baseIgnore.files !== void 0 || overrideIgnore.files !== void 0) mergedIgnore.files = unionValues(baseIgnore.files, overrideIgnore.files);
75708
+ if (baseIgnore.tags !== void 0 || overrideIgnore.tags !== void 0) mergedIgnore.tags = unionValues(baseIgnore.tags, overrideIgnore.tags);
75709
+ if (baseIgnore.overrides !== void 0 || overrideIgnore.overrides !== void 0) mergedIgnore.overrides = [...Array.isArray(baseIgnore.overrides) ? baseIgnore.overrides : [], ...Array.isArray(overrideIgnore.overrides) ? overrideIgnore.overrides : []];
75282
75710
  return mergedIgnore;
75283
75711
  };
75284
75712
  /**
@@ -75460,7 +75888,6 @@ const clearCaches = () => {
75460
75888
  clearPackageJsonCache();
75461
75889
  clearIgnorePatternsCache();
75462
75890
  clearPackageRoleCache();
75463
- clearAutoSuppressionCaches();
75464
75891
  clearMinifiedFileCache();
75465
75892
  };
75466
75893
  const toJsonReport = (result, options) => buildJsonReport({
@@ -75487,4 +75914,4 @@ const toJsonReport = (result, options) => buildJsonReport({
75487
75914
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
75488
75915
 
75489
75916
  //# sourceMappingURL=index.js.map
75490
- //# debugId=0c9da9c7-4847-5807-983e-a57b7eec97cd
75917
+ //# debugId=6d2e43de-ae97-5213-b1a7-a9905ce4b1d5