react-doctor 0.7.6-dev.3f22fe3 → 0.7.6-dev.47beb25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4c1e681e-4dee-5ffd-a4ba-69b876f47147")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5dcb5b0b-ca1e-5655-8f52-3c22b421256d")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -39594,49 +39594,6 @@ const assignFixGroups = (diagnostics) => {
39594
39594
  };
39595
39595
  });
39596
39596
  };
39597
- const DERIVED_STATE_RULE_PRIORITY = new Map([
39598
- ["no-initialize-state", 0],
39599
- ["no-adjust-state-on-prop-change", 1],
39600
- ["no-derived-state", 2],
39601
- ["no-derived-state-effect", 3]
39602
- ]);
39603
- const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
39604
- const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
39605
- const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
39606
- const doDiagnosticSpansOverlap = (first, second) => {
39607
- if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
39608
- if (first.offset !== void 0 && second.offset !== void 0) {
39609
- const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
39610
- const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
39611
- return first.offset < secondEnd && second.offset < firstEnd;
39612
- }
39613
- const firstEndLine = first.endLine ?? first.line;
39614
- const secondEndLine = second.endLine ?? second.line;
39615
- return first.line <= secondEndLine && second.line <= firstEndLine;
39616
- };
39617
- const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
39618
- ...survivingDiagnostic,
39619
- severity: "error"
39620
- } : survivingDiagnostic;
39621
- const dedupeRelatedDiagnostics = (diagnostics) => {
39622
- const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
39623
- const uniqueDiagnostics = [];
39624
- for (const diagnostic of diagnostics) {
39625
- if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
39626
- const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
39627
- if (priority !== void 0) {
39628
- const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
39629
- if (overlappingDiagnosticIndex >= 0) {
39630
- const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
39631
- const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
39632
- if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
39633
- continue;
39634
- }
39635
- }
39636
- uniqueDiagnostics.push(diagnostic);
39637
- }
39638
- return uniqueDiagnostics;
39639
- };
39640
39597
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
39641
39598
  const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
39642
39599
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -42778,13 +42735,13 @@ const encodeSidecarFileEntry = (entry) => ({
42778
42735
  diagnostics: entry.diagnostics
42779
42736
  });
42780
42737
  const emptyCache = () => ({
42781
- version: 2,
42738
+ version: 1,
42782
42739
  rulesets: {}
42783
42740
  });
42784
42741
  const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
42785
42742
  const entries = /* @__PURE__ */ new Map();
42786
42743
  const persisted = failOpenReadJson(cacheFilePath, emptyCache());
42787
- if (persisted.version !== 2 || !isRecord$1(persisted.rulesets)) return entries;
42744
+ if (persisted.version !== 1 || !isRecord$1(persisted.rulesets)) return entries;
42788
42745
  const bucket = persisted.rulesets[rulesetHash];
42789
42746
  if (!isRecord$1(bucket) || !isRecord$1(bucket.files)) return entries;
42790
42747
  for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
@@ -42804,7 +42761,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
42804
42761
  },
42805
42762
  persist: () => {
42806
42763
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
42807
- const rulesets = onDisk.version === 2 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
42764
+ const rulesets = onDisk.version === 1 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
42808
42765
  const existingBucket = rulesets[rulesetHash];
42809
42766
  const existingFiles = isRecord$1(existingBucket) && isRecord$1(existingBucket.files) ? existingBucket.files : {};
42810
42767
  const ourFiles = {};
@@ -42818,7 +42775,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
42818
42775
  files: Object.fromEntries(cappedEntries)
42819
42776
  };
42820
42777
  atomicWriteJson(cacheFilePath, {
42821
- version: 2,
42778
+ version: 1,
42822
42779
  rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord$1(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
42823
42780
  });
42824
42781
  }
@@ -42869,6 +42826,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
42869
42826
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
42870
42827
  return null;
42871
42828
  };
