react-doctor 0.7.4 → 0.7.5-dev.037bd56

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/lsp.js CHANGED
@@ -5,10 +5,10 @@ import * as OS from "node:os";
5
5
  import os from "node:os";
6
6
  import * as Path from "node:path";
7
7
  import path from "node:path";
8
+ 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";
8
9
  import * as NodeChildProcess from "node:child_process";
9
10
  import { execFileSync, spawn, spawnSync } from "node:child_process";
10
- import * as ts from "typescript";
11
- 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";
11
+ import ts from "typescript";
12
12
  import { parseJSON5 } from "confbox";
13
13
  import * as NodeUrl from "node:url";
14
14
  import { fileURLToPath } from "node:url";
@@ -19339,6 +19339,31 @@ var Diagnostic = class extends Class("Diagnostic")({
19339
19339
  relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
19340
19340
  fixGroupId: optional(String$1)
19341
19341
  }) {};
19342
+ var JsonReportDiagnosticV3 = class extends Class("JsonReportDiagnosticV3")({
19343
+ id: String$1,
19344
+ normalizedFilePath: String$1,
19345
+ filePath: String$1,
19346
+ plugin: String$1,
19347
+ rule: String$1,
19348
+ severity: Severity,
19349
+ tags: ArraySchema(String$1),
19350
+ title: optional(String$1),
19351
+ message: String$1,
19352
+ help: String$1,
19353
+ url: optional(String$1),
19354
+ line: Number$1,
19355
+ column: Number$1,
19356
+ offset: optional(Number$1),
19357
+ length: optional(Number$1),
19358
+ endLine: optional(Number$1),
19359
+ endColumn: optional(Number$1),
19360
+ category: String$1,
19361
+ matchByOccurrence: optional(Boolean$1),
19362
+ fileContext: optional(Literals(["test", "story"])),
19363
+ suppressionHint: optional(String$1),
19364
+ relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
19365
+ fixGroupId: optional(String$1)
19366
+ }) {};
19342
19367
  /**
19343
19368
  * Deterministic identity string for a diagnostic. Same diagnostic
19344
19369
  * across two scans yields the same identity; lets baselines,
@@ -19352,6 +19377,7 @@ const JsonReportMode = Literals([
19352
19377
  "staged",
19353
19378
  "baseline"
19354
19379
  ]);
19380
+ const Framework = Literals(FRAMEWORK_TOKENS);
19355
19381
  var JsonReportSummary = class extends Class("JsonReportSummary")({
19356
19382
  errorCount: Number$1,
19357
19383
  warningCount: Number$1,
@@ -19389,13 +19415,22 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
19389
19415
  scannedFileCount: optional(Number$1),
19390
19416
  elapsedMilliseconds: Number$1
19391
19417
  }) {};
19392
- /**
19393
- * Versioned JsonReport schema. `JsonReport` is a `Schema.Union` so we
19394
- * can add `schemaVersion: 2` later as one new union member without
19395
- * breaking existing v1 consumers (the GitHub Action keys off the
19396
- * version literal). Today's union is single-arm; the shape is
19397
- * intentional.
19398
- */
19418
+ var JsonReportProjectEntryV3 = class extends Class("JsonReportProjectEntryV3")({
19419
+ directory: String$1,
19420
+ packageRoot: String$1,
19421
+ framework: Framework,
19422
+ project: Unknown,
19423
+ diagnostics: ArraySchema(JsonReportDiagnosticV3),
19424
+ score: Unknown,
19425
+ skippedChecks: ArraySchema(String$1),
19426
+ skippedCheckReasons: optional(Record$1(String$1, String$1)),
19427
+ analyzedFiles: ArraySchema(String$1),
19428
+ analyzedFileCount: Number$1,
19429
+ complete: Boolean$1,
19430
+ scannedFileCount: optional(Number$1),
19431
+ elapsedMilliseconds: Number$1
19432
+ }) {};
19433
+ /** Original full, diff, and staged report contract. */
19399
19434
  var JsonReportV1 = class extends Class("JsonReportV1")({
19400
19435
  schemaVersion: Literal(1),
19401
19436
  version: String$1,
@@ -19456,7 +19491,27 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
19456
19491
  elapsedMilliseconds: Number$1,
19457
19492
  error: NullOr(JsonReportError)
19458
19493
  }) {};
