react-doctor 0.7.4 → 0.7.5-dev.0f07133

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,15 +1,15 @@
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]="e047abe8-ac42-570c-8465-e0f0dcb0715c")}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]="ab586686-464c-58e6-a96d-70f7b39b1c5d")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
+ import reactDoctorPlugin, { 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, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
5
6
  import * as NFS from "node:fs";
6
7
  import fs from "node:fs";
7
8
  import * as Path from "node:path";
8
9
  import path from "node:path";
9
10
  import * as NodeChildProcess from "node:child_process";
10
11
  import { execFileSync, spawn, spawnSync } from "node:child_process";
11
- import * as ts from "typescript";
12
- import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
12
+ import ts from "typescript";
13
13
  import * as OS from "node:os";
14
14
  import os from "node:os";
15
15
  import { parseJSON5 } from "confbox";
@@ -19310,12 +19310,45 @@ var Diagnostic = class extends Class("Diagnostic")({
19310
19310
  relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
19311
19311
  fixGroupId: optional(String$1)
19312
19312
  }) {};
19313
+ var JsonReportDiagnosticV3 = class extends Class("JsonReportDiagnosticV3")({
19314
+ id: String$1,
19315
+ normalizedFilePath: String$1,
19316
+ filePath: String$1,
19317
+ plugin: String$1,
19318
+ rule: String$1,
19319
+ severity: Severity,
19320
+ tags: ArraySchema(String$1),
19321
+ title: optional(String$1),
19322
+ message: String$1,
19323
+ help: String$1,
19324
+ url: optional(String$1),
19325
+ line: Number$1,
19326
+ column: Number$1,
19327
+ offset: optional(Number$1),
19328
+ length: optional(Number$1),
19329
+ endLine: optional(Number$1),
19330
+ endColumn: optional(Number$1),
19331
+ category: String$1,
19332
+ matchByOccurrence: optional(Boolean$1),
19333
+ fileContext: optional(Literals(["test", "story"])),
19334
+ suppressionHint: optional(String$1),
19335
+ relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
19336
+ fixGroupId: optional(String$1)
19337
+ }) {};
19338
+ /**
19339
+ * Deterministic identity string for a diagnostic. Same diagnostic
19340
+ * across two scans yields the same identity; lets baselines,
19341
+ * suppression files, content-hash caches, and IDE "ignore this"
19342
+ * actions all key off one shared shape.
19343
+ */
19344
+ const buildDiagnosticIdentity = (input) => `${input.filePath}::${input.line}:${input.column}::${input.plugin}/${input.rule}`;
19313
19345
  const JsonReportMode = Literals([
19314
19346
  "full",
19315
19347
  "diff",
19316
19348
  "staged",
19317
19349
  "baseline"
19318
19350
  ]);
19351
+ const Framework = Literals(FRAMEWORK_TOKENS);
19319
19352
  var JsonReportSummary = class extends Class("JsonReportSummary")({
19320
19353
  errorCount: Number$1,
19321
19354
  warningCount: Number$1,
@@ -19353,13 +19386,22 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
19353
19386
  scannedFileCount: optional(Number$1),
19354
19387
  elapsedMilliseconds: Number$1
19355
19388
  }) {};
19356
- /**
19357
- * Versioned JsonReport schema. `JsonReport` is a `Schema.Union` so we
19358
- * can add `schemaVersion: 2` later as one new union member without
19359
- * breaking existing v1 consumers (the GitHub Action keys off the
19360
- * version literal). Today's union is single-arm; the shape is
19361
- * intentional.
19362
- */
19389
+ var JsonReportProjectEntryV3 = class extends Class("JsonReportProjectEntryV3")({
19390
+ directory: String$1,
19391
+ packageRoot: String$1,
19392
+ framework: Framework,
19393
+ project: Unknown,
19394
+ diagnostics: ArraySchema(JsonReportDiagnosticV3),
19395
+ score: Unknown,
19396
+ skippedChecks: ArraySchema(String$1),
19397
+ skippedCheckReasons: optional(Record$1(String$1, String$1)),
19398
+ analyzedFiles: ArraySchema(String$1),
19399
+ analyzedFileCount: Number$1,
19400
+ complete: Boolean$1,
19401
+ scannedFileCount: optional(Number$1),
19402
+ elapsedMilliseconds: Number$1
19403
+ }) {};
19404
+ /** Original full, diff, and staged report contract. */
19363
19405
  var JsonReportV1 = class extends Class("JsonReportV1")({
19364
19406
  schemaVersion: Literal(1),
19365
19407
  version: String$1,
@@ -19420,7 +19462,27 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
19420
19462
  elapsedMilliseconds: Number$1,
19421
19463
  error: NullOr(JsonReportError)
19422
19464
  }) {};
19423
- Union([JsonReportV1, JsonReportV2]);
19465
+ var JsonReportV3 = class extends Class("JsonReportV3")({
19466
+ schemaVersion: Literal(3),
19467
+ version: String$1,
19468
+ ok: Boolean$1,
19469
+ directory: String$1,
19470
+ mode: JsonReportMode,
19471
+ baselineDegraded: optional(Boolean$1),
19472
+ reactDetected: optional(Boolean$1),
19473
+ diff: NullOr(JsonReportDiffInfo),
19474
+ baseline: optional(JsonReportBaseline),
19475
+ projects: ArraySchema(JsonReportProjectEntryV3),
19476
+ diagnostics: ArraySchema(JsonReportDiagnosticV3),
19477
+ summary: JsonReportSummary,
19478
+ elapsedMilliseconds: Number$1,
19479
+ error: NullOr(JsonReportError)
19480
+ }) {};
19481
+ Union([
19482
+ JsonReportV1,
19483
+ JsonReportV2,
19484
+ JsonReportV3
19485
+ ]);
19424
19486
  //#endregion
19425
19487
  //#region ../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/constants.js
19426
19488
  var require_constants$1 = /* @__PURE__ */ __commonJSMin$1(((exports, module) => {
@@ -32978,7 +33040,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32978
33040
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32979
33041
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
32980
33042
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
32981
- const CORE_PACKAGE_VERSION = "0.7.4";
33043
+ const CORE_PACKAGE_VERSION = "0.7.5";
32982
33044
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32983
33045
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
32984
33046
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -33890,6 +33952,22 @@ const isPackageJsonReanimatedAware = (packageJson) => {
33890
33952
  };
33891
33953
  return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
33892
33954
  };
33955
+ const SSR_DEPENDENCY_NAMES = new Set([
33956
+ "@react-router/cloudflare",
33957
+ "@react-router/node",
33958
+ "@react-router/serve",
33959
+ "vike",
33960
+ "vite-plugin-ssr"
33961
+ ]);
33962
+ const isPackageJsonSsrAware = (packageJson) => {
33963
+ const allDependencies = {
33964
+ ...packageJson.peerDependencies,
33965
+ ...packageJson.dependencies,
33966
+ ...packageJson.devDependencies,
33967
+ ...packageJson.optionalDependencies
33968
+ };
33969
+ return Object.keys(allDependencies).some((packageName) => SSR_DEPENDENCY_NAMES.has(packageName));
33970
+ };
33893
33971
  const getWorkspacePatterns = (rootDirectory, packageJson) => {
33894
33972
  const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
33895
33973
  if (pnpmPatterns.length > 0) return pnpmPatterns;
@@ -34030,6 +34108,7 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
34030
34108
  }
34031
34109
  facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34032
34110
  facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34111
+ facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
34033
34112
  };
34034
34113
  const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34035
34114
  const facts = {
@@ -34051,6 +34130,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34051
34130
  },
34052
34131
  hasReactNativeAwarePackage: false,
34053
34132
  hasReanimatedAwarePackage: false,
34133
+ hasSsrDependency: false,
34054
34134
  reanimatedVersion: null
34055
34135
  };
34056
34136
  evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
@@ -34290,6 +34370,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
34290
34370
  hasTypeScript: hasOwnTsConfig,
34291
34371
  hasReactCompiler: false,
34292
34372
  hasTanStackQuery: false,
34373
+ hasSsrDependency: false,
34293
34374
  preactVersion: null,
34294
34375
  preactMajorVersion: null,
34295
34376
  hasReactNativeWorkspace: false,
@@ -34409,6 +34490,7 @@ const discoverProject = (directory) => {
34409
34490
  hasTypeScript,
34410
34491
  hasReactCompiler: detectReactCompiler(directory, packageJson),
34411
34492
  hasTanStackQuery: hasTanStackQuery(packageJson),
34493
+ hasSsrDependency: workspaceFacts.hasSsrDependency,
34412
34494
  preactVersion,
34413
34495
  preactMajorVersion: parseReactMajor(preactVersion),
34414
34496
  hasReactNativeWorkspace,
@@ -35488,6 +35570,14 @@ var AmbiguousProject = class extends TaggedErrorClass()("AmbiguousProject", {
35488
35570
  return `Ambiguous project at ${this.directory}: found ${this.candidates.length} candidates (${this.candidates.join(", ")})`;
35489
35571
  }
35490
35572
  };
35573
+ var ProjectDiscoveryFailed = class extends TaggedErrorClass()("ProjectDiscoveryFailed", {
35574
+ directory: String$1,
35575
+ cause: Unknown
35576
+ }) {
35577
+ get message() {
35578
+ return `Project discovery failed for ${this.directory}: ${pretty(fail$6(this.cause))}`;
35579
+ }
35580
+ };
35491
35581
  var DeadCodeAnalysisFailed = class extends TaggedErrorClass()("DeadCodeAnalysisFailed", { cause: Unknown }) {
35492
35582
  get message() {
35493
35583
  return `Dead-code analysis failed: ${pretty(fail$6(this.cause))}`;
@@ -35522,6 +35612,7 @@ const ReactDoctorErrorReason = Union([
35522
35612
  ProjectNotFound,
35523
35613
  NoReactDependency,
35524
35614
  AmbiguousProject,
35615
+ ProjectDiscoveryFailed,
35525
35616
  DeadCodeAnalysisFailed,
35526
35617
  GitInvocationFailed,
35527
35618
  GitBaseBranchMissing,
@@ -37218,6 +37309,12 @@ const CLIENT_ONLY_FRAMEWORKS = new Set([
37218
37309
  "react-native",
37219
37310
  "expo"
37220
37311
  ]);
37312
+ const SSR_FRAMEWORKS = new Set([
37313
+ "nextjs",
37314
+ "remix",
37315
+ "gatsby",
37316
+ "tanstack-start"
37317
+ ]);
37221
37318
  const addMajorLadder = (capabilities, name, major, earliest, latest) => {
37222
37319
  if (major === null) return;
37223
37320
  const cappedMajor = Math.min(major, latest);
@@ -37230,6 +37327,7 @@ const buildCapabilities = (project) => {
37230
37327
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
37231
37328
  if (project.expoVersion !== null) capabilities.add("expo");
37232
37329
  if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
37330
+ if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
37233
37331
  if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
37234
37332
  if (project.isStaticExport) capabilities.add("nextjs:static-export");
37235
37333
  if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
@@ -40634,6 +40732,8 @@ const spawnLintBatches = async (input) => {
40634
40732
  const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
40635
40733
  reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
40636
40734
  reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
40735
+ const unavailableFiles = new Set([...droppedFiles, ...outcome.deadlineSkippedFiles]);
40736
+ input.onAnalyzedFiles?.([...new Set(fileBatches.flat().filter((filePath) => !unavailableFiles.has(filePath)))].sort());
40637
40737
  return dedupeDiagnostics(diagnostics);
40638
40738
  };
40639
40739
  let didValidate = false;
@@ -40890,12 +40990,18 @@ const reactHooksJsPluginDropNote = (error) => {
40890
40990
  * 6. always restore disable directives + clean up the temp dir
40891
40991
  */
40892
40992
  const runOxlint = async (options) => {
40893
- const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
40993
+ const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, onFileCoverage, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
40894
40994
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40895
40995
  const severityControls = buildRuleSeverityControls(userConfig);
40896
40996
  validateRuleRegistration();
40897
40997
  resetManifestCaches();
40898
- if (includePaths !== void 0 && includePaths.length === 0) return [];
40998
+ if (includePaths !== void 0 && includePaths.length === 0) {
40999
+ onFileCoverage?.({
41000
+ candidateFiles: [],
41001
+ analyzedFiles: []
41002
+ });
41003
+ return [];
41004
+ }
40899
41005
  const pluginPath = resolvePluginPath();
40900
41006
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
40901
41007
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
@@ -40956,6 +41062,7 @@ const runOxlint = async (options) => {
40956
41062
  const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
40957
41063
  if (files.length === 0) return {
40958
41064
  diagnostics: [],
41065
+ analyzedFiles: [],
40959
41066
  didDropReactHooksJsPlugin: false,
40960
41067
  hadPartialFailure: false
40961
41068
  };
@@ -40967,24 +41074,32 @@ const runOxlint = async (options) => {
40967
41074
  const passConfigPath = Path.join(configDirectory, configFileName);
40968
41075
  const passBaseArgs = makeBaseArgs(passConfigPath);
40969
41076
  const passFileBatches = buildFileBatches(passBaseArgs, files);
40970
- const spawnPass = () => spawnLintBatches({
40971
- baseArgs: passBaseArgs,
40972
- fileBatches: passFileBatches,
40973
- rootDirectory,
40974
- nodeBinaryPath,
40975
- project,
40976
- onPartialFailure: reportPartialFailure,
40977
- onFileProgress: fileProgress,
40978
- spawnTimeoutMs,
40979
- outputMaxBytes,
40980
- concurrency: options.concurrency,
40981
- signal: options.signal,
40982
- deadlineEpochMs: options.deadlineEpochMs
40983
- });
41077
+ let analyzedFiles = [];
41078
+ const spawnPass = () => {
41079
+ analyzedFiles = [];
41080
+ return spawnLintBatches({
41081
+ baseArgs: passBaseArgs,
41082
+ fileBatches: passFileBatches,
41083
+ rootDirectory,
41084
+ nodeBinaryPath,
41085
+ project,
41086
+ onPartialFailure: reportPartialFailure,
41087
+ onAnalyzedFiles: (filePaths) => {
41088
+ analyzedFiles = filePaths;
41089
+ },
41090
+ onFileProgress: fileProgress,
41091
+ spawnTimeoutMs,
41092
+ outputMaxBytes,
41093
+ concurrency: options.concurrency,
41094
+ signal: options.signal,
41095
+ deadlineEpochMs: options.deadlineEpochMs
41096
+ });
41097
+ };
40984
41098
  writeOxlintConfig(passConfigPath, buildConfigForPass({}));
40985
41099
  try {
40986
41100
  return {
40987
41101
  diagnostics: await spawnPass(),
41102
+ analyzedFiles,
40988
41103
  didDropReactHooksJsPlugin: false,
40989
41104
  hadPartialFailure
40990
41105
  };
@@ -40996,6 +41111,7 @@ const runOxlint = async (options) => {
40996
41111
  reportPartialFailure(reactHooksJsDropNote);
40997
41112
  return {
40998
41113
  diagnostics,
41114
+ analyzedFiles,
40999
41115
  didDropReactHooksJsPlugin: true,
41000
41116
  hadPartialFailure
41001
41117
  };
@@ -41071,6 +41187,7 @@ const runOxlint = async (options) => {
41071
41187
  const missFileSet = new Set(missFiles);
41072
41188
  const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
41073
41189
  const sidecarReplayedDiagnostics = [];
41190
+ const sidecarReplayedFiles = [];
41074
41191
  const sidecarLintFiles = [];
41075
41192
  if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
41076
41193
  else {
@@ -41078,8 +41195,10 @@ const runOxlint = async (options) => {
41078
41195
  for (const hitFile of hitFiles) {
41079
41196
  const cacheKey = cacheKeyByFile.get(hitFile);
41080
41197
  const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
41081
- if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
41082
- else sidecarLintFiles.push(hitFile);
41198
+ if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) {
41199
+ sidecarReplayedFiles.push(hitFile);
41200
+ sidecarReplayedDiagnostics.push(...entry.diagnostics);
41201
+ } else sidecarLintFiles.push(hitFile);
41083
41202
  if (performance.now() - verifySliceStartedAt >= 12) {
41084
41203
  await yieldToEventLoop();
41085
41204
  verifySliceStartedAt = performance.now();
@@ -41105,6 +41224,7 @@ const runOxlint = async (options) => {
41105
41224
  sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
41106
41225
  }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
41107
41226
  diagnostics: [],
41227
+ analyzedFiles: [],
41108
41228
  didDropReactHooksJsPlugin: false,
41109
41229
  hadPartialFailure: false
41110
41230
  };
@@ -41135,6 +41255,15 @@ const runOxlint = async (options) => {
41135
41255
  probesByFile: await missProbesTask
41136
41256
  }]
41137
41257
  });
41258
+ const analyzedFileSet = new Set(fullResult.analyzedFiles);
41259
+ const boundedSidecarAnalyzedFileSet = new Set(boundedSidecarResult.analyzedFiles);
41260
+ const unboundedSidecarAnalyzedFileSet = new Set(unboundedSidecarResult.analyzedFiles);
41261
+ const sidecarReplayedFileSet = new Set(sidecarReplayedFiles);
41262
+ for (const hitFile of hitFiles) if (useSidecarCache ? (sidecarReplayedFileSet.has(hitFile) || boundedSidecarAnalyzedFileSet.has(hitFile)) && (unboundedSidecarRuleIds.length === 0 || unboundedSidecarAnalyzedFileSet.has(hitFile)) : boundedSidecarAnalyzedFileSet.has(hitFile)) analyzedFileSet.add(hitFile);
41263
+ onFileCoverage?.({
41264
+ candidateFiles,
41265
+ analyzedFiles: candidateFiles.filter((candidateFile) => analyzedFileSet.has(candidateFile))
41266
+ });
41138
41267
  return dedupeDiagnostics([
41139
41268
  ...replayedDiagnostics,
41140
41269
  ...fullResult.diagnostics,
@@ -41145,6 +41274,7 @@ const runOxlint = async (options) => {
41145
41274
  }
41146
41275
  const baseArgs = makeBaseArgs(configPath);
41147
41276
  const fileBatches = buildFileBatches(baseArgs, candidateFiles);
41277
+ let analyzedFiles = [];
41148
41278
  const runBatches = () => spawnLintBatches({
41149
41279
  baseArgs,
41150
41280
  fileBatches,
@@ -41152,6 +41282,9 @@ const runOxlint = async (options) => {
41152
41282
  nodeBinaryPath,
41153
41283
  project,
41154
41284
  onPartialFailure,
41285
+ onAnalyzedFiles: (filePaths) => {
41286
+ analyzedFiles = filePaths;
41287
+ },
41155
41288
  onFileProgress: options.onFileProgress,
41156
41289
  spawnTimeoutMs,
41157
41290
  outputMaxBytes,
@@ -41161,7 +41294,12 @@ const runOxlint = async (options) => {
41161
41294
  });
41162
41295
  writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
41163
41296
  try {
41164
- return await runBatches();
41297
+ const diagnostics = await runBatches();
41298
+ onFileCoverage?.({
41299
+ candidateFiles,
41300
+ analyzedFiles
41301
+ });
41302
+ return diagnostics;
41165
41303
  } catch (error) {
41166
41304
  const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
41167
41305
  if (reactHooksJsDropNote !== null) {
@@ -41171,11 +41309,20 @@ const runOxlint = async (options) => {
41171
41309
  }));
41172
41310
  const diagnostics = await runBatches();
41173
41311
  onPartialFailure?.(reactHooksJsDropNote);
41312
+ onFileCoverage?.({
41313
+ candidateFiles,
41314
+ analyzedFiles
41315
+ });
41174
41316
  return diagnostics;
41175
41317
  }
41176
41318
  if (extendsPaths.length === 0) throw error;
41177
41319
  writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
41178
- return await runBatches();
41320
+ const diagnostics = await runBatches();
41321
+ onFileCoverage?.({
41322
+ candidateFiles,
41323
+ analyzedFiles
41324
+ });
41325
+ return diagnostics;
41179
41326
  }
41180
41327
  } finally {
41181
41328
  restoreDisableDirectives();
@@ -41255,6 +41402,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
41255
41402
  onPartialFailure: (reason) => {
41256
41403
  collectedFailures.push(reason);
41257
41404
  },
41405
+ onFileCoverage: input.onFileCoverage,
41258
41406
  onFileProgress: input.onFileProgress,
41259
41407
  perFileLintCacheEnabled,
41260
41408
  sidecarLintCacheEnabled,
@@ -41347,7 +41495,10 @@ const translateProjectInfoError = (cause, directory) => {
41347
41495
  directory: cause.directory,
41348
41496
  candidates: cause.candidates
41349
41497
  }) });
41350
- return new ReactDoctorError({ reason: new ProjectNotFound({ directory }) });
41498
+ return new ReactDoctorError({ reason: new ProjectDiscoveryFailed({
41499
+ directory,
41500
+ cause
41501
+ }) });
41351
41502
  };
41352
41503
  var Project = class Project extends Service()("react-doctor/Project") {
41353
41504
  static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (directory) {
@@ -41959,6 +42110,7 @@ const resolveGithubActionsScoreMetadata = (environment = process.env) => {
41959
42110
  ...actorAssociation !== void 0 ? { githubActorAssociation: actorAssociation } : {}
41960
42111
  };
41961
42112
  };
42113
+ const toNormalizedRelativePath = (filePath, rootDirectory) => Path.relative(Path.resolve(rootDirectory), Path.resolve(rootDirectory, filePath)).replaceAll("\\", "/") || ".";
41962
42114
  const NO_HOOKS = {
41963
42115
  beforeLint: () => void_,
41964
42116
  afterLint: () => void_
@@ -42057,7 +42209,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42057
42209
  repo
42058
42210
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
42059
42211
  const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
42060
- const scannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
42212
+ const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
42061
42213
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
42062
42214
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
42063
42215
  yield* beforeLint(project, lintIncludePaths ?? void 0);
@@ -42162,6 +42314,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42162
42314
  let deadCodeCacheHit = null;
42163
42315
  let deadCodeSummaryCacheHits = null;
42164
42316
  let deadCodeSummaryCacheMisses = null;
42317
+ const lintFileCoverageState = { value: null };
42165
42318
  const baseLintStream = linterService.run({
42166
42319
  rootDirectory: scanDirectory,
42167
42320
  project,
@@ -42177,6 +42330,9 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42177
42330
  lastReportedTotalFileCount = totalFileCount;
42178
42331
  runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})${workerCountSuffix}...`));
42179
42332
  },
42333
+ onFileCoverage: (coverage) => {
42334
+ lintFileCoverageState.value = coverage;
42335
+ },
42180
42336
  onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
42181
42337
  lintCacheHitFileCount = cacheHitFileCount;
42182
42338
  lintCacheTotalFileCount = totalConsideredFileCount;
@@ -42207,7 +42363,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42207
42363
  const lintFailureState = yield* get$2(lintFailure);
42208
42364
  yield* afterLint(lintFailureState.didFail);
42209
42365
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
42210
- const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
42366
+ const candidateFiles = lintFileCoverageState.value === null ? [] : [...new Set(lintFileCoverageState.value.candidateFiles.map((filePath) => toNormalizedRelativePath(filePath, scanDirectory)))];
42367
+ const analyzedFiles = lintFileCoverageState.value === null ? [] : [...new Set(lintFileCoverageState.value.analyzedFiles.map((filePath) => toNormalizedRelativePath(filePath, scanDirectory)))].sort();
42368
+ const totalFileCount = candidateFiles.length || lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
42369
+ const scannedFilePaths = input.suppressScanSummary ? candidateFiles.length > 0 ? candidateFiles.map((filePath) => Path.resolve(scanDirectory, filePath)) : fallbackScannedFilePaths : [];
42211
42370
  const scannedFilesLabel = `${totalFileCount} ${totalFileCount === 1 ? "file" : "files"}`;
42212
42371
  let deadCodeCollected = [];
42213
42372
  if (lintFailureState.didFail) {
@@ -42289,6 +42448,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42289
42448
  deadCodeOverlapped: shouldOverlapDeadCode,
42290
42449
  scannedFileCount: totalFileCount,
42291
42450
  scannedFilePaths,
42451
+ analyzedFiles,
42292
42452
  scanElapsedMilliseconds,
42293
42453
  scanConcurrency,
42294
42454
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
@@ -42540,7 +42700,7 @@ const buildJsonReportError = (input) => {
42540
42700
  sentryEventId
42541
42701
  };
42542
42702
  return {
42543
- schemaVersion: 1,
42703
+ schemaVersion: 3,
42544
42704
  version: input.version,
42545
42705
  ok: false,
42546
42706
  directory: input.directory,
@@ -42585,6 +42745,7 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
42585
42745
  * React-runtime rule family was gated off for the project.
42586
42746
  */
42587
42747
  const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
42748
+ const isScanComplete = (input) => input.analyzedFileCount !== void 0 && input.scannedFileCount !== void 0 && input.skippedCheckCount === 0 && input.skippedCheckReasonCount === 0 && input.analyzedFileCount === input.scannedFileCount;
42588
42749
  const toJsonDiff = (diff) => {
42589
42750
  if (!diff) return null;
42590
42751
  return {
@@ -42607,21 +42768,68 @@ const findWorstScoredProject = (projects) => {
42607
42768
  }
42608
42769
  return worst;
42609
42770
  };
42771
+ const toJsonReportDiagnostic = (diagnostic, projectRoot, reportRoot) => {
42772
+ const normalizedFilePath = toNormalizedRelativePath(diagnostic.filePath, projectRoot);
42773
+ const reportRelativeFilePath = toNormalizedRelativePath(Path.resolve(projectRoot, diagnostic.filePath), reportRoot);
42774
+ const ruleIdentity = getDiagnosticRuleIdentity(diagnostic);
42775
+ return {
42776
+ ...diagnostic,
42777
+ id: buildDiagnosticIdentity({
42778
+ filePath: reportRelativeFilePath,
42779
+ line: diagnostic.line,
42780
+ column: diagnostic.column,
42781
+ plugin: diagnostic.plugin,
42782
+ rule: diagnostic.rule
42783
+ }),
42784
+ normalizedFilePath,
42785
+ category: ruleIdentity.category,
42786
+ tags: [...new Set(ruleIdentity.tags)].sort()
42787
+ };
42788
+ };
42610
42789
  const buildJsonReport = (input) => {
42611
- const projects = input.scans.map(({ directory, result }) => ({
42612
- directory,
42613
- project: result.project,
42614
- diagnostics: result.diagnostics,
42615
- score: result.score,
42616
- skippedChecks: result.skippedChecks,
42617
- ...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
42618
- ...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
42619
- elapsedMilliseconds: result.elapsedMilliseconds
42620
- }));
42790
+ const projects = input.scans.map(({ directory, result }) => {
42791
+ const skippedChecks = result.skippedChecks ?? [];
42792
+ const analyzedFiles = [...new Set((result.analyzedFiles ?? []).map((filePath) => toNormalizedRelativePath(filePath, result.project.rootDirectory)))].sort();
42793
+ const scannedFileCount = result.scannedFileCount ?? result.project.sourceFileCount;
42794
+ return {
42795
+ directory,
42796
+ packageRoot: result.project.rootDirectory,
42797
+ framework: result.project.framework,
42798
+ project: result.project,
42799
+ diagnostics: result.diagnostics.map((diagnostic) => toJsonReportDiagnostic(diagnostic, result.project.rootDirectory, input.directory)),
42800
+ score: result.score,
42801
+ skippedChecks,
42802
+ ...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
42803
+ analyzedFiles,
42804
+ analyzedFileCount: analyzedFiles.length,
42805
+ complete: isScanComplete({
42806
+ analyzedFileCount: result.analyzedFiles === void 0 ? void 0 : analyzedFiles.length,
42807
+ scannedFileCount,
42808
+ skippedCheckCount: skippedChecks.length,
42809
+ skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
42810
+ }),
42811
+ ...typeof result.scannedFileCount === "number" ? { scannedFileCount } : {},
42812
+ elapsedMilliseconds: result.elapsedMilliseconds
42813
+ };
42814
+ });
42621
42815
  const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
42622
42816
  const worstScoredProject = findWorstScoredProject(projects);
42623
- const summary = summarizeDiagnostics(flattenedDiagnostics, worstScoredProject?.score?.score ?? null, worstScoredProject?.score?.label ?? null);
42624
- const shared = {
42817
+ const diagnosticSummary = summarizeDiagnostics(flattenedDiagnostics, worstScoredProject?.score?.score ?? null, worstScoredProject?.score?.label ?? null);
42818
+ const affectedFileCount = projects.reduce((totalAffectedFileCount, project) => totalAffectedFileCount + new Set(project.diagnostics.map((diagnostic) => diagnostic.normalizedFilePath)).size, 0);
42819
+ const summary = {
42820
+ ...diagnosticSummary,
42821
+ affectedFileCount
42822
+ };
42823
+ return {
42824
+ schemaVersion: 3,
42825
+ mode: input.baseline !== void 0 ? "baseline" : input.baselineDegraded && input.mode === "baseline" ? "diff" : input.mode,
42826
+ ...input.baseline ? { baseline: {
42827
+ baseRef: input.baseline.baseRef,
42828
+ newCount: summary.totalDiagnosticCount,
42829
+ fixedCount: input.baseline.fixedCount,
42830
+ baseTotalCount: input.baseline.baseTotalCount
42831
+ } } : {},
42832
+ ...input.baselineDegraded ? { baselineDegraded: true } : {},
42625
42833
  ...input.scans.length > 0 ? { reactDetected: input.scans.some((scan) => hasReactRuntime(scan.result.project)) } : {},
42626
42834
  version: input.version,
42627
42835
  ok: true,
@@ -42633,39 +42841,26 @@ const buildJsonReport = (input) => {
42633
42841
  elapsedMilliseconds: input.totalElapsedMilliseconds,
42634
42842
  error: null
42635
42843
  };
42636
- if (input.baseline) return {
42637
- schemaVersion: 2,
42638
- mode: "baseline",
42639
- baseline: {
42640
- baseRef: input.baseline.baseRef,
42641
- newCount: summary.totalDiagnosticCount,
42642
- fixedCount: input.baseline.fixedCount,
42643
- baseTotalCount: input.baseline.baseTotalCount
42644
- },
42645
- ...shared
42646
- };
42647
- return {
42648
- schemaVersion: 1,
42649
- mode: input.mode,
42650
- ...input.baselineDegraded ? { baselineDegraded: true } : {},
42651
- ...shared
42652
- };
42653
42844
  };
42654
42845
  /**
42655
42846
  * Single source of truth for the skipped-check accounting shared by the
42656
42847
  * CLI renderer (`react-doctor/src/inspect.ts → finalizeAndRender`) and the
42657
42848
  * programmatic shell (`@react-doctor/api → diagnose()`). Both surface a
42658
- * failed lint / dead-code pass instead of a false "all clear", so the
42849
+ * failed or partial analysis pass instead of a false "all clear", so the
42659
42850
  * branch logic lives here once.
42660
42851
  */
42661
42852
  const buildSkippedChecks = (input) => {
42662
42853
  const skippedChecks = [];
42663
42854
  if (input.didLintFail) skippedChecks.push("lint");
42664
42855
  if (input.didDeadCodeFail) skippedChecks.push("dead-code");
42856
+ if (input.supplyChainOverlapTimedOut) skippedChecks.push("supply-chain");
42857
+ if (input.securityScanFailed) skippedChecks.push("security-scan");
42665
42858
  const skippedCheckReasons = {};
42666
42859
  if (input.didLintFail && input.lintFailureReason !== null) skippedCheckReasons.lint = input.lintFailureReason;
42667
42860
  else if (input.lintPartialFailures.length > 0) skippedCheckReasons["lint:partial"] = input.lintPartialFailures.join("; ");
42668
42861
  if (input.didDeadCodeFail && input.deadCodeFailureReason !== null) skippedCheckReasons["dead-code"] = input.deadCodeFailureReason;
42862
+ if (input.supplyChainOverlapTimedOut) skippedCheckReasons["supply-chain"] = "Supply-chain analysis timed out and was skipped.";
42863
+ if (input.securityScanFailed) skippedCheckReasons["security-scan"] = "Security scan failed and was skipped.";
42669
42864
  return {
42670
42865
  skippedChecks,
42671
42866
  skippedCheckReasons
@@ -42854,6 +43049,8 @@ const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
42854
43049
  score: output.score,
42855
43050
  skippedChecks,
42856
43051
  ...Object.keys(skippedCheckReasons).length > 0 ? { skippedCheckReasons } : {},
43052
+ analyzedFiles: output.analyzedFiles,
43053
+ scannedFileCount: output.scannedFileCount,
42857
43054
  project: output.project,
42858
43055
  reactDetected: hasReactRuntime(output.project),
42859
43056
  elapsedMilliseconds
@@ -42963,6 +43160,8 @@ const toJsonReport = (result, options) => buildJsonReport({
42963
43160
  score: result.score,
42964
43161
  skippedChecks: result.skippedChecks,
42965
43162
  ...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
43163
+ ...result.analyzedFiles ? { analyzedFiles: result.analyzedFiles } : {},
43164
+ ...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
42966
43165
  project: result.project,
42967
43166
  elapsedMilliseconds: result.elapsedMilliseconds
42968
43167
  }
@@ -42973,4 +43172,4 @@ const toJsonReport = (result, options) => buildJsonReport({
42973
43172
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
42974
43173
 
42975
43174
  //# sourceMappingURL=index.js.map
42976
- //# debugId=e047abe8-ac42-570c-8465-e0f0dcb0715c
43175
+ //# debugId=ab586686-464c-58e6-a96d-70f7b39b1c5d