42829
+ const DERIVED_STATE_RULE_PRIORITY = new Map([
42830
+ ["no-initialize-state", 0],
42831
+ ["no-adjust-state-on-prop-change", 1],
42832
+ ["no-derived-state", 2],
42833
+ ["no-derived-state-effect", 3]
42834
+ ]);
42835
+ const doDiagnosticSpansOverlap = (first, second) => {
42836
+ if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
42837
+ if (first.offset !== void 0 && second.offset !== void 0) {
42838
+ const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
42839
+ const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
42840
+ return first.offset < secondEnd && second.offset < firstEnd;
42841
+ }
42842
+ const firstEndLine = first.endLine ?? first.line;
42843
+ const secondEndLine = second.endLine ?? second.line;
42844
+ return first.line <= secondEndLine && second.line <= firstEndLine;
42845
+ };
42872
42846
  const dedupeDiagnostics = (diagnostics) => {
42873
42847
  const seenKeys = /* @__PURE__ */ new Set();
42874
42848
  const uniqueDiagnostics = [];
@@ -42876,6 +42850,16 @@ const dedupeDiagnostics = (diagnostics) => {
42876
42850
  const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
42877
42851
  if (seenKeys.has(key)) continue;
42878
42852
  seenKeys.add(key);
42853
+ const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
42854
+ if (priority !== void 0) {
42855
+ const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
42856
+ if (overlappingDiagnosticIndex >= 0) {
42857
+ const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
42858
+ const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
42859
+ if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
42860
+ continue;
42861
+ }
42862
+ }
42879
42863
  uniqueDiagnostics.push(diagnostic);
42880
42864
  }
42881
42865
  return uniqueDiagnostics;
@@ -43675,11 +43659,11 @@ const buildRelatedLocations = (labels, filePath) => {
43675
43659
  }
43676
43660
  return related;
43677
43661
  };
43678
- const isOxlintSpan = (value) => isRecord$1(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
43679
- const isOxlintLabel = (value) => isRecord$1(value) && isOxlintSpan(value.span);
43680
- const isMappableOxlintDiagnostic = (value) => isRecord$1(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
43681
- const isEngineFailureDiagnostic = (value) => !isRecord$1(value) || typeof value.filename !== "string" || value.filename.length === 0;
43682
- const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagnostics);
43662
+ const isOxlintOutput = (value) => {
43663
+ if (typeof value !== "object" || value === null) return false;
43664
+ const candidate = value;
43665
+ return Array.isArray(candidate.diagnostics);
43666
+ };
43683
43667
  /**
43684
43668
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
43685
43669
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -43698,8 +43682,6 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43698
43682
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43699
43683
  }
43700
43684
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43701
- const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
43702
- if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
43703
43685
  const minifiedFileCache = /* @__PURE__ */ new Map();
43704
43686
  const sourceBufferCache = /* @__PURE__ */ new Map();
43705
43687
  const sourceLinesCache = /* @__PURE__ */ new Map();
@@ -43734,7 +43716,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43734
43716
  minifiedFileCache.set(absolutePath, minified);
43735
43717
  return minified;
43736
43718
  };
43737
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43719
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43738
43720
  const { plugin, rule } = parseRuleCode(diagnostic.code);
43739
43721
  const primaryLabel = diagnostic.labels[0];
43740
43722
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
@@ -45569,8 +45551,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45569
45551
  respectInlineDisables: input.respectInlineDisables,
45570
45552
  showWarnings
45571
45553
  });
45572
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
45573
- const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
45554
+ const applyPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)), tap((diagnostic) => reporterService.emit(diagnostic)));
45574
45555
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
45575
45556
  ...checkReducedMotion(scanDirectory),
45576
45557
  ...checkPnpmHardening(scanDirectory),
@@ -45699,7 +45680,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45699
45680
  });
45700
45681
  return empty$4;
45701
45682
  }))));
45702
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
45683
+ const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
45703
45684
  onNone: () => set(lintFailure, {
45704
45685
  didFail: true,
45705
45686
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -45707,8 +45688,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45707
45688
  reasonKind: null
45708
45689
  }).pipe(as([])),
45709
45690
  onSome: succeed$2
45710
- }))));
45711
- yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
45691
+ })));
45712
45692
  const lintFailureState = yield* get$2(lintFailure);
45713
45693
  yield* afterLint(lintFailureState.didFail);
45714
45694
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
@@ -46642,43 +46622,6 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
46642
46622
  //#region src/cli/utils/constants.ts
