react-doctor 0.7.5 → 0.7.6-dev.0a8d30b

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/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="26168f4d-471c-5b49-bec9-5b2ac01d7cd5")}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]="5dcb5b0b-ca1e-5655-8f52-3c22b421256d")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -36241,7 +36241,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
36241
36241
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
36242
36242
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
36243
36243
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
36244
- const CORE_PACKAGE_VERSION = "0.7.5";
36244
+ const CORE_PACKAGE_VERSION = "0.7.6";
36245
36245
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
36246
36246
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
36247
36247
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -36341,10 +36341,12 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
36341
36341
  const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
36342
36342
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
36343
36343
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
36344
+ const UTF8_BOM_CHARACTER = "";
36345
+ const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
36344
36346
  const cachedPackageJsons = /* @__PURE__ */ new Map();
36345
36347
  const readPackageJsonUncached = (packageJsonPath) => {
36346
36348
  try {
36347
- return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
36349
+ return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
36348
36350
  } catch (error) {
36349
36351
  if (error instanceof SyntaxError) return {};
36350
36352
  if (isErrnoException(error)) {
@@ -40017,7 +40019,6 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
40017
40019
  const PNPM_LOCKFILE = "pnpm-lock.yaml";
40018
40020
  const PACKAGE_JSON_FILE = "package.json";
40019
40021
  const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
40020
- const UTF8_BOM_CHAR = "";
40021
40022
  const HARDENING_SETTING_KEYS = new Set([
40022
40023
  "minimumReleaseAge",
40023
40024
  "blockExoticSubdeps",
@@ -40042,12 +40043,11 @@ const stripInlineComment = (rawValue) => {
40042
40043
  return rawValue;
40043
40044
  };
40044
40045
  const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
40045
- const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
40046
40046
  const parseHardeningSettings = (content) => {
40047
40047
  let minimumReleaseAge = null;
40048
40048
  let blockExoticSubdeps = null;
40049
40049
  let trustPolicy = null;
40050
- const lines = stripBom(content).split(/\r?\n/);
40050
+ const lines = stripUtf8Bom(content).split(/\r?\n/);
40051
40051
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
40052
40052
  const lineText = lines[lineIndex];
40053
40053
  if (lineText === void 0) continue;
@@ -40081,7 +40081,7 @@ const isPnpmManagedProject = (rootDirectory) => {
40081
40081
  const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
40082
40082
  if (!isFile(packageJsonPath)) return false;
40083
40083
  try {
40084
- const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
40084
+ const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
40085
40085
  const packageJson = JSON.parse(packageJsonRaw);
40086
40086
  if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
40087
40087
  } catch {
@@ -40762,9 +40762,7 @@ const setColorEnabled = (enabled) => {
40762
40762
  highlighter.gray = colors.gray;
40763
40763
  highlighter.bold = colors.bold;
40764
40764
  };
40765
- const NEXT_ENTRY_FILE_PATTERN = /^(?:\.\/)?(?:src\/)?(?:middleware|proxy)\.(?:tsx?|jsx?|mts|mjs)$/;
40766
- const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
40767
- const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
40765
+ const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
40768
40766
  const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
40769
40767
  const FALSY_VALUES = new Set([
40770
40768
  "false",
@@ -41603,11 +41601,11 @@ const resolveDeadCodeTimeout = (input) => {
41603
41601
  phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
41604
41602
  };
41605
41603
  };
41606
- const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
41604
+ const resolveLintIncludePaths = (rootDirectory, userConfig) => {
41607
41605
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
41608
41606
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
41609
41607
  return listSourceFiles(rootDirectory).filter((filePath) => {
41610
- if (!shouldIncludeExplicitLintPath(filePath, project)) return false;
41608
+ if (!isLintableSourceFile(filePath)) return false;
41611
41609
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
41612
41610
  });
41613
41611
  };
@@ -41825,6 +41823,7 @@ const parseGithubViewerPermission = (stdout) => {
41825
41823
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
41826
41824
  };
41827
41825
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
41826
+ const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
41828
41827
  /**
41829
41828
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
41830
41829
  * behind a `Context.Service`. The production layer (`layerNode`)
@@ -41895,6 +41894,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
41895
41894
  args,
41896
41895
  directory
41897
41896
  });
41897
+ const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
41898
+ "ls-files",
41899
+ "-z",
41900
+ "--others",
41901
+ "--exclude-standard",
41902
+ ...includePaths.length > 0 ? ["--", ...includePaths] : []
41903
+ ]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
41904
+ const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
41898
41905
  const currentBranch = (directory) => runGit(directory, [
41899
41906
  "rev-parse",
41900
41907
  "--abbrev-ref",
@@ -42017,7 +42024,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42017
42024
  githubViewerPermission,
42018
42025
  branchExists,
42019
42026
  mergeBase,
42020
- diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
42027
+ diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
42021
42028
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
42022
42029
  if (explicitBaseBranch !== void 0) {
42023
42030
  const range = parseGitDiffRange(explicitBaseBranch);
@@ -42046,7 +42053,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42046
42053
  "HEAD"
42047
42054
  ]);
42048
42055
  if (uncommitted.status !== 0) return null;
42049
- const files = splitNullSeparated(uncommitted.stdout);
42056
+ const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
42050
42057
  if (files.length === 0) return null;
42051
42058
  return {
42052
42059
  currentBranch: resolvedCurrentBranch,
@@ -42076,7 +42083,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42076
42083
  currentBranch: resolvedCurrentBranch,
42077
42084
  baseBranch,
42078
42085
  diffBaseRef: mergeBaseRef,
42079
- changedFiles: splitNullSeparated(diff.stdout),
42086
+ changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
42080
42087
  isCurrentChanges: false
42081
42088
  };
42082
42089
  }).pipe(withSpan("Git.diffSelection")),
@@ -42122,7 +42129,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42122
42129
  stdout: result.stdout
42123
42130
  };
42124
42131
  }).pipe(withSpan("Git.grep")),
42125
- changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
42132
+ changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
42126
42133
  if (files.length === 0) return [];
42127
42134
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
42128
42135
  const result = yield* runGit(directory, [
@@ -42136,7 +42143,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
42136
42143
  ...files
42137
42144
  ]);
42138
42145
  if (result.status !== 0) return null;
42139
- return parseChangedLineRanges(result.stdout);
42146
+ const changedLineRanges = parseChangedLineRanges(result.stdout);
42147
+ if (cached || !includeUntracked) return changedLineRanges;
42148
+ const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
42149
+ if (untrackedFilePaths === null) return changedLineRanges;
42150
+ return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
42151
+ file,
42152
+ ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
42153
+ }))];
42140
42154
  }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
42141
42155
  });
42142
42156
  })).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
@@ -42368,7 +42382,7 @@ const normalizeConfigForHash = (config) => {
42368
42382
  if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
42369
42383
  return clone;
42370
42384
  };
42371
- const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").digest("hex");
42385
+ const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").update("\0").update(input.respectInlineDisables ? "respect-inline-disables" : "audit-mode").digest("hex");
42372
42386
  /**
42373
42387
  * Loads a plugin module via the local require resolver and extracts
42374
42388
  * `(name, ruleNames)` from either `module.exports.meta + rules` or
@@ -42404,7 +42418,7 @@ const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
42404
42418
  } catch {
42405
42419
  return null;
42406
42420
  }
42407
- const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire$1(spec));
42421
+ const { ruleNames } = readPluginShape(pluginSpecifier, (specifier) => bundledRequire$1(specifier));
42408
42422
  return {
42409
42423
  entry: {
42410
42424
  name: "react-hooks-js",
@@ -42812,6 +42826,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
42812
42826
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
42813
42827
  return null;
42814
42828
  };
42829
+ const DERIVED_STATE_RULE_PRIORITY = new Map([
42830
+ ["no-initialize-state", 0],
42831
+ ["no-adjust-state-on-prop-change", 1],
42832
+ ["no-derived-state", 2],
42833
+ ["no-derived-state-effect", 3]
42834
+ ]);
42835
+ const doDiagnosticSpansOverlap = (first, second) => {
42836
+ if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
42837
+ if (first.offset !== void 0 && second.offset !== void 0) {
42838
+ const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
42839
+ const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
42840
+ return first.offset < secondEnd && second.offset < firstEnd;
42841
+ }
42842
+ const firstEndLine = first.endLine ?? first.line;
42843
+ const secondEndLine = second.endLine ?? second.line;
42844
+ return first.line <= secondEndLine && second.line <= firstEndLine;
42845
+ };
42815
42846
  const dedupeDiagnostics = (diagnostics) => {
42816
42847
  const seenKeys = /* @__PURE__ */ new Set();
42817
42848
  const uniqueDiagnostics = [];
@@ -42819,6 +42850,16 @@ const dedupeDiagnostics = (diagnostics) => {
42819
42850
  const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
42820
42851
  if (seenKeys.has(key)) continue;
42821
42852
  seenKeys.add(key);
42853
+ const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
42854
+ if (priority !== void 0) {
42855
+ const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
42856
+ if (overlappingDiagnosticIndex >= 0) {
42857
+ const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
42858
+ const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
42859
+ if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
42860
+ continue;
42861
+ }
42862
+ }
42822
42863
  uniqueDiagnostics.push(diagnostic);
42823
42864
  }
42824
42865
  return uniqueDiagnostics;
@@ -42861,6 +42902,12 @@ const mapWithConcurrency = async (items, concurrency, task) => {
42861
42902
  if (errors.length > 0) throw errors[0];
42862
42903
  return results;
42863
42904
  };
42905
+ const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
42906
+ let lineNumber = 1;
42907
+ const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
42908
+ for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
42909
+ return lineNumber;
42910
+ };
42864
42911
  const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
42865
42912
  const ACCESSORS_MIN_VERSION = "3.15.0";
42866
42913
  const supportsAccessors = (reanimatedVersion) => {
@@ -43432,13 +43479,6 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
43432
43479
  visit(sourceFile);
43433
43480
  return foundRange;
43434
43481
  };
43435
- const NEWLINE_BYTE = 10;
43436
- const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
43437
- let lineNumber = 1;
43438
- const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
43439
- for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
43440
- return lineNumber;
43441
- };
43442
43482
  /**
43443
43483
  * Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
43444
43484
  * whose enclosing function also carries a `react-hooks-js` (React
@@ -43643,8 +43683,33 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43643
43683
  }
43644
43684
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43645
43685
  const minifiedFileCache = /* @__PURE__ */ new Map();
43686
+ const sourceBufferCache = /* @__PURE__ */ new Map();
43687
+ const sourceLinesCache = /* @__PURE__ */ new Map();
43688
+ const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43689
+ const readSourceBuffer = (filename) => {
43690
+ const absolutePath = resolveAbsolutePath(filename);
43691
+ const cached = sourceBufferCache.get(absolutePath);
43692
+ if (cached !== void 0) return cached;
43693
+ let sourceBuffer;
43694
+ try {
43695
+ sourceBuffer = NFS.readFileSync(absolutePath);
43696
+ } catch {
43697
+ sourceBuffer = null;
43698
+ }
43699
+ sourceBufferCache.set(absolutePath, sourceBuffer);
43700
+ return sourceBuffer;
43701
+ };
43702
+ const readSourceLines = (filename) => {
43703
+ const absolutePath = resolveAbsolutePath(filename);
43704
+ const cached = sourceLinesCache.get(absolutePath);
43705
+ if (cached !== void 0) return cached;
43706
+ const sourceBuffer = readSourceBuffer(filename);
43707
+ const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
43708
+ sourceLinesCache.set(absolutePath, sourceLines);
43709
+ return sourceLines;
43710
+ };
43646
43711
  const isMinifiedDiagnosticFile = (filename) => {
43647
- const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43712
+ const absolutePath = resolveAbsolutePath(filename);
43648
43713
  const cached = minifiedFileCache.get(absolutePath);
43649
43714
  if (cached !== void 0) return cached;
43650
43715
  const minified = isMinifiedSource(absolutePath);
@@ -43657,8 +43722,17 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43657
43722
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
43658
43723
  const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
43659
43724
  const primarySpan = primaryLabel?.span;
43725
+ const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
43660
43726
  const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
43661
43727
  const category = resolveDiagnosticCategory(plugin, rule);
43728
+ const matchByOccurrence = resolveMatchByOccurrence(rule, category);
43729
+ const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
43730
+ const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
43731
+ const primaryLine = sourceLines?.[primaryLineIndex];
43732
+ const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
43733
+ const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
43734
+ const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
43735
+ const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
43662
43736
  return {
43663
43737
  filePath: normalizedFilePath,
43664
43738
  plugin,
@@ -43672,10 +43746,11 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43672
43746
  column: primarySpan?.column ?? 0,
43673
43747
  ...primarySpan ? {
43674
43748
  offset: primarySpan.offset,
43675
- length: primarySpan.length
43749
+ length: primarySpan.length,
43750
+ ...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
43676
43751
  } : {},
43677
43752
  category,
43678
- ...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
43753
+ ...matchByOccurrence ? { matchByOccurrence: true } : {},
43679
43754
  ...relatedLocations.length > 0 ? { relatedLocations } : {}
43680
43755
  };
43681
43756
  }), rootDirectory);
@@ -43692,6 +43767,11 @@ const buildOxlintChildEnv = (sourceEnv) => {
43692
43767
  if (!isCompileCacheDisabled && !isCompileCacheAlreadySet) childEnv.NODE_COMPILE_CACHE = Path.join(os.tmpdir(), NODE_COMPILE_CACHE_DIR_NAME);
43693
43768
  return childEnv;
43694
43769
  };
43770
+ const buildProfiledNodeArguments = (input) => [
43771
+ ...input.cpuProfileDirectory ? ["--cpu-prof", `--cpu-prof-dir=${input.cpuProfileDirectory}`] : [],
43772
+ ...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
43773
+ ...input.argumentsList
43774
+ ];
43695
43775
  const SANITIZED_ENV = buildOxlintChildEnv(process.env);
43696
43776
  /**
43697
43777
  * Spawn one oxlint subprocess with hard ceilings on wall time and
@@ -43716,7 +43796,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
43716
43796
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
43717
43797
  return;
43718
43798
  }
43719
- const child = spawn(nodeBinaryPath, args, {
43799
+ const child = spawn(nodeBinaryPath, buildProfiledNodeArguments({
43800
+ argumentsList: args,
43801
+ cpuProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_CPU_PROFILE_DIR,
43802
+ heapProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_HEAP_PROFILE_DIR
43803
+ }), {
43720
43804
  cwd: rootDirectory,
43721
43805
  env: SANITIZED_ENV,
43722
43806
  stdio: [
@@ -44360,7 +44444,7 @@ const runOxlint = async (options) => {
44360
44444
  };
44361
44445
  }
44362
44446
  };
44363
- if (perFileLintCacheEnabled && respectInlineDisables && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
44447
+ if (perFileLintCacheEnabled && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
44364
44448
  const rulesetHash = computeRulesetHash({
44365
44449
  config: buildConfig({
44366
44450
  extendsPaths: [],
@@ -44368,7 +44452,8 @@ const runOxlint = async (options) => {
44368
44452
  }),
44369
44453
  toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
44370
44454
  ignorePatterns: combinedPatterns,
44371
- tsconfigContent
44455
+ tsconfigContent,
44456
+ respectInlineDisables
44372
44457
  });
44373
44458
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
44374
44459
  const cacheKeyByFile = /* @__PURE__ */ new Map();
@@ -44411,7 +44496,8 @@ const runOxlint = async (options) => {
44411
44496
  }),
44412
44497
  toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
44413
44498
  ignorePatterns: combinedPatterns,
44414
- tsconfigContent
44499
+ tsconfigContent,
44500
+ respectInlineDisables
44415
44501
  })) : null;
44416
44502
  const probeAnswers = createSidecarProbeAnswerResolver({
44417
44503
  rootDirectory,
@@ -45451,7 +45537,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45451
45537
  directory: scanDirectory,
45452
45538
  repo
45453
45539
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
45454
- const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
45540
+ const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
45455
45541
  const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
45456
45542
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
45457
45543
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
@@ -46196,10 +46282,11 @@ const computeDiagnosticDelta = (input) => {
46196
46282
  * render diff-base mistakes (`GitBaseBranchInvalid` /
46197
46283
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
46198
46284
  */
46199
- const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
46285
+ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
46200
46286
  const selection = yield* (yield* Git).diffSelection({
46201
46287
  directory,
46202
- explicitBaseBranch
46288
+ explicitBaseBranch,
46289
+ includeUntracked
46203
46290
  });
46204
46291
  if (selection === null) return null;
46205
46292
  return {
@@ -46629,7 +46716,7 @@ const makeNoopConsole = () => ({
46629
46716
  });
46630
46717
  //#endregion
46631
46718
  //#region src/cli/utils/version.ts
46632
- const VERSION = "0.7.5";
46719
+ const VERSION = "0.7.6-dev.0a8d30b";
46633
46720
  //#endregion
46634
46721
  //#region src/cli/utils/json-mode.ts
46635
46722
  let context = null;
@@ -46998,13 +47085,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
46998
47085
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
46999
47086
  * standard `SENTRY_RELEASE` override.
47000
47087
  */
47001
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.5`;
47088
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.0a8d30b`;
47002
47089
  /**
47003
47090
  * Deployment environment shown in Sentry's environment filter. Defaults to
47004
47091
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47005
47092
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47006
47093
  */
47007
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.5") ? "development" : "production");
47094
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.0a8d30b") ? "development" : "production");
47008
47095
  /**
47009
47096
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47010
47097
  * (set to `0` to disable tracing) and falls back to
@@ -50769,7 +50856,7 @@ const BLOCKING_SUMMARY = {
50769
50856
  const SCOPE_SUMMARY = {
50770
50857
  full: "Scan the whole project on every run",
50771
50858
  files: "Report every issue in changed files",
50772
- lines: "Report issues on changed lines",
50859
+ lines: "Report issues whose source spans touch changed lines",
50773
50860
  changed: "Report only the issues a change introduces"
50774
50861
  };
50775
50862
  const gatesEqual = (left, right) => left.blocking === right.blocking && left.scope === right.scope && left.comment === right.comment && left.reviewComments === right.reviewComments && left.commitStatus === right.commitStatus;
@@ -59164,7 +59251,7 @@ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
59164
59251
  return null;
59165
59252
  }
59166
59253
  if (!stats.isFile()) return null;
59167
- if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
59254
+ if (stats.size > 10485760) return null;
59168
59255
  return hashFileContents(absolutePath);
59169
59256
  };
59170
59257
  const buildWorktreeFingerprint = (projectDirectory) => {
@@ -59638,7 +59725,9 @@ const buildScanAttributes = (input) => {
59638
59725
  hasCustomConfig: input.hasCustomConfig,
59639
59726
  rulesConfigured: ruleKeys.length,
59640
59727
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
59641
- fileCount: input.result?.scannedFileCount ?? null
59728
+ fileCount: input.result?.scannedFileCount ?? null,
59729
+ nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null,
59730
+ multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length ?? null
59642
59731
  });
59643
59732
  };
59644
59733
  /**
@@ -59707,6 +59796,12 @@ const countDeadlineSkippedFiles = (lintPartialFailures) => sumFileCountsMatching
59707
59796
  const DROPPED_FILES_MESSAGE_PATTERN = /^(\d+) file\(s\) failed to lint and were skipped/;
59708
59797
  const countDroppedLintFiles = (lintPartialFailures) => sumFileCountsMatching(lintPartialFailures, DROPPED_FILES_MESSAGE_PATTERN);
59709
59798
  //#endregion
59799
+ //#region src/cli/utils/diagnostic-intersects-line-ranges.ts
59800
+ const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
59801
+ const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
59802
+ return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
59803
+ };
59804
+ //#endregion
59710
59805
  //#region src/cli/utils/materialize-baseline-files.ts
59711
59806
  /**
59712
59807
  * Materializes the `ref` version of `files` into `tempDirectory` (via
@@ -60892,7 +60987,7 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
60892
60987
  const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(directory, diagnostic.filePath) : diagnostic.filePath);
60893
60988
  const ranges = rangesByFile.get(relativePath);
60894
60989
  if (ranges === void 0) return false;
60895
- return ranges.some(([start, end]) => diagnostic.line >= start && diagnostic.line <= end);
60990
+ return diagnosticIntersectsLineRanges(diagnostic, ranges);
60896
60991
  };
60897
60992
  };
60898
60993
  const buildIgnoredTags = (userConfig) => {
@@ -63714,10 +63809,15 @@ const warnIfAiTrainingEnvironment = () => {
63714
63809
  //#region src/cli/utils/validate-mode-flags.ts
63715
63810
  const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
63716
63811
  const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
63812
+ const validateIncludeUntrackedScope = (includeUntracked, scope) => {
63813
+ if (!includeUntracked || scope !== void 0 && scope !== "full") return;
63814
+ throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
63815
+ };
63717
63816
  const validateModeFlags = (flags) => {
63718
63817
  if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
63719
63818
  if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
63720
63819
  if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
63820
+ if (flags.includeUntracked && flags.staged) throw new CliInputError("Cannot combine --include-untracked with --staged; the git index never holds untracked files.");
63721
63821
  if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
63722
63822
  if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
63723
63823
  if (flags.debug && (flags.score === false || flags.telemetry === false)) {
@@ -63927,13 +64027,15 @@ const inspectAction = async (directory, flags) => {
63927
64027
  const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
63928
64028
  const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
63929
64029
  const requestedScope = resolveScope(flags, userConfig);
64030
+ const includeUntracked = flags.includeUntracked ?? false;
63930
64031
  const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
63931
64032
  ...requestedScope,
63932
64033
  scope: "changed"
63933
64034
  } : requestedScope;
64035
+ validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
63934
64036
  const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
63935
64037
  const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
63936
- const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
64038
+ const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
63937
64039
  const scope = await finalizeScope({
63938
64040
  requested: scopeRequest,
63939
64041
  diffInfo,
@@ -63947,7 +64049,8 @@ const inspectAction = async (directory, flags) => {
63947
64049
  const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
63948
64050
  directory: resolvedDirectory,
63949
64051
  baseRef: linesBaseRef ?? void 0,
63950
- files: [...diffInfo.changedFiles]
64052
+ files: [...diffInfo.changedFiles],
64053
+ includeUntracked
63951
64054
  }) : null;
63952
64055
  if (scope === "lines" && changedLineRanges === null && !isQuiet) {
63953
64056
  cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
@@ -64880,6 +64983,7 @@ const ROOT_FLAG_SPEC = {
64880
64983
  "--dead-code",
64881
64984
  "--debug",
64882
64985
  "--help",
64986
+ "--include-untracked",
64883
64987
  "--json",
64884
64988
  "--json-compact",
64885
64989
  "--lint",
@@ -65143,7 +65247,7 @@ ${highlighter.dim("Learn more:")}
65143
65247
  ${highlighter.info(CI_URL)}
65144
65248
  `;
65145
65249
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
65146
- const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
65250
+ const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
65147
65251
  program.action(inspectAction);
65148
65252
  program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
65149
65253
  program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
@@ -65193,4 +65297,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
65193
65297
  export {};
65194
65298
 
65195
65299
  //# sourceMappingURL=cli.js.map
65196
- //# debugId=26168f4d-471c-5b49-bec9-5b2ac01d7cd5
65300
+ //# debugId=5dcb5b0b-ca1e-5655-8f52-3c22b421256d
package/dist/index.d.ts CHANGED
@@ -9654,6 +9654,7 @@ interface DiagnoseOptions {
9654
9654
  /** See `ReactDoctorConfig.deadCode`. Ignored in diff mode. */
9655
9655
  deadCode?: boolean;
9656
9656
  verbose?: boolean;
9657
+ /** Restrict linting to these supported JS/TS source files. */
9657
9658
  includePaths?: string[];
9658
9659
  /**
9659
9660
  * Per-call override for `ReactDoctorConfig.respectInlineDisables`.
@@ -9800,9 +9801,9 @@ interface InspectResult {
9800
9801
  scanElapsedMilliseconds?: number;
9801
9802
  /**
9802
9803
  * Per-file lint cache outcome: files served from cache, and total files
9803
- * considered. Both absent when the cache was off or bypassed (audit mode,
9804
- * adopted `extends`, user plugins). The CLI projects these onto the Sentry
9805
- * wide event as `lint.cacheHitRatio`.
9804
+ * considered. Both absent when the cache was off or bypassed (adopted
9805
+ * `extends`, user plugins, React Compiler). The CLI projects these onto the
9806
+ * Sentry wide event as `lint.cacheHitRatio`.
9806
9807
  */
9807
9808
  lintCacheHitFileCount?: number | null;
9808
9809
  lintCacheTotalFileCount?: number | null;
@@ -9915,9 +9916,9 @@ interface JsonReportProjectEntry {
9915
9916
  skippedCheckReasons?: Record<string, string>;
9916
9917
  /**
9917
9918
  * Number of source files this scan's linter examined. In diff / changed
9918
- * mode it's the count of changed React-eligible files (`.tsx`/`.jsx` plus
9919
- * framework entry files); in a full scan it's the whole source tree. `0`
9920
- * in a diff scan means the changed files held nothing React Doctor lints —
9919
+ * mode it's the count of changed supported JS/TS source files; in a full
9920
+ * scan it's the whole source tree. `0` in a diff scan means the changed
9921
+ * files held nothing React Doctor lints —
9921
9922
  * the GitHub Action reads that as "nothing to report" (skips the PR comment;
9922
9923
  * the commit status says "skipped"). Optional: absent on reports from
9923
9924
  * constructors that don't track it (e.g. `toJsonReport`).
@@ -10255,7 +10256,7 @@ declare const buildJsonReport: (input: BuildJsonReportInput) => JsonReportV3$1;
10255
10256
  * render diff-base mistakes (`GitBaseBranchInvalid` /
10256
10257
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
10257
10258
  */
10258
- declare const getDiffInfo: (directory: string, explicitBaseBranch?: string) => Promise<DiffInfo | null>;
10259
+ declare const getDiffInfo: (directory: string, explicitBaseBranch?: string, includeUntracked?: boolean) => Promise<DiffInfo | null>;
10259
10260
  declare const filterSourceFiles: (filePaths: string[]) => string[];
10260
10261
  /**
10261
10262
  * Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs