react-doctor 0.7.4-dev.b686594 → 0.7.4-dev.d4f2209

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,7 +1,8 @@
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]="e5be91b0-fb63-5b7d-849e-60c4e4eb96b2")}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]="95e70e55-12b7-5577-81f3-f5331db7ca53")}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";
@@ -9,7 +10,6 @@ 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
12
  import 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";
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) => {
@@ -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,
@@ -37227,6 +37309,12 @@ const CLIENT_ONLY_FRAMEWORKS = new Set([
37227
37309
  "react-native",
37228
37310
  "expo"
37229
37311
  ]);
37312
+ const SSR_FRAMEWORKS = new Set([
37313
+ "nextjs",
37314
+ "remix",
37315
+ "gatsby",
37316
+ "tanstack-start"
37317
+ ]);
37230
37318
  const addMajorLadder = (capabilities, name, major, earliest, latest) => {
37231
37319
  if (major === null) return;
37232
37320
  const cappedMajor = Math.min(major, latest);
@@ -37239,6 +37327,7 @@ const buildCapabilities = (project) => {
37239
37327
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
37240
37328
  if (project.expoVersion !== null) capabilities.add("expo");
37241
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");
37242
37331
  if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
37243
37332
  if (project.isStaticExport) capabilities.add("nextjs:static-export");
37244
37333
  if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
@@ -40643,6 +40732,8 @@ const spawnLintBatches = async (input) => {
40643
40732
  const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
40644
40733
  reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
40645
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());
40646
40737
  return dedupeDiagnostics(diagnostics);
40647
40738
  };
40648
40739
  let didValidate = false;
@@ -40899,12 +40990,18 @@ const reactHooksJsPluginDropNote = (error) => {
40899
40990
  * 6. always restore disable directives + clean up the temp dir
40900
40991
  */
40901
40992
  const runOxlint = async (options) => {
40902
- 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;
40903
40994
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40904
40995
  const severityControls = buildRuleSeverityControls(userConfig);
40905
40996
  validateRuleRegistration();
40906
40997
  resetManifestCaches();
40907
- 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
+ }
40908
41005
  const pluginPath = resolvePluginPath();
40909
41006
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
40910
41007
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
@@ -40965,6 +41062,7 @@ const runOxlint = async (options) => {
40965
41062
  const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
40966
41063
  if (files.length === 0) return {
40967
41064
  diagnostics: [],
41065
+ analyzedFiles: [],
40968
41066
  didDropReactHooksJsPlugin: false,
40969
41067
  hadPartialFailure: false
40970
41068
  };
@@ -40976,24 +41074,32 @@ const runOxlint = async (options) => {
40976
41074
  const passConfigPath = Path.join(configDirectory, configFileName);
40977
41075
  const passBaseArgs = makeBaseArgs(passConfigPath);
40978
41076
  const passFileBatches = buildFileBatches(passBaseArgs, files);
40979
- const spawnPass = () => spawnLintBatches({
40980
- baseArgs: passBaseArgs,
40981
- fileBatches: passFileBatches,
40982
- rootDirectory,
40983
- nodeBinaryPath,
40984
- project,
40985
- onPartialFailure: reportPartialFailure,
40986
- onFileProgress: fileProgress,
40987
- spawnTimeoutMs,
40988
- outputMaxBytes,
40989
- concurrency: options.concurrency,
40990
- signal: options.signal,
40991
- deadlineEpochMs: options.deadlineEpochMs
40992
- });
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
+ };
40993
41098
  writeOxlintConfig(passConfigPath, buildConfigForPass({}));
40994
41099
  try {
40995
41100
  return {
40996
41101
  diagnostics: await spawnPass(),
41102
+ analyzedFiles,
40997
41103
  didDropReactHooksJsPlugin: false,
40998
41104
  hadPartialFailure
40999
41105
  };
@@ -41005,6 +41111,7 @@ const runOxlint = async (options) => {
41005
41111
  reportPartialFailure(reactHooksJsDropNote);
41006
41112
  return {
41007
41113
  diagnostics,
41114
+ analyzedFiles,
41008
41115
  didDropReactHooksJsPlugin: true,
41009
41116
  hadPartialFailure
41010
41117
  };
@@ -41080,6 +41187,7 @@ const runOxlint = async (options) => {
41080
41187
  const missFileSet = new Set(missFiles);
41081
41188
  const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
41082
41189
  const sidecarReplayedDiagnostics = [];
41190
+ const sidecarReplayedFiles = [];
41083
41191
  const sidecarLintFiles = [];
41084
41192
  if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
41085
41193
  else {
@@ -41087,8 +41195,10 @@ const runOxlint = async (options) => {
41087
41195
  for (const hitFile of hitFiles) {
41088
41196
  const cacheKey = cacheKeyByFile.get(hitFile);
41089
41197
  const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
41090
- if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
41091
- 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);
41092
41202
  if (performance.now() - verifySliceStartedAt >= 12) {
41093
41203
  await yieldToEventLoop();
41094
41204
  verifySliceStartedAt = performance.now();
@@ -41114,6 +41224,7 @@ const runOxlint = async (options) => {
41114
41224
  sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
41115
41225
  }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
41116
41226
  diagnostics: [],
41227
+ analyzedFiles: [],
41117
41228
  didDropReactHooksJsPlugin: false,
41118
41229
  hadPartialFailure: false
41119
41230
  };
@@ -41144,6 +41255,15 @@ const runOxlint = async (options) => {
41144
41255
  probesByFile: await missProbesTask
41145
41256
  }]
41146
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
+ });
41147
41267
  return dedupeDiagnostics([
41148
41268
  ...replayedDiagnostics,
41149
41269
  ...fullResult.diagnostics,
@@ -41154,6 +41274,7 @@ const runOxlint = async (options) => {
41154
41274
  }
41155
41275
  const baseArgs = makeBaseArgs(configPath);
41156
41276
  const fileBatches = buildFileBatches(baseArgs, candidateFiles);
41277
+ let analyzedFiles = [];
41157
41278
  const runBatches = () => spawnLintBatches({
41158
41279
  baseArgs,
41159
41280
  fileBatches,
@@ -41161,6 +41282,9 @@ const runOxlint = async (options) => {
41161
41282
  nodeBinaryPath,
41162
41283
  project,
41163
41284
  onPartialFailure,
41285
+ onAnalyzedFiles: (filePaths) => {
41286
+ analyzedFiles = filePaths;
41287
+ },
41164
41288
  onFileProgress: options.onFileProgress,
41165
41289
  spawnTimeoutMs,
41166
41290
  outputMaxBytes,
@@ -41170,7 +41294,12 @@ const runOxlint = async (options) => {
41170
41294
  });
41171
41295
  writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
41172
41296
  try {
41173
- return await runBatches();
41297
+ const diagnostics = await runBatches();
41298
+ onFileCoverage?.({
41299
+ candidateFiles,
41300
+ analyzedFiles
41301
+ });
41302
+ return diagnostics;
41174
41303
  } catch (error) {
41175
41304
  const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
41176
41305
  if (reactHooksJsDropNote !== null) {
@@ -41180,11 +41309,20 @@ const runOxlint = async (options) => {
41180
41309
  }));
41181
41310
  const diagnostics = await runBatches();
41182
41311
  onPartialFailure?.(reactHooksJsDropNote);
41312
+ onFileCoverage?.({
41313
+ candidateFiles,
41314
+ analyzedFiles
41315
+ });
41183
41316
  return diagnostics;
41184
41317
  }
41185
41318
  if (extendsPaths.length === 0) throw error;
41186
41319
  writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
41187
- return await runBatches();
41320
+ const diagnostics = await runBatches();
41321
+ onFileCoverage?.({
41322
+ candidateFiles,
41323
+ analyzedFiles
41324
+ });
41325
+ return diagnostics;
41188
41326
  }
41189
41327
  } finally {
41190
41328
  restoreDisableDirectives();
@@ -41264,6 +41402,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
41264
41402
  onPartialFailure: (reason) => {
41265
41403
  collectedFailures.push(reason);
41266
41404
  },
41405
+ onFileCoverage: input.onFileCoverage,
41267
41406
  onFileProgress: input.onFileProgress,
41268
41407
  perFileLintCacheEnabled,
41269
41408
  sidecarLintCacheEnabled,
@@ -41971,6 +42110,7 @@ const resolveGithubActionsScoreMetadata = (environment = process.env) => {
41971
42110
  ...actorAssociation !== void 0 ? { githubActorAssociation: actorAssociation } : {}
41972
42111
  };
41973
42112
  };
42113
+ const toNormalizedRelativePath = (filePath, rootDirectory) => Path.relative(Path.resolve(rootDirectory), Path.resolve(rootDirectory, filePath)).replaceAll("\\", "/") || ".";
41974
42114
  const NO_HOOKS = {
41975
42115
  beforeLint: () => void_,
41976
42116
  afterLint: () => void_
@@ -42069,7 +42209,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42069
42209
  repo
42070
42210
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
42071
42211
  const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
42072
- 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)) : [];
42073
42213
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
42074
42214
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
42075
42215
  yield* beforeLint(project, lintIncludePaths ?? void 0);
@@ -42174,6 +42314,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42174
42314
  let deadCodeCacheHit = null;
42175
42315
  let deadCodeSummaryCacheHits = null;
42176
42316
  let deadCodeSummaryCacheMisses = null;
42317
+ const lintFileCoverageState = { value: null };
42177
42318
  const baseLintStream = linterService.run({
42178
42319
  rootDirectory: scanDirectory,
42179
42320
  project,
@@ -42189,6 +42330,9 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42189
42330
  lastReportedTotalFileCount = totalFileCount;
42190
42331
  runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})${workerCountSuffix}...`));
42191
42332
  },
42333
+ onFileCoverage: (coverage) => {
42334
+ lintFileCoverageState.value = coverage;
42335
+ },
42192
42336
  onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
42193
42337
  lintCacheHitFileCount = cacheHitFileCount;
42194
42338
  lintCacheTotalFileCount = totalConsideredFileCount;
@@ -42219,7 +42363,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42219
42363
  const lintFailureState = yield* get$2(lintFailure);
42220
42364
  yield* afterLint(lintFailureState.didFail);
42221
42365
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
42222
- 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 : [];
42223
42370
  const scannedFilesLabel = `${totalFileCount} ${totalFileCount === 1 ? "file" : "files"}`;
42224
42371
  let deadCodeCollected = [];
42225
42372
  if (lintFailureState.didFail) {
@@ -42301,6 +42448,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42301
42448
  deadCodeOverlapped: shouldOverlapDeadCode,
42302
42449
  scannedFileCount: totalFileCount,
42303
42450
  scannedFilePaths,
42451
+ analyzedFiles,
42304
42452
  scanElapsedMilliseconds,
42305
42453
  scanConcurrency,
42306
42454
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
@@ -42552,7 +42700,7 @@ const buildJsonReportError = (input) => {
42552
42700
  sentryEventId
42553
42701
  };
42554
42702
  return {
42555
- schemaVersion: 1,
42703
+ schemaVersion: 3,
42556
42704
  version: input.version,
42557
42705
  ok: false,
42558
42706
  directory: input.directory,
@@ -42597,6 +42745,7 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
42597
42745
  * React-runtime rule family was gated off for the project.
42598
42746
  */
42599
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;
42600
42749
  const toJsonDiff = (diff) => {
42601
42750
  if (!diff) return null;
42602
42751
  return {
@@ -42619,21 +42768,68 @@ const findWorstScoredProject = (projects) => {
42619
42768
  }
42620
42769
  return worst;
42621
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
+ };
42622
42789
  const buildJsonReport = (input) => {
42623
- const projects = input.scans.map(({ directory, result }) => ({
42624
- directory,
42625
- project: result.project,
42626
- diagnostics: result.diagnostics,
42627
- score: result.score,
42628
- skippedChecks: result.skippedChecks,
42629
- ...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
42630
- ...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
42631
- elapsedMilliseconds: result.elapsedMilliseconds
42632
- }));
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
+ });
42633
42815
  const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
42634
42816
  const worstScoredProject = findWorstScoredProject(projects);
42635
- const summary = summarizeDiagnostics(flattenedDiagnostics, worstScoredProject?.score?.score ?? null, worstScoredProject?.score?.label ?? null);
42636
- 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 } : {},
42637
42833
  ...input.scans.length > 0 ? { reactDetected: input.scans.some((scan) => hasReactRuntime(scan.result.project)) } : {},
42638
42834
  version: input.version,
42639
42835
  ok: true,
@@ -42645,39 +42841,26 @@ const buildJsonReport = (input) => {
42645
42841
  elapsedMilliseconds: input.totalElapsedMilliseconds,
42646
42842
  error: null
42647
42843
  };
42648
- if (input.baseline) return {
42649
- schemaVersion: 2,
42650
- mode: "baseline",
42651
- baseline: {
42652
- baseRef: input.baseline.baseRef,
42653
- newCount: summary.totalDiagnosticCount,
42654
- fixedCount: input.baseline.fixedCount,
42655
- baseTotalCount: input.baseline.baseTotalCount
42656
- },
42657
- ...shared
42658
- };
42659
- return {
42660
- schemaVersion: 1,
42661
- mode: input.mode,
42662
- ...input.baselineDegraded ? { baselineDegraded: true } : {},
42663
- ...shared
42664
- };
42665
42844
  };
42666
42845
  /**
42667
42846
  * Single source of truth for the skipped-check accounting shared by the
42668
42847
  * CLI renderer (`react-doctor/src/inspect.ts → finalizeAndRender`) and the
42669
42848
  * programmatic shell (`@react-doctor/api → diagnose()`). Both surface a
42670
- * 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
42671
42850
  * branch logic lives here once.
42672
42851
  */
42673
42852
  const buildSkippedChecks = (input) => {
42674
42853
  const skippedChecks = [];
42675
42854
  if (input.didLintFail) skippedChecks.push("lint");
42676
42855
  if (input.didDeadCodeFail) skippedChecks.push("dead-code");
42856
+ if (input.supplyChainOverlapTimedOut) skippedChecks.push("supply-chain");
42857
+ if (input.securityScanFailed) skippedChecks.push("security-scan");
42677
42858
  const skippedCheckReasons = {};
42678
42859
  if (input.didLintFail && input.lintFailureReason !== null) skippedCheckReasons.lint = input.lintFailureReason;
42679
42860
  else if (input.lintPartialFailures.length > 0) skippedCheckReasons["lint:partial"] = input.lintPartialFailures.join("; ");
42680
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.";
42681
42864
  return {
42682
42865
  skippedChecks,
42683
42866
  skippedCheckReasons
@@ -42866,6 +43049,8 @@ const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
42866
43049
  score: output.score,
42867
43050
  skippedChecks,
42868
43051
  ...Object.keys(skippedCheckReasons).length > 0 ? { skippedCheckReasons } : {},
43052
+ analyzedFiles: output.analyzedFiles,
43053
+ scannedFileCount: output.scannedFileCount,
42869
43054
  project: output.project,
42870
43055
  reactDetected: hasReactRuntime(output.project),
42871
43056
  elapsedMilliseconds
@@ -42975,6 +43160,8 @@ const toJsonReport = (result, options) => buildJsonReport({
42975
43160
  score: result.score,
42976
43161
  skippedChecks: result.skippedChecks,
42977
43162
  ...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
43163
+ ...result.analyzedFiles ? { analyzedFiles: result.analyzedFiles } : {},
43164
+ ...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
42978
43165
  project: result.project,
42979
43166
  elapsedMilliseconds: result.elapsedMilliseconds
42980
43167
  }
@@ -42985,4 +43172,4 @@ const toJsonReport = (result, options) => buildJsonReport({
42985
43172
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
42986
43173
 
42987
43174
  //# sourceMappingURL=index.js.map
42988
- //# debugId=e5be91b0-fb63-5b7d-849e-60c4e4eb96b2
43175
+ //# debugId=95e70e55-12b7-5577-81f3-f5331db7ca53