46643
46623
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
46644
46624
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
46645
- const STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES = [
46646
- ".babelrc",
46647
- ".babelrc.json",
46648
- ".eslintignore",
46649
- ".eslintrc.json",
46650
- ".oxlintignore",
46651
- "app.config.cjs",
46652
- "app.config.js",
46653
- "app.config.json",
46654
- "app.config.mjs",
46655
- "app.config.ts",
46656
- "app.json",
46657
- "babel.config.cjs",
46658
- "babel.config.js",
46659
- "babel.config.json",
46660
- "babel.config.mjs",
46661
- "eslint.config.cjs",
46662
- "eslint.config.cts",
46663
- "eslint.config.js",
46664
- "eslint.config.mjs",
46665
- "eslint.config.mts",
46666
- "eslint.config.ts",
46667
- "next.config.cjs",
46668
- "next.config.js",
46669
- "next.config.mjs",
46670
- "next.config.ts",
46671
- "oxlint.config.json",
46672
- "react-doctor.config.json",
46673
- "vite.config.cjs",
46674
- "vite.config.cts",
46675
- "vite.config.js",
46676
- "vite.config.mjs",
46677
- "vite.config.mts",
46678
- "vite.config.ts",
46679
- "vitest.config.js",
46680
- "vitest.config.ts"
46681
- ];
46682
46625
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
46683
46626
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
46684
46627
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
@@ -46693,7 +46636,6 @@ const METRIC = {
46693
46636
  cliInvoked: "cli.invoked",
46694
46637
  cliError: "cli.error",
46695
46638
  cliEnvironmentError: "cli.env_error",
46696
- stagedSnapshotDivergence: "staged.snapshot_divergence",
46697
46639
  projectDetected: "project.detected",
46698
46640
  projectPathSelected: "project.path_selected",
46699
46641
  projectConfigSelected: "project.config_selected",
@@ -46774,7 +46716,7 @@ const makeNoopConsole = () => ({
46774
46716
  });
46775
46717
  //#endregion
46776
46718
  //#region src/cli/utils/version.ts
46777
- const VERSION = "0.7.6-dev.3f22fe3";
46719
+ const VERSION = "0.7.6-dev.47beb25";
46778
46720
  //#endregion
46779
46721
  //#region src/cli/utils/json-mode.ts
46780
46722
  let context = null;
@@ -47143,13 +47085,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47143
47085
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47144
47086
  * standard `SENTRY_RELEASE` override.
47145
47087
  */
47146
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.3f22fe3`;
47088
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.47beb25`;
47147
47089
  /**
47148
47090
  * Deployment environment shown in Sentry's environment filter. Defaults to
47149
47091
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47150
47092
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47151
47093
  */
47152
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.3f22fe3") ? "development" : "production");
47094
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.47beb25") ? "development" : "production");
47153
47095
  /**
47154
47096
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47155
47097
  * (set to `0` to disable tracing) and falls back to
@@ -47341,7 +47283,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
47341
47283
  "fi"
47342
47284
  ].join(" ");
47343
47285
  const PACKAGE_JSON_FILE_NAME = "package.json";
47344
- const runGitRaw = (projectRoot, args) => {
47286
+ const runGit = (projectRoot, args) => {
47345
47287
  try {
47346
47288
  return execFileSync("git", [...args], {
47347
47289
  cwd: projectRoot,
@@ -47352,12 +47294,11 @@ const runGitRaw = (projectRoot, args) => {
47352
47294
  "ignore"
47353
47295
  ],
47354
47296
  maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
47355
- });
47297
+ }).trim();
47356
47298
  } catch {
47357
47299
  return null;
47358
47300
  }
47359
47301
  };
47360
- const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
47361
47302
  const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
47362
47303
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
47363
47304
  const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
@@ -59228,17 +59169,6 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
59228
59169
  })).toSorted((left, right) => right.fileCount - left.fileCount);
59229
59170
  const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
59230
59171
  //#endregion
59231
- //#region src/cli/utils/has-lint-hard-failure.ts
59232
- const hasLintHardFailure = (result) => result.skippedChecks.includes("lint");
59233
- //#endregion
59234
- //#region src/cli/utils/is-inspect-result-complete.ts
59235
- const isInspectResultComplete = (result) => isScanComplete({
59236
- analyzedFileCount: result.analyzedFiles?.length,
59237
- scannedFileCount: result.scannedFileCount,
59238
- skippedCheckCount: result.skippedChecks.length,
59239
- skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
59240
- });
59241
- //#endregion
59242
59172
  //#region src/cli/utils/scan-result-cache.ts
59243
59173
  const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
59244
59174
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
@@ -59644,12 +59574,15 @@ const buildOutcomeAttributes = (input) => {
59644
59574
  const summary = summarizeDiagnostics(result.diagnostics);
59645
59575
  const blockingLevel = resolveTelemetryBlocking(input.userConfig);
59646
59576
  const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
59647
- const complete = isInspectResultComplete(result);
59648
- const didLintHardFail = hasLintHardFailure(result);
59649
- const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
59650
59577
  const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
59578
+ const complete = isScanComplete({
59579
+ analyzedFileCount: result.analyzedFiles?.length,
59580
+ scannedFileCount: result.scannedFileCount,
59581
+ skippedCheckCount: result.skippedChecks.length,
59582
+ skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
59583
+ });
59651
59584
  const isClean = result.diagnostics.length === 0 && complete;
59652
- const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59585
+ const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59653
59586
  const firings = summarizeRuleFirings(result.diagnostics);
59654
59587
  const countByRule = /* @__PURE__ */ new Map();
59655
59588
  const countByCategory = /* @__PURE__ */ new Map();
@@ -59694,7 +59627,7 @@ const buildOutcomeAttributes = (input) => {
59694
59627
  const attributes = {
59695
59628
  ...withNamespace("outcome", {
59696
59629
  status: outcome,
59697
- exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
59630
+ exitCode: wouldBlock ? 1 : 0,
59698
59631
  wouldBlock,
59699
59632
  blocking: blockingLevel,
59700
59633
  clean: isClean,
@@ -63893,39 +63826,6 @@ const validateModeFlags = (flags) => {
63893
63826
  }
63894
63827
  };
63895
63828
  //#endregion
63896
- //#region src/cli/utils/parse-staged-snapshot-divergences.ts
63897
- const SNAPSHOT_CONFIG_FILENAMES = new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES]);
63898
- const parseStagedSnapshotDivergences = (statusOutput) => {
63899
- const divergentConfigFiles = /* @__PURE__ */ new Set();
63900
- const statusEntries = statusOutput.split("\0").filter((entry) => entry.length > 0);
63901
- for (let entryIndex = 0; entryIndex < statusEntries.length; entryIndex += 1) {
63902
- const entry = statusEntries[entryIndex];
63903
- const [indexStatus, worktreeStatus] = entry;
63904
- const filePath = entry.slice(3);
63905
- const hasRenameOrCopySource = indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
63906
- const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : void 0;
63907
- const worktreePaths = worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
63908
- if (worktreeStatus !== " ") {
63909
- for (const worktreePath of worktreePaths) if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(Path.basename(worktreePath))) divergentConfigFiles.add(worktreePath);
63910
- }
63911
- if (hasRenameOrCopySource) entryIndex += 1;
63912
- }
63913
- return [...divergentConfigFiles].sort();
63914
- };
63915
- //#endregion
63916
- //#region src/cli/utils/find-staged-snapshot-divergences.ts
63917
- const findStagedSnapshotDivergences = (directory) => {
63918
- const statusOutput = runGitRaw(directory, [
63919
- "status",
63920
- "--porcelain=v1",
63921
- "-z",
63922
- "--untracked-files=all",
63923
- "--ignored=matching"
63924
- ]);
63925
- if (statusOutput === null) return null;
63926
- return parseStagedSnapshotDivergences(statusOutput);
63927
- };
63928
- //#endregion
63929
63829
  //#region src/cli/commands/inspect.ts
63930
63830
  const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63931
63831
  if (categoryFilters.size === 0) return [...completedScans];
@@ -63940,14 +63840,9 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63940
63840
  /**
63941
63841
  * Post-scan finalization shared by the staged-arm and project-loop
63942
63842
  * paths of `inspectAction`: emit the JSON report (when in JSON mode)
63943
- * and set `process.exitCode = 1` when any scan's lint pass hard-failed
63944
- * (an engine/plugin/binding failure destroys the findings, so success
63945
- * would be a false clean) or a diagnostic at or above the `--blocking`
63946
- * threshold (default `"error"`) reaches the `ciFailure` surface.
63947
- * `--blocking none` keeps the scan advisory (always exits 0), and
63948
- * fail-open degradations — `--no-lint`, `--max-duration` truncation,
63949
- * supply-chain/security skips — stay advisory too, surfaced through
63950
- * `complete: false` in the JSON report.
63843
+ * and set `process.exitCode = 1` when a diagnostic at or above the
63844
+ * `--blocking` threshold (default `"error"`) reaches the `ciFailure`
63845
+ * surface. `--blocking none` keeps the scan advisory (always exits 0).
63951
63846
  */
63952
63847
  const finalizeScans = (input) => {
63953
63848
  const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
@@ -63977,13 +63872,8 @@ const finalizeScans = (input) => {
63977
63872
  baselineDegraded
63978
63873
  }));