19459
- Union([JsonReportV1, JsonReportV2]);
19494
+ var JsonReportV3 = class extends Class("JsonReportV3")({
19495
+ schemaVersion: Literal(3),
19496
+ version: String$1,
19497
+ ok: Boolean$1,
19498
+ directory: String$1,
19499
+ mode: JsonReportMode,
19500
+ baselineDegraded: optional(Boolean$1),
19501
+ reactDetected: optional(Boolean$1),
19502
+ diff: NullOr(JsonReportDiffInfo),
19503
+ baseline: optional(JsonReportBaseline),
19504
+ projects: ArraySchema(JsonReportProjectEntryV3),
19505
+ diagnostics: ArraySchema(JsonReportDiagnosticV3),
19506
+ summary: JsonReportSummary,
19507
+ elapsedMilliseconds: Number$1,
19508
+ error: NullOr(JsonReportError)
19509
+ }) {};
19510
+ Union([
19511
+ JsonReportV1,
19512
+ JsonReportV2,
19513
+ JsonReportV3
19514
+ ]);
19460
19515
  //#endregion
19461
19516
  //#region ../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/constants.js
19462
19517
  var require_constants$1 = /* @__PURE__ */ __commonJSMin$1(((exports, module) => {
@@ -33037,7 +33092,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
33037
33092
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
33038
33093
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
33039
33094
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
33040
- const CORE_PACKAGE_VERSION = "0.7.4";
33095
+ const CORE_PACKAGE_VERSION = "0.7.5";
33041
33096
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
33042
33097
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
33043
33098
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -33949,6 +34004,22 @@ const isPackageJsonReanimatedAware = (packageJson) => {
33949
34004
  };
33950
34005
  return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
33951
34006
  };
34007
+ const SSR_DEPENDENCY_NAMES = new Set([
34008
+ "@react-router/cloudflare",
34009
+ "@react-router/node",
34010
+ "@react-router/serve",
34011
+ "vike",
34012
+ "vite-plugin-ssr"
34013
+ ]);
34014
+ const isPackageJsonSsrAware = (packageJson) => {
34015
+ const allDependencies = {
34016
+ ...packageJson.peerDependencies,
34017
+ ...packageJson.dependencies,
34018
+ ...packageJson.devDependencies,
34019
+ ...packageJson.optionalDependencies
34020
+ };
34021
+ return Object.keys(allDependencies).some((packageName) => SSR_DEPENDENCY_NAMES.has(packageName));
34022
+ };
33952
34023
  const getWorkspacePatterns = (rootDirectory, packageJson) => {
33953
34024
  const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
33954
34025
  if (pnpmPatterns.length > 0) return pnpmPatterns;
@@ -34089,6 +34160,7 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
34089
34160
  }
34090
34161
  facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34091
34162
  facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34163
+ facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
34092
34164
  };
34093
34165
  const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34094
34166
  const facts = {
@@ -34110,6 +34182,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34110
34182
  },
34111
34183
  hasReactNativeAwarePackage: false,
34112
34184
  hasReanimatedAwarePackage: false,
34185
+ hasSsrDependency: false,
34113
34186
  reanimatedVersion: null
34114
34187
  };
34115
34188
  evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
@@ -34349,6 +34422,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
34349
34422
  hasTypeScript: hasOwnTsConfig,
34350
34423
  hasReactCompiler: false,
34351
34424
  hasTanStackQuery: false,
34425
+ hasSsrDependency: false,
34352
34426
  preactVersion: null,
34353
34427
  preactMajorVersion: null,
34354
34428
  hasReactNativeWorkspace: false,