63979
63874
  }
63980
- const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
63981
- if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
63982
- process.exitCode = 1;
63983
- return;
63984
- }
63985
63875
  if (input.isScoreOnly || baselineDegraded) return;
63986
- if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
63876
+ if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), resolveBlockingLevel(input.flags, input.userConfig))) process.exitCode = 1;
63987
63877
  };
63988
63878
  const buildChangedFilesDiffInfo = (changedFiles) => ({
63989
63879
  currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
@@ -64021,7 +63911,6 @@ const inspectAction = async (directory, flags) => {
64021
63911
  recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
64022
63912
  try {
64023
63913
  validateModeFlags(flags);
64024
- if (flags.staged) setJsonReportMode("staged");
64025
63914
  await maybeMigrateLegacyConfig(requestedDirectory, {
64026
63915
  isQuiet,
64027
63916
  isStaged: Boolean(flags.staged)
@@ -64037,14 +63926,6 @@ const inspectAction = async (directory, flags) => {
64037
63926
  cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
64038
63927
  cliLogger.break();
64039
63928
  }
64040
- if (flags.staged) {
64041
- const divergentConfigFiles = findStagedSnapshotDivergences(resolvedDirectory);
64042
- if (divergentConfigFiles === null) throw new CliInputError("Could not verify that staged configuration matches the worktree. Run the command from a Git worktree with Git available.");
64043
- if (divergentConfigFiles.length > 0) {
64044
- recordCount(METRIC.stagedSnapshotDivergence, 1, { divergentInputCount: divergentConfigFiles.length });
64045
- throw new CliInputError(`Cannot scan staged files while configuration differs between the index and worktree: ${divergentConfigFiles.join(", ")}. Stage or restore those files, then rerun react-doctor --staged.`);
64046
- }
64047
- }
64048
63929
  const explainArgument = flags.explain;
64049
63930
  if (explainArgument !== void 0) {
64050
63931
  await runExplain(explainArgument, {
@@ -64065,6 +63946,7 @@ const inspectAction = async (directory, flags) => {
64065
63946
  json: flags.json
64066
63947
  });
64067
63948
  if (flags.staged) {
63949
+ setJsonReportMode("staged");
64068
63950
  const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
64069
63951
  if (stagedFiles.length === 0) {
64070
63952
  if (isJsonMode) writeJsonReport(buildJsonReport({
@@ -65415,4 +65297,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
65415
65297
  export {};
65416
65298
 
65417
65299
  //# sourceMappingURL=cli.js.map
65418
- //# debugId=4c1e681e-4dee-5ffd-a4ba-69b876f47147
65300
+ //# debugId=5dcb5b0b-ca1e-5655-8f52-3c22b421256d
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4da79386-f10f-5bae-95ac-7333b2d6c4e8")}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]="80f981d4-ebf0-5bc0-9643-8a68d27626a5")}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
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";
@@ -36366,49 +36366,6 @@ const assignFixGroups = (diagnostics) => {
36366
36366
  };
36367
36367
  });
36368
36368
  };
36369
- const DERIVED_STATE_RULE_PRIORITY = new Map([
36370
- ["no-initialize-state", 0],
36371
- ["no-adjust-state-on-prop-change", 1],
36372
- ["no-derived-state", 2],
36373
- ["no-derived-state-effect", 3]
36374
- ]);
36375
- const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
36376
- const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
36377
- const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
36378
- const doDiagnosticSpansOverlap = (first, second) => {
36379
- if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
36380
- if (first.offset !== void 0 && second.offset !== void 0) {
36381
- const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
36382
- const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
36383
- return first.offset < secondEnd && second.offset < firstEnd;
36384
- }
36385
- const firstEndLine = first.endLine ?? first.line;
36386
- const secondEndLine = second.endLine ?? second.line;
36387
- return first.line <= secondEndLine && second.line <= firstEndLine;
36388
- };
36389
- const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
36390
- ...survivingDiagnostic,
36391
- severity: "error"
36392
- } : survivingDiagnostic;
36393
- const dedupeRelatedDiagnostics = (diagnostics) => {
36394
- const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
36395
- const uniqueDiagnostics = [];
36396
- for (const diagnostic of diagnostics) {
36397
- if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
36398
- const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
36399
- if (priority !== void 0) {
36400
- const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
36401
- if (overlappingDiagnosticIndex >= 0) {
36402
- const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
36403
- const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
36404
- if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
36405
- continue;
36406
- }
36407
- }
36408
- uniqueDiagnostics.push(diagnostic);
36409
- }
36410
- return uniqueDiagnostics;
36411
- };
36412
36369
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
36413
36370
  const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
36414
36371
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -39534,13 +39491,13 @@ const encodeSidecarFileEntry = (entry) => ({
39534
39491
  diagnostics: entry.diagnostics
39535
39492
  });
39536
39493
  const emptyCache = () => ({
39537
- version: 2,
39494
+ version: 1,
39538
39495
  rulesets: {}
39539
39496
  });
39540
39497
  const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39541
39498
  const entries = /* @__PURE__ */ new Map();
39542
39499
  const persisted = failOpenReadJson(cacheFilePath, emptyCache());
39543
- if (persisted.version !== 2 || !isRecord(persisted.rulesets)) return entries;
39500
+ if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39544
39501
  const bucket = persisted.rulesets[rulesetHash];
39545
39502
  if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
39546
39503
  for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
@@ -39560,7 +39517,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39560
39517
  },
39561
39518
  persist: () => {
39562
39519
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
39563
- const rulesets = onDisk.version === 2 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39520
+ const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39564
39521
  const existingBucket = rulesets[rulesetHash];
39565
39522
  const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39566
39523
  const ourFiles = {};
@@ -39574,7 +39531,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39574
39531
  files: Object.fromEntries(cappedEntries)
39575
39532
  };
39576
39533
  atomicWriteJson(cacheFilePath, {
39577
- version: 2,
39534
+ version: 1,
39578
39535
  rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
39579
39536
  });
39580
39537
  }
@@ -39625,6 +39582,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
39625
39582
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
39626
39583
  return null;
39627
39584
  };
39585
+ const DERIVED_STATE_RULE_PRIORITY = new Map([
39586
+ ["no-initialize-state", 0],
39587
+ ["no-adjust-state-on-prop-change", 1],
39588
+ ["no-derived-state", 2],
39589
+ ["no-derived-state-effect", 3]
39590
+ ]);
39591
+ const doDiagnosticSpansOverlap = (first, second) => {
39592
+ if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
39593
+ if (first.offset !== void 0 && second.offset !== void 0) {
39594
+ const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
39595
+ const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
39596
+ return first.offset < secondEnd && second.offset < firstEnd;
39597
+ }
39598
+ const firstEndLine = first.endLine ?? first.line;
39599
+ const secondEndLine = second.endLine ?? second.line;
39600
+ return first.line <= secondEndLine && second.line <= firstEndLine;
39601
+ };
39628
39602
  const dedupeDiagnostics = (diagnostics) => {
39629
39603
  const seenKeys = /* @__PURE__ */ new Set();
39630
39604
  const uniqueDiagnostics = [];
@@ -39632,6 +39606,16 @@ const dedupeDiagnostics = (diagnostics) => {
39632
39606
  const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
39633
39607
  if (seenKeys.has(key)) continue;
39634
39608
  seenKeys.add(key);
39609
+ const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
39610
+ if (priority !== void 0) {
39611
+ const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
39612
+ if (overlappingDiagnosticIndex >= 0) {
39613
+ const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
39614
+ const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
39615
+ if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
39616
+ continue;
39617
+ }
39618
+ }
39635
39619
  uniqueDiagnostics.push(diagnostic);
39636
39620
  }
39637
39621
  return uniqueDiagnostics;
@@ -40431,11 +40415,11 @@ const buildRelatedLocations = (labels, filePath) => {
40431
40415
  }
40432
40416
  return related;
40433
40417
  };
40434
- const isOxlintSpan = (value) => isRecord(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
40435
- const isOxlintLabel = (value) => isRecord(value) && isOxlintSpan(value.span);
40436
- const isMappableOxlintDiagnostic = (value) => isRecord(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
40437
- const isEngineFailureDiagnostic = (value) => !isRecord(value) || typeof value.filename !== "string" || value.filename.length === 0;
40438
- const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnostics);
40418
+ const isOxlintOutput = (value) => {
40419
+ if (typeof value !== "object" || value === null) return false;
40420
+ const candidate = value;
40421
+ return Array.isArray(candidate.diagnostics);
40422
+ };
40439
40423
  /**
40440
40424
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
40441
40425
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -40454,8 +40438,6 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
40454
40438
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
40455
40439
  }
40456
40440
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
40457
- const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
40458
- if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
40459
40441
  const minifiedFileCache = /* @__PURE__ */ new Map();
40460
40442
  const sourceBufferCache = /* @__PURE__ */ new Map();
40461
40443
  const sourceLinesCache = /* @__PURE__ */ new Map();
@@ -40490,7 +40472,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
40490
40472
  minifiedFileCache.set(absolutePath, minified);
40491
40473
  return minified;
40492
40474
  };
40493
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
40475
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
40494
40476
  const { plugin, rule } = parseRuleCode(diagnostic.code);
40495
40477
  const primaryLabel = diagnostic.labels[0];
40496
40478
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
@@ -42325,8 +42307,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42325
42307
  respectInlineDisables: input.respectInlineDisables,
42326
42308
  showWarnings
42327
42309
  });
42328
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
42329
- const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
42310
+ const applyPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)), tap((diagnostic) => reporterService.emit(diagnostic)));
42330
42311
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
42331
42312
  ...checkReducedMotion(scanDirectory),
42332
42313
  ...checkPnpmHardening(scanDirectory),
@@ -42455,7 +42436,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42455
42436
  });
42456
42437
  return empty$4;
42457
42438
  }))));
42458
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
42439
+ const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
42459
42440
  onNone: () => set(lintFailure, {
42460
42441
  didFail: true,
42461
42442
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -42463,8 +42444,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42463
42444
  reasonKind: null
42464
42445
  }).pipe(as([])),
42465
42446
  onSome: succeed$2
42466
- }))));
42467
- yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
42447
+ })));
42468
42448
  const lintFailureState = yield* get$2(lintFailure);
42469
42449
  yield* afterLint(lintFailureState.didFail);
42470
42450
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
@@ -43278,4 +43258,4 @@ const toJsonReport = (result, options) => buildJsonReport({
43278
43258
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
43279
43259
 
43280
43260
  //# sourceMappingURL=index.js.map
43281
- //# debugId=4da79386-f10f-5bae-95ac-7333b2d6c4e8
43261
+ //# debugId=80f981d4-ebf0-5bc0-9643-8a68d27626a5
package/dist/lsp.js CHANGED
@@ -36344,49 +36344,6 @@ const assignFixGroups = (diagnostics) => {
36344
36344
  };
36345
36345
  });
36346
36346
  };
36347
- const DERIVED_STATE_RULE_PRIORITY = new Map([
36348
- ["no-initialize-state", 0],
36349
- ["no-adjust-state-on-prop-change", 1],
36350
- ["no-derived-state", 2],
36351
- ["no-derived-state-effect", 3]
36352
- ]);
36353
- const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
36354
- const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
36355
- const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
36356
- const doDiagnosticSpansOverlap = (first, second) => {
36357
- if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
36358
- if (first.offset !== void 0 && second.offset !== void 0) {
36359
- const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
36360
- const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
36361
- return first.offset < secondEnd && second.offset < firstEnd;
36362
- }
36363
- const firstEndLine = first.endLine ?? first.line;
36364
- const secondEndLine = second.endLine ?? second.line;
36365
- return first.line <= secondEndLine && second.line <= firstEndLine;
36366
- };
36367
- const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
36368
- ...survivingDiagnostic,
36369
- severity: "error"
36370
- } : survivingDiagnostic;
36371
- const dedupeRelatedDiagnostics = (diagnostics) => {
36372
- const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
36373
- const uniqueDiagnostics = [];
36374
- for (const diagnostic of diagnostics) {
36375
- if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
36376
- const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
36377
- if (priority !== void 0) {
36378
- const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
36379
- if (overlappingDiagnosticIndex >= 0) {
36380
- const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
36381
- const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
36382
- if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
36383
- continue;
36384
- }
36385
- }
36386
- uniqueDiagnostics.push(diagnostic);
36387
- }
36388
- return uniqueDiagnostics;
36389
- };
36390
36347
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
36391
36348
  const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