@@ -34468,6 +34542,7 @@ const discoverProject = (directory) => {
34468
34542
  hasTypeScript,
34469
34543
  hasReactCompiler: detectReactCompiler(directory, packageJson),
34470
34544
  hasTanStackQuery: hasTanStackQuery(packageJson),
34545
+ hasSsrDependency: workspaceFacts.hasSsrDependency,
34471
34546
  preactVersion,
34472
34547
  preactMajorVersion: parseReactMajor(preactVersion),
34473
34548
  hasReactNativeWorkspace,
@@ -35543,6 +35618,14 @@ var AmbiguousProject = class extends TaggedErrorClass()("AmbiguousProject", {
35543
35618
  return `Ambiguous project at ${this.directory}: found ${this.candidates.length} candidates (${this.candidates.join(", ")})`;
35544
35619
  }
35545
35620
  };
35621
+ var ProjectDiscoveryFailed = class extends TaggedErrorClass()("ProjectDiscoveryFailed", {
35622
+ directory: String$1,
35623
+ cause: Unknown
35624
+ }) {
35625
+ get message() {
35626
+ return `Project discovery failed for ${this.directory}: ${pretty(fail$6(this.cause))}`;
35627
+ }
35628
+ };
35546
35629
  var DeadCodeAnalysisFailed = class extends TaggedErrorClass()("DeadCodeAnalysisFailed", { cause: Unknown }) {
35547
35630
  get message() {
35548
35631
  return `Dead-code analysis failed: ${pretty(fail$6(this.cause))}`;
@@ -35577,6 +35660,7 @@ const ReactDoctorErrorReason = Union([
35577
35660
  ProjectNotFound,
35578
35661
  NoReactDependency,
35579
35662
  AmbiguousProject,
35663
+ ProjectDiscoveryFailed,
35580
35664
  DeadCodeAnalysisFailed,
35581
35665
  GitInvocationFailed,
35582
35666
  GitBaseBranchMissing,
@@ -37203,6 +37287,12 @@ const CLIENT_ONLY_FRAMEWORKS = new Set([
37203
37287
  "react-native",
37204
37288
  "expo"
37205
37289
  ]);
37290
+ const SSR_FRAMEWORKS = new Set([
37291
+ "nextjs",
37292
+ "remix",
37293
+ "gatsby",
37294
+ "tanstack-start"
37295
+ ]);
37206
37296
  const addMajorLadder = (capabilities, name, major, earliest, latest) => {
37207
37297
  if (major === null) return;
37208
37298
  const cappedMajor = Math.min(major, latest);
@@ -37215,6 +37305,7 @@ const buildCapabilities = (project) => {
37215
37305
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
37216
37306
  if (project.expoVersion !== null) capabilities.add("expo");
37217
37307
  if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
37308
+ if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
37218
37309
  if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
37219
37310
  if (project.isStaticExport) capabilities.add("nextjs:static-export");
37220
37311
  if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
@@ -39012,7 +39103,7 @@ const normalizeConfigForHash = (config) => {
39012
39103
  if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
39013
39104
  return clone;
39014
39105
  };
39015
- 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");
39106
+ 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");
39016
39107
  /**
39017
39108
  * Loads a plugin module via the local require resolver and extracts
39018
39109
  * `(name, ruleNames)` from either `module.exports.meta + rules` or
@@ -39048,7 +39139,7 @@ const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
39048
39139
  } catch {
39049
39140
  return null;
39050
39141
  }
39051
- const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire$1(spec));
39142
+ const { ruleNames } = readPluginShape(pluginSpecifier, (specifier) => bundledRequire$1(specifier));
39052
39143
  return {
39053
39144
  entry: {
39054
39145
  name: "react-hooks-js",
@@ -39456,6 +39547,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
39456
39547
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
39457
39548
  return null;
39458
39549
  };
39550
+ const DERIVED_STATE_RULE_PRIORITY = new Map([
39551
+ ["no-initialize-state", 0],
39552
+ ["no-adjust-state-on-prop-change", 1],
39553
+ ["no-derived-state", 2],
39554
+ ["no-derived-state-effect", 3]
39555
+ ]);
39556
+ const doDiagnosticSpansOverlap = (first, second) => {
39557
+ if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
39558
+ if (first.offset !== void 0 && second.offset !== void 0) {
39559
+ const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
39560
+ const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
39561
+ return first.offset < secondEnd && second.offset < firstEnd;
39562
+ }
39563
+ const firstEndLine = first.endLine ?? first.line;
39564
+ const secondEndLine = second.endLine ?? second.line;
39565
+ return first.line <= secondEndLine && second.line <= firstEndLine;
39566
+ };
39459
39567
  const dedupeDiagnostics = (diagnostics) => {
39460
39568
  const seenKeys = /* @__PURE__ */ new Set();
39461
39569
  const uniqueDiagnostics = [];
@@ -39463,6 +39571,16 @@ const dedupeDiagnostics = (diagnostics) => {
39463
39571
  const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
39464
39572
  if (seenKeys.has(key)) continue;
39465
39573
  seenKeys.add(key);
39574
+ const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
39575
+ if (priority !== void 0) {
39576
+ const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
39577
+ if (overlappingDiagnosticIndex >= 0) {
39578
+ const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
39579
+ const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
39580
+ if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
39581
+ continue;
39582
+ }
39583
+ }
39466
39584
  uniqueDiagnostics.push(diagnostic);
39467
39585
  }
39468
39586
  return uniqueDiagnostics;
@@ -40336,6 +40454,11 @@ const buildOxlintChildEnv = (sourceEnv) => {
40336
40454
  if (!isCompileCacheDisabled && !isCompileCacheAlreadySet) childEnv.NODE_COMPILE_CACHE = Path.join(os.tmpdir(), NODE_COMPILE_CACHE_DIR_NAME);
40337
40455
  return childEnv;
40338
40456
  };
40457
+ const buildProfiledNodeArguments = (input) => [
40458
+ ...input.cpuProfileDirectory ? ["--cpu-prof", `--cpu-prof-dir=${input.cpuProfileDirectory}`] : [],
40459
+ ...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
40460
+ ...input.argumentsList
40461
+ ];
40339
40462
  const SANITIZED_ENV = buildOxlintChildEnv(process.env);
40340
40463
  /**
40341
40464
  * Spawn one oxlint subprocess with hard ceilings on wall time and
@@ -40360,7 +40483,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
40360
40483
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
40361
40484
  return;
40362
40485
  }
40363
- const child = spawn(nodeBinaryPath, args, {
40486
+ const child = spawn(nodeBinaryPath, buildProfiledNodeArguments({
40487
+ argumentsList: args,
40488
+ cpuProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_CPU_PROFILE_DIR,
40489
+ heapProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_HEAP_PROFILE_DIR
40490
+ }), {
40364
40491
  cwd: rootDirectory,
40365
40492
  env: SANITIZED_ENV,
40366
40493
  stdio: [
@@ -40619,6 +40746,8 @@ const spawnLintBatches = async (input) => {
40619
40746
  const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
40620
40747
  reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
40621
40748
  reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
40749
+ const unavailableFiles = new Set([...droppedFiles, ...outcome.deadlineSkippedFiles]);
40750
+ input.onAnalyzedFiles?.([...new Set(fileBatches.flat().filter((filePath) => !unavailableFiles.has(filePath)))].sort());
40622
40751
  return dedupeDiagnostics(diagnostics);
40623
40752
  };
40624
40753
  let didValidate = false;
@@ -40875,12 +41004,18 @@ const reactHooksJsPluginDropNote = (error) => {
40875
41004
  * 6. always restore disable directives + clean up the temp dir
40876
41005
  */
40877
41006
  const runOxlint = async (options) => {
40878
- 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;
41007
+ 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;
40879
41008
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40880
41009
  const severityControls = buildRuleSeverityControls(userConfig);
40881
41010
  validateRuleRegistration();
40882
41011
  resetManifestCaches();
40883
- if (includePaths !== void 0 && includePaths.length === 0) return [];
41012
+ if (includePaths !== void 0 && includePaths.length === 0) {
41013
+ onFileCoverage?.({
41014
+ candidateFiles: [],
41015
+ analyzedFiles: []
41016
+ });
41017
+ return [];
41018
+ }
40884
41019
  const pluginPath = resolvePluginPath();
40885
41020
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
40886
41021
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
@@ -40941,6 +41076,7 @@ const runOxlint = async (options) => {
40941
41076
  const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
40942
41077
  if (files.length === 0) return {
40943
41078
  diagnostics: [],
41079
+ analyzedFiles: [],
40944
41080
  didDropReactHooksJsPlugin: false,
40945
41081
  hadPartialFailure: false
40946
41082
  };
@@ -40952,24 +41088,32 @@ const runOxlint = async (options) => {
40952
41088
  const passConfigPath = Path.join(configDirectory, configFileName);
40953
41089
  const passBaseArgs = makeBaseArgs(passConfigPath);
40954
41090
  const passFileBatches = buildFileBatches(passBaseArgs, files);
40955
- const spawnPass = () => spawnLintBatches({
40956
- baseArgs: passBaseArgs,
40957
- fileBatches: passFileBatches,
40958
- rootDirectory,
40959
- nodeBinaryPath,
40960
- project,
40961
- onPartialFailure: reportPartialFailure,
40962
- onFileProgress: fileProgress,
40963
- spawnTimeoutMs,
40964
- outputMaxBytes,
40965
- concurrency: options.concurrency,
40966
- signal: options.signal,
40967
- deadlineEpochMs: options.deadlineEpochMs
40968
- });
41091
+ let analyzedFiles = [];
41092
+ const spawnPass = () => {
41093
+ analyzedFiles = [];
41094
+ return spawnLintBatches({
41095
+ baseArgs: passBaseArgs,
41096
+ fileBatches: passFileBatches,
41097
+ rootDirectory,
41098
+ nodeBinaryPath,
41099
+ project,
41100
+ onPartialFailure: reportPartialFailure,
41101
+ onAnalyzedFiles: (filePaths) => {
41102
+ analyzedFiles = filePaths;
41103
+ },
41104
+ onFileProgress: fileProgress,
41105
+ spawnTimeoutMs,
41106
+ outputMaxBytes,
41107
+ concurrency: options.concurrency,
41108
+ signal: options.signal,
41109
+ deadlineEpochMs: options.deadlineEpochMs
41110
+ });
41111
+ };
40969
41112
  writeOxlintConfig(passConfigPath, buildConfigForPass({}));
40970
41113
  try {
40971
41114
  return {
40972
41115
  diagnostics: await spawnPass(),
41116
+ analyzedFiles,
40973
41117
  didDropReactHooksJsPlugin: false,
40974
41118
  hadPartialFailure
40975
41119
  };
@@ -40981,12 +41125,13 @@ const runOxlint = async (options) => {
40981
41125
  reportPartialFailure(reactHooksJsDropNote);
40982
41126
  return {
40983
41127
  diagnostics,
41128
+ analyzedFiles,
40984
41129
  didDropReactHooksJsPlugin: true,
40985
41130
  hadPartialFailure
40986
41131
  };
40987
41132
  }
40988
41133
  };
40989
- if (perFileLintCacheEnabled && respectInlineDisables && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
41134
+ if (perFileLintCacheEnabled && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
40990
41135
  const rulesetHash = computeRulesetHash({
40991
41136
  config: buildConfig({
40992
41137
  extendsPaths: [],
@@ -40994,7 +41139,8 @@ const runOxlint = async (options) => {
40994
41139
  }),
40995
41140
  toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
40996
41141
  ignorePatterns: combinedPatterns,
40997
- tsconfigContent
41142
+ tsconfigContent,
41143
+ respectInlineDisables
40998
41144
  });
40999
41145
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
41000
41146
  const cacheKeyByFile = /* @__PURE__ */ new Map();
@@ -41037,7 +41183,8 @@ const runOxlint = async (options) => {
41037
41183
  }),
41038
41184
  toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
41039
41185
  ignorePatterns: combinedPatterns,
41040
- tsconfigContent
41186
+ tsconfigContent,
41187
+ respectInlineDisables
41041
41188
  })) : null;
41042
41189
  const probeAnswers = createSidecarProbeAnswerResolver({
41043
41190
  rootDirectory,
@@ -41056,6 +41203,7 @@ const runOxlint = async (options) => {
41056
41203
  const missFileSet = new Set(missFiles);
41057
41204
  const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
41058
41205
  const sidecarReplayedDiagnostics = [];
41206
+ const sidecarReplayedFiles = [];
41059
41207
  const sidecarLintFiles = [];
41060
41208
  if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
41061
41209
  else {
@@ -41063,8 +41211,10 @@ const runOxlint = async (options) => {
41063
41211
  for (const hitFile of hitFiles) {
41064
41212
  const cacheKey = cacheKeyByFile.get(hitFile);
41065
41213
  const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
41066
- if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
41067
- else sidecarLintFiles.push(hitFile);
41214
+ if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) {
41215
+ sidecarReplayedFiles.push(hitFile);
41216
+ sidecarReplayedDiagnostics.push(...entry.diagnostics);
41217
+ } else sidecarLintFiles.push(hitFile);
41068
41218
  if (performance.now() - verifySliceStartedAt >= 12) {
41069
41219
  await yieldToEventLoop();
41070
41220
  verifySliceStartedAt = performance.now();
@@ -41090,6 +41240,7 @@ const runOxlint = async (options) => {
41090
41240
  sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
41091
41241
  }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
41092
41242
  diagnostics: [],
41243
+ analyzedFiles: [],
41093
41244
  didDropReactHooksJsPlugin: false,
41094
41245
  hadPartialFailure: false
41095
41246
  };
@@ -41120,6 +41271,15 @@ const runOxlint = async (options) => {
41120
41271
  probesByFile: await missProbesTask
41121
41272
  }]
41122
41273
  });
41274
+ const analyzedFileSet = new Set(fullResult.analyzedFiles);
41275
+ const boundedSidecarAnalyzedFileSet = new Set(boundedSidecarResult.analyzedFiles);
41276
+ const unboundedSidecarAnalyzedFileSet = new Set(unboundedSidecarResult.analyzedFiles);
41277
+ const sidecarReplayedFileSet = new Set(sidecarReplayedFiles);
41278
+ 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);
41279
+ onFileCoverage?.({
41280
+ candidateFiles,
41281
+ analyzedFiles: candidateFiles.filter((candidateFile) => analyzedFileSet.has(candidateFile))
41282
+ });
41123
41283
  return dedupeDiagnostics([
41124
41284
  ...replayedDiagnostics,
41125
41285
  ...fullResult.diagnostics,
@@ -41130,6 +41290,7 @@ const runOxlint = async (options) => {
41130
41290
  }
41131
41291
  const baseArgs = makeBaseArgs(configPath);
41132
41292
  const fileBatches = buildFileBatches(baseArgs, candidateFiles);
41293
+ let analyzedFiles = [];
41133
41294
  const runBatches = () => spawnLintBatches({
41134
41295
  baseArgs,
41135
41296
  fileBatches,
@@ -41137,6 +41298,9 @@ const runOxlint = async (options) => {
41137
41298
  nodeBinaryPath,
41138
41299
  project,
41139
41300
  onPartialFailure,
41301
+ onAnalyzedFiles: (filePaths) => {
41302
+ analyzedFiles = filePaths;
41303
+ },
41140
41304
  onFileProgress: options.onFileProgress,
41141
41305
  spawnTimeoutMs,
41142
41306
  outputMaxBytes,
@@ -41146,7 +41310,12 @@ const runOxlint = async (options) => {
41146
41310
  });
41147
41311
  writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
41148
41312
  try {
41149
- return await runBatches();
41313
+ const diagnostics = await runBatches();
41314
+ onFileCoverage?.({
41315
+ candidateFiles,
41316
+ analyzedFiles
41317
+ });
41318
+ return diagnostics;
41150
41319
  } catch (error) {
41151
41320
  const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
41152
41321
  if (reactHooksJsDropNote !== null) {
@@ -41156,11 +41325,20 @@ const runOxlint = async (options) => {
41156
41325
  }));
41157
41326
  const diagnostics = await runBatches();
41158
41327
  onPartialFailure?.(reactHooksJsDropNote);
41328
+ onFileCoverage?.({
41329
+ candidateFiles,
41330
+ analyzedFiles
41331
+ });
41159
41332
  return diagnostics;
41160
41333
  }
41161
41334
  if (extendsPaths.length === 0) throw error;
41162
41335
  writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
41163
- return await runBatches();
41336
+ const diagnostics = await runBatches();
41337
+ onFileCoverage?.({
41338
+ candidateFiles,
41339
+ analyzedFiles
41340
+ });
41341
+ return diagnostics;
41164
41342
  }
41165
41343
  } finally {
41166
41344
  restoreDisableDirectives();
@@ -41240,6 +41418,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
41240
41418
  onPartialFailure: (reason) => {
41241
41419
  collectedFailures.push(reason);
41242
41420
  },
41421
+ onFileCoverage: input.onFileCoverage,
41243
41422
  onFileProgress: input.onFileProgress,
41244
41423
  perFileLintCacheEnabled,
41245
41424
  sidecarLintCacheEnabled,
@@ -41332,7 +41511,10 @@ const translateProjectInfoError = (cause, directory) => {
41332
41511
  directory: cause.directory,
41333
41512
  candidates: cause.candidates
41334
41513
  }) });
41335
- return new ReactDoctorError({ reason: new ProjectNotFound({ directory }) });
41514
+ return new ReactDoctorError({ reason: new ProjectDiscoveryFailed({
41515
+ directory,
41516
+ cause
41517
+ }) });
41336
41518
  };
41337
41519
  var Project = class Project extends Service()("react-doctor/Project") {
41338
41520
  static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (directory) {
@@ -41944,6 +42126,7 @@ const resolveGithubActionsScoreMetadata = (environment = process.env) => {
41944
42126
  ...actorAssociation !== void 0 ? { githubActorAssociation: actorAssociation } : {}
41945
42127
  };
41946
42128
  };
42129
+ const toNormalizedRelativePath = (filePath, rootDirectory) => Path.relative(Path.resolve(rootDirectory), Path.resolve(rootDirectory, filePath)).replaceAll("\\", "/") || ".";
41947
42130
  const NO_HOOKS = {
41948
42131
  beforeLint: () => void_,
41949
42132
  afterLint: () => void_
@@ -42042,7 +42225,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42042
42225
  repo
42043
42226
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
42044
42227
  const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
42045
- const scannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
42228
+ const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
42046
42229
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
42047
42230
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
42048
42231
  yield* beforeLint(project, lintIncludePaths ?? void 0);
@@ -42147,6 +42330,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42147
42330
  let deadCodeCacheHit = null;
42148
42331
  let deadCodeSummaryCacheHits = null;
42149
42332
  let deadCodeSummaryCacheMisses = null;
42333
+ const lintFileCoverageState = { value: null };
42150
42334
  const baseLintStream = linterService.run({
42151
42335
  rootDirectory: scanDirectory,
42152
42336
  project,
@@ -42162,6 +42346,9 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42162
42346
  lastReportedTotalFileCount = totalFileCount;
42163
42347
  runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})${workerCountSuffix}...`));
42164
42348
  },
42349
+ onFileCoverage: (coverage) => {
42350
+ lintFileCoverageState.value = coverage;
42351
+ },
42165
42352
  onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
42166
42353
  lintCacheHitFileCount = cacheHitFileCount;
42167
42354
  lintCacheTotalFileCount = totalConsideredFileCount;
@@ -42192,7 +42379,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42192
42379
  const lintFailureState = yield* get$2(lintFailure);
42193
42380
  yield* afterLint(lintFailureState.didFail);
42194
42381
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
42195
- const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
42382
+ const candidateFiles = lintFileCoverageState.value === null ? [] : [...new Set(lintFileCoverageState.value.candidateFiles.map((filePath) => toNormalizedRelativePath(filePath, scanDirectory)))];
42383
+ const analyzedFiles = lintFileCoverageState.value === null ? [] : [...new Set(lintFileCoverageState.value.analyzedFiles.map((filePath) => toNormalizedRelativePath(filePath, scanDirectory)))].sort();
42384
+ const totalFileCount = candidateFiles.length || lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
42385
+ const scannedFilePaths = input.suppressScanSummary ? candidateFiles.length > 0 ? candidateFiles.map((filePath) => Path.resolve(scanDirectory, filePath)) : fallbackScannedFilePaths : [];
42196
42386
  const scannedFilesLabel = `${totalFileCount} ${totalFileCount === 1 ? "file" : "files"}`;
42197
42387
  let deadCodeCollected = [];
42198
42388
  if (lintFailureState.didFail) {
@@ -42274,6 +42464,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42274
42464
  deadCodeOverlapped: shouldOverlapDeadCode,
42275
42465
  scannedFileCount: totalFileCount,
42276
42466
  scannedFilePaths,
42467
+ analyzedFiles,
42277
42468
  scanElapsedMilliseconds,
42278
42469
  scanConcurrency,
42279
42470
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
@@ -42612,7 +42803,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
42612
42803
  /** Display name used in client-facing messages and progress titles. */
42613
42804
  const SERVER_DISPLAY_NAME = "React Doctor";
42614
42805
  /** Server version reported in `serverInfo`; injected at build, `dev` from source. */
42615
- const SERVER_VERSION = "0.7.4";
42806
+ const SERVER_VERSION = "0.7.5";
42616
42807
  /** `Diagnostic.source` shown next to every published diagnostic. */
42617
42808
  const DIAGNOSTIC_SOURCE = "react-doctor";
42618
42809
  /**
@@ -44663,5 +44854,5 @@ const startLanguageServer = () => {
44663
44854
  };
44664
44855
  //#endregion
44665
44856
  export { startLanguageServer };
44666
- !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]="bc30542b-b95d-5630-bd67-52e3d1367e54")}catch(e){}}();
44667
- //# debugId=bc30542b-b95d-5630-bd67-52e3d1367e54
44857
+ !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]="172df417-33ac-5881-af14-5df32c7af09c")}catch(e){}}();
44858
+ //# debugId=172df417-33ac-5881-af14-5df32c7af09c
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.7.4",
3
+ "version": "0.7.5-dev.037bd56",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -65,8 +65,8 @@
65
65
  "vscode-languageserver-textdocument": "^1.0.12",
66
66
  "vscode-uri": "^3.1.0",
67
67
  "yaml": "^2.9.0",
68
- "oxlint-plugin-react-doctor": "0.7.4",
69
- "deslop-js": "0.7.4"
68
+ "oxlint-plugin-react-doctor": "0.7.5-dev.037bd56",
69
+ "deslop-js": "0.7.5"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/babel__code-frame": "^7.27.0",
@@ -74,9 +74,9 @@
74
74
  "@xterm/headless": "^6.0.0",
75
75
  "commander": "^14.0.3",
76
76
  "ora": "^9.4.0",
77
- "@react-doctor/core": "0.7.4",
78
- "@react-doctor/api": "0.7.4",
79
- "@react-doctor/language-server": "0.7.4"
77
+ "@react-doctor/api": "0.7.5",
78
+ "@react-doctor/language-server": "0.7.5",
79
+ "@react-doctor/core": "0.7.5"
80
80
  },
81
81
  "engines": {
82
82
  "node": "^20.19.0 || >=22.13.0"