36392
36349
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -39512,13 +39469,13 @@ const encodeSidecarFileEntry = (entry) => ({
39512
39469
  diagnostics: entry.diagnostics
39513
39470
  });
39514
39471
  const emptyCache = () => ({
39515
- version: 2,
39472
+ version: 1,
39516
39473
  rulesets: {}
39517
39474
  });
39518
39475
  const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39519
39476
  const entries = /* @__PURE__ */ new Map();
39520
39477
  const persisted = failOpenReadJson(cacheFilePath, emptyCache());
39521
- if (persisted.version !== 2 || !isRecord(persisted.rulesets)) return entries;
39478
+ if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39522
39479
  const bucket = persisted.rulesets[rulesetHash];
39523
39480
  if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
39524
39481
  for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
@@ -39538,7 +39495,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39538
39495
  },
39539
39496
  persist: () => {
39540
39497
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
39541
- const rulesets = onDisk.version === 2 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39498
+ const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39542
39499
  const existingBucket = rulesets[rulesetHash];
39543
39500
  const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39544
39501
  const ourFiles = {};
@@ -39552,7 +39509,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39552
39509
  files: Object.fromEntries(cappedEntries)
39553
39510
  };
39554
39511
  atomicWriteJson(cacheFilePath, {
39555
- version: 2,
39512
+ version: 1,
39556
39513
  rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
39557
39514
  });
39558
39515
  }
@@ -39603,6 +39560,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
39603
39560
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
39604
39561
  return null;
39605
39562
  };
39563
+ const DERIVED_STATE_RULE_PRIORITY = new Map([
39564
+ ["no-initialize-state", 0],
39565
+ ["no-adjust-state-on-prop-change", 1],
39566
+ ["no-derived-state", 2],
39567
+ ["no-derived-state-effect", 3]
39568
+ ]);
39569
+ const doDiagnosticSpansOverlap = (first, second) => {
39570
+ if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
39571
+ if (first.offset !== void 0 && second.offset !== void 0) {
39572
+ const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
39573
+ const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
39574
+ return first.offset < secondEnd && second.offset < firstEnd;
39575
+ }
39576
+ const firstEndLine = first.endLine ?? first.line;
39577
+ const secondEndLine = second.endLine ?? second.line;
39578
+ return first.line <= secondEndLine && second.line <= firstEndLine;
39579
+ };
39606
39580
  const dedupeDiagnostics = (diagnostics) => {
39607
39581
  const seenKeys = /* @__PURE__ */ new Set();
39608
39582
  const uniqueDiagnostics = [];
@@ -39610,6 +39584,16 @@ const dedupeDiagnostics = (diagnostics) => {
39610
39584
  const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
39611
39585
  if (seenKeys.has(key)) continue;
39612
39586
  seenKeys.add(key);
39587
+ const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
39588
+ if (priority !== void 0) {
39589
+ const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
39590
+ if (overlappingDiagnosticIndex >= 0) {
39591
+ const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
39592
+ const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
39593
+ if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
39594
+ continue;
39595
+ }
39596
+ }
39613
39597
  uniqueDiagnostics.push(diagnostic);
39614
39598
  }
39615
39599
  return uniqueDiagnostics;
@@ -40409,11 +40393,11 @@ const buildRelatedLocations = (labels, filePath) => {
40409
40393
  }
40410
40394
  return related;
40411
40395
  };
40412
- const isOxlintSpan = (value) => isRecord(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
40413
- const isOxlintLabel = (value) => isRecord(value) && isOxlintSpan(value.span);
40414
- const isMappableOxlintDiagnostic = (value) => isRecord(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
40415
- const isEngineFailureDiagnostic = (value) => !isRecord(value) || typeof value.filename !== "string" || value.filename.length === 0;
40416
- const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnostics);
40396
+ const isOxlintOutput = (value) => {
40397
+ if (typeof value !== "object" || value === null) return false;
40398
+ const candidate = value;
40399
+ return Array.isArray(candidate.diagnostics);
40400
+ };
40417
40401
  /**
40418
40402
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
40419
40403
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -40432,8 +40416,6 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
40432
40416
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
40433
40417
  }
40434
40418
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
40435
- const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
40436
- if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
40437
40419
  const minifiedFileCache = /* @__PURE__ */ new Map();
40438
40420
  const sourceBufferCache = /* @__PURE__ */ new Map();
40439
40421
  const sourceLinesCache = /* @__PURE__ */ new Map();
@@ -40468,7 +40450,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
40468
40450
  minifiedFileCache.set(absolutePath, minified);
40469
40451
  return minified;
40470
40452
  };
40471
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
40453
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
40472
40454
  const { plugin, rule } = parseRuleCode(diagnostic.code);
40473
40455
  const primaryLabel = diagnostic.labels[0];
40474
40456
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
@@ -42303,8 +42285,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42303
42285
  respectInlineDisables: input.respectInlineDisables,
42304
42286
  showWarnings
42305
42287
  });
42306
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
42307
- const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
42288
+ const applyPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)), tap((diagnostic) => reporterService.emit(diagnostic)));
42308
42289
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
42309
42290
  ...checkReducedMotion(scanDirectory),
42310
42291
  ...checkPnpmHardening(scanDirectory),
@@ -42433,7 +42414,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42433
42414
  });
42434
42415
  return empty$4;
42435
42416
  }))));
42436
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
42417
+ const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
42437
42418
  onNone: () => set(lintFailure, {
42438
42419
  didFail: true,
42439
42420
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -42441,8 +42422,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42441
42422
  reasonKind: null
42442
42423
  }).pipe(as([])),
42443
42424
  onSome: succeed$2
42444
- }))));
42445
- yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
42425
+ })));
42446
42426
  const lintFailureState = yield* get$2(lintFailure);
42447
42427
  yield* afterLint(lintFailureState.didFail);
42448
42428
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
@@ -44560,7 +44540,6 @@ const METRIC = {
44560
44540
  cliInvoked: "cli.invoked",
44561
44541
  cliError: "cli.error",
44562
44542
  cliEnvironmentError: "cli.env_error",
44563
- stagedSnapshotDivergence: "staged.snapshot_divergence",
44564
44543
  projectDetected: "project.detected",
44565
44544
  projectPathSelected: "project.path_selected",
44566
44545
  projectConfigSelected: "project.config_selected",
@@ -44915,5 +44894,5 @@ const startLanguageServer = () => {
44915
44894
  };
44916
44895
  //#endregion
44917
44896
  export { startLanguageServer };
44918
- !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]="b75c36e9-f19d-5127-be1a-89ac93910ff0")}catch(e){}}();
44919
- //# debugId=b75c36e9-f19d-5127-be1a-89ac93910ff0
44897
+ !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]="288f7240-2ee1-5bcb-a56e-bca2b972818c")}catch(e){}}();
44898
+ //# debugId=288f7240-2ee1-5bcb-a56e-bca2b972818c
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.7.6-dev.3f22fe3",
3
+ "version": "0.7.6-dev.47beb25",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -66,7 +66,7 @@
66
66
  "vscode-uri": "^3.1.0",
67
67
  "yaml": "^2.9.0",
68
68
  "deslop-js": "0.7.6",
69
- "oxlint-plugin-react-doctor": "0.7.6-dev.3f22fe3"
69
+ "oxlint-plugin-react-doctor": "0.7.6-dev.47beb25"
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.6",
78
77
  "@react-doctor/api": "0.7.6",
79
- "@react-doctor/language-server": "0.7.6"
78
+ "@react-doctor/language-server": "0.7.6",
79
+ "@react-doctor/core": "0.7.6"
80
80
  },
81
81
  "engines": {
82
82
  "node": "^20.19.0 || >=22.13.0"