react-doctor 0.8.1-dev.fe241a9 → 0.8.1

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]="24153a3f-aef9-53fd-8dff-9b016a9613fb")}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]="941df082-c8b4-5084-baae-33ec22d4e55f")}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";
@@ -42334,35 +42334,6 @@ const parseGithubViewerPermission = (stdout) => {
42334
42334
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
42335
42335
  };
42336
42336
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
42337
- const parseBaselineDiffPlan = (value) => {
42338
- const entries = splitNullSeparated(value);
42339
- const baseFiles = /* @__PURE__ */ new Set();
42340
- const headFiles = /* @__PURE__ */ new Set();
42341
- for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
42342
- const status = entries[entryIndex];
42343
- const filePath = entries[entryIndex + 1];
42344
- if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
42345
- if (status === "A") {
42346
- headFiles.add(filePath);
42347
- continue;
42348
- }
42349
- if (status === "D") {
42350
- baseFiles.add(filePath);
42351
- continue;
42352
- }
42353
- if (status === "M" || status === "T") {
42354
- baseFiles.add(filePath);
42355
- headFiles.add(filePath);
42356
- continue;
42357
- }
42358
- return null;
42359
- }
42360
- return {
42361
- baseFiles: [...baseFiles],
42362
- headFiles: [...headFiles],
42363
- untrackedFiles: []
42364
- };
42365
- };
42366
42337
  const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
42367
42338
  /**
42368
42339
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
@@ -42565,45 +42536,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
42565
42536
  githubViewerPermission,
42566
42537
  branchExists,
42567
42538
  mergeBase,
42568
- baselineDiffPlan: (input) => {
42569
- if (!isSafeGitRevision(input.ref)) return succeed$2(null);
42570
- return gen(function* () {
42571
- const unmerged = yield* runGit(input.directory, [
42572
- "diff",
42573
- "--no-ext-diff",
42574
- "-z",
42575
- "--name-only",
42576
- "--diff-filter=U",
42577
- "--relative"
42578
- ]);
42579
- if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
42580
- const result = yield* runGit(input.directory, [
42581
- "diff",
42582
- "--no-ext-diff",
42583
- "--no-textconv",
42584
- "--no-renames",
42585
- "-z",
42586
- "--name-status",
42587
- "--relative",
42588
- input.ref
42589
- ]);
42590
- if (result.status !== 0) return null;
42591
- const plan = parseBaselineDiffPlan(result.stdout);
42592
- if (plan === null) return null;
42593
- const untracked = yield* runGit(input.directory, [
42594
- "ls-files",
42595
- "--others",
42596
- "--exclude-standard",
42597
- "-z"
42598
- ]);
42599
- if (untracked.status !== 0) return null;
42600
- return {
42601
- baseFiles: plan.baseFiles,
42602
- headFiles: plan.headFiles,
42603
- untrackedFiles: splitNullSeparated(untracked.stdout)
42604
- };
42605
- }).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
42606
- },
42607
42539
  diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
42608
42540
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
42609
42541
  if (explicitBaseBranch !== void 0) {
@@ -42753,7 +42685,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
42753
42685
  githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
42754
42686
  branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
42755
42687
  mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
42756
- baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
42757
42688
  diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
42758
42689
  stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
42759
42690
  showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
@@ -44091,17 +44022,12 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
44091
44022
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
44092
44023
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
44093
44024
  const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
44094
- const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
44095
44025
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
44096
44026
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
44097
44027
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
44098
44028
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
44099
44029
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
44100
44030
  };
44101
- const buildSetStateInEffectMessage = (reasonSummary) => {
44102
- const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
44103
- return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
44104
- };
44105
44031
  const PLUGIN_CATEGORY_MAP = {
44106
44032
  react: "Bugs",
44107
44033
  "react-hooks": "Bugs",
@@ -44159,10 +44085,6 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
44159
44085
  message: REACT_ERROR_BOUNDARY_MESSAGE,
44160
44086
  help: reasonDetail || help
44161
44087
  };
44162
- if (rule === "set-state-in-effect") return {
44163
- message: buildSetStateInEffectMessage(reasonSummary.trim()),
44164
- help: reasonDetail || help
44165
- };
44166
44088
  return {
44167
44089
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
44168
44090
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -46499,19 +46421,12 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
46499
46421
  */
46500
46422
  const materializeSourceTree = (input) => gen(function* () {
46501
46423
  const materializedFiles = [];
46502
- const unmaterializedFiles = [];
46503
46424
  const resolvedTempDirectory = path.resolve(input.tempDirectory);
46504
46425
  for (const relativePath of input.files) {
46505
46426
  const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
46506
- if (content === null) {
46507
- unmaterializedFiles.push(relativePath);
46508
- continue;
46509
- }
46427
+ if (content === null) continue;
46510
46428
  const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
46511
- if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
46512
- unmaterializedFiles.push(relativePath);
46513
- continue;
46514
- }
46429
+ if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
46515
46430
  yield* sync(() => {
46516
46431
  fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
46517
46432
  fs.writeFileSync(candidateTargetPath, content);
@@ -46528,7 +46443,6 @@ const materializeSourceTree = (input) => gen(function* () {
46528
46443
  return {
46529
46444
  tempDirectory: input.tempDirectory,
46530
46445
  materializedFiles,
46531
- unmaterializedFiles,
46532
46446
  cleanup: () => {
46533
46447
  try {
46534
46448
  fs.rmSync(input.tempDirectory, {
@@ -46798,80 +46712,54 @@ const buildSkippedChecks = (input) => {
46798
46712
  skippedCheckReasons
46799
46713
  };
46800
46714
  };
46801
- const fingerprintText = (text) => createHash("sha256").update(text).digest("hex");
46802
- const normalizeEvidence = (evidence) => evidence.replace(/\s+/g, " ").trim();
46803
- const getDiagnosticMatchKeys = (diagnostic, evidence) => {
46715
+ const fingerprintDiagnostic = (diagnostic, lineText) => {
46804
46716
  const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
46805
- const messageFingerprint = fingerprintText(`${diagnostic.title ?? ""}\0${diagnostic.message}`);
46806
- const normalizedEvidence = evidence === null ? "" : normalizeEvidence(evidence);
46807
- const stableEvidenceKey = normalizedEvidence.length > 0 ? `evidence\0${ruleKey}\0${messageFingerprint}\0${fingerprintText(normalizedEvidence)}` : null;
46808
- return {
46809
- stableEvidenceKey,
46810
- sameFileStableEvidenceKey: stableEvidenceKey === null ? null : `${diagnostic.filePath}\0${stableEvidenceKey}`,
46811
- sameFileFallbackKey: diagnostic.matchByOccurrence || normalizedEvidence.length === 0 ? `fallback\0${diagnostic.filePath}\0${ruleKey}\0${messageFingerprint}` : null
46812
- };
46813
- };
46814
- const addDiagnosticIndex = (buckets, key, diagnosticIndex) => {
46815
- if (key === null) return;
46816
- const diagnosticIndexes = buckets.get(key) ?? [];
46817
- diagnosticIndexes.push(diagnosticIndex);
46818
- buckets.set(key, diagnosticIndexes);
46819
- };
46820
- const takeMatchingDiagnosticIndex = (buckets, key, matchedDiagnosticIndexes) => {
46821
- if (key === null) return null;
46822
- for (const diagnosticIndex of buckets.get(key) ?? []) if (!matchedDiagnosticIndexes.has(diagnosticIndex)) return diagnosticIndex;
46823
- return null;
46824
- };
46825
- const readDiagnosticEvidence = (diagnostic, readEvidence, readLine) => readEvidence?.(diagnostic) ?? readLine(diagnostic.filePath, diagnostic.line);
46826
- const buildMatchCandidates = (diagnostics, readEvidence, readLine) => diagnostics.map((diagnostic, diagnosticIndex) => ({
46827
- diagnosticIndex,
46828
- ...getDiagnosticMatchKeys(diagnostic, readDiagnosticEvidence(diagnostic, readEvidence, readLine))
46829
- }));
46830
- /**
46831
- * Diffs a head scan against a base scan using a multiset of construct-level
46832
- * evidence. Stable identities combine plugin/rule, the diagnostic message,
46833
- * and normalized diagnosed source, so unchanged findings can move across
46834
- * files while changed constructs or messages remain new. Cardinality is
46835
- * retained for identical findings. Diagnostics explicitly marked
46836
- * `matchByOccurrence` may fall back to same-file plugin/rule/message matching
46837
- * after same-file strict evidence matching. Cross-file evidence matching runs
46838
- * last so a copy cannot consume a reformatted local occurrence. Unreadable
46839
- * evidence uses the same conservative fallback rather than matching across
46840
- * files without proof.
46717
+ const snippet = lineText === null || diagnostic.matchByOccurrence ? "" : createHash("sha1").update(lineText.trim()).digest("hex");
46718
+ return `${diagnostic.filePath}\u0000${ruleKey}\u0000${snippet}`;
46719
+ };
46720
+ /**
46721
+ * Diffs a head scan against a base scan to isolate the diagnostics a change
46722
+ * introduced (and count the ones it resolved). Matching is a multiset over a
46723
+ * position-independent fingerprint — `(filePath, plugin/rule, hash(flagged
46724
+ * line text))` — so inserting lines above an existing issue doesn't make it
46725
+ * look new, while a genuinely new occurrence (new line text, or one more of
46726
+ * the same) surfaces. Identical repeated findings are matched by count.
46727
+ *
46728
+ * Diagnostics carrying `matchByOccurrence` (resolved at diagnostic creation:
46729
+ * every Accessibility-category finding, plus rules opting in via their
46730
+ * `matchByOccurrence` metadata flag) drop the line-text snippet and match by
46731
+ * `(filePath, plugin/rule)` occurrence count alone. Their identity is the
46732
+ * flagged element, not the line's text, so editing the line (reindentation,
46733
+ * prettier reflow, collapsing a multi-line JSX element) doesn't reclassify a
46734
+ * pre-existing finding as new — while one MORE occurrence of the same rule in
46735
+ * the file still surfaces. Expression-level rules keep the line-text snippet:
46736
+ * there the flagged expression IS the finding, so changed text means new +
46737
+ * fixed.
46738
+ *
46739
+ * v1 limitation: the fingerprint keys on the head-relative `filePath`, and base
46740
+ * content is read at that same path. A file renamed by the change therefore has
46741
+ * no base match, so its pre-existing findings are reported as new. This
46742
+ * over-reports (never hides a real issue) and is rare; rename-aware base
46743
+ * resolution is a follow-up.
46841
46744
  */
46842
46745
  const computeDiagnosticDelta = (input) => {
46843
- const baseCandidates = buildMatchCandidates(input.baseDiagnostics, input.readBaseEvidence, input.readBaseLine);
46844
- const headCandidates = buildMatchCandidates(input.headDiagnostics, input.readHeadEvidence, input.readHeadLine);
46845
- const baseByStableEvidence = /* @__PURE__ */ new Map();
46846
- const baseBySameFileStableEvidence = /* @__PURE__ */ new Map();
46847
- const baseBySameFileFallback = /* @__PURE__ */ new Map();
46848
- for (const candidate of baseCandidates) {
46849
- addDiagnosticIndex(baseByStableEvidence, candidate.stableEvidenceKey, candidate.diagnosticIndex);
46850
- addDiagnosticIndex(baseBySameFileStableEvidence, candidate.sameFileStableEvidenceKey, candidate.diagnosticIndex);
46851
- addDiagnosticIndex(baseBySameFileFallback, candidate.sameFileFallbackKey, candidate.diagnosticIndex);
46852
- }
46853
- const matchedHeadDiagnosticIndexes = /* @__PURE__ */ new Set();
46854
- const matchedBaseDiagnosticIndexes = /* @__PURE__ */ new Set();
46855
- const matchCandidates = (baseBuckets, getKey, onMatch) => {
46856
- for (const candidate of headCandidates) {
46857
- if (matchedHeadDiagnosticIndexes.has(candidate.diagnosticIndex)) continue;
46858
- const baseDiagnosticIndex = takeMatchingDiagnosticIndex(baseBuckets, getKey(candidate), matchedBaseDiagnosticIndexes);
46859
- if (baseDiagnosticIndex === null) continue;
46860
- matchedHeadDiagnosticIndexes.add(candidate.diagnosticIndex);
46861
- matchedBaseDiagnosticIndexes.add(baseDiagnosticIndex);
46862
- onMatch?.(candidate.diagnosticIndex, baseDiagnosticIndex);
46863
- }
46864
- };
46865
- matchCandidates(baseBySameFileStableEvidence, (candidate) => candidate.sameFileStableEvidenceKey);
46866
- matchCandidates(baseBySameFileFallback, (candidate) => candidate.sameFileFallbackKey);
46867
- let crossFileMatchCount = 0;
46868
- matchCandidates(baseByStableEvidence, (candidate) => candidate.stableEvidenceKey, (head, base) => {
46869
- if (input.headDiagnostics[head]?.filePath !== input.baseDiagnostics[base]?.filePath) crossFileMatchCount += 1;
46870
- });
46746
+ const unmatchedBaseByFingerprint = /* @__PURE__ */ new Map();
46747
+ for (const diagnostic of input.baseDiagnostics) {
46748
+ const key = fingerprintDiagnostic(diagnostic, input.readBaseLine(diagnostic.filePath, diagnostic.line));
46749
+ unmatchedBaseByFingerprint.set(key, (unmatchedBaseByFingerprint.get(key) ?? 0) + 1);
46750
+ }
46751
+ const newDiagnostics = [];
46752
+ for (const diagnostic of input.headDiagnostics) {
46753
+ const key = fingerprintDiagnostic(diagnostic, input.readHeadLine(diagnostic.filePath, diagnostic.line));
46754
+ const availableMatches = unmatchedBaseByFingerprint.get(key) ?? 0;
46755
+ if (availableMatches > 0) unmatchedBaseByFingerprint.set(key, availableMatches - 1);
46756
+ else newDiagnostics.push(diagnostic);
46757
+ }
46758
+ let fixedCount = 0;
46759
+ for (const remaining of unmatchedBaseByFingerprint.values()) fixedCount += remaining;
46871
46760
  return {
46872
- newDiagnostics: input.headDiagnostics.filter((_diagnostic, diagnosticIndex) => !matchedHeadDiagnosticIndexes.has(diagnosticIndex)),
46873
- fixedCount: input.baseDiagnostics.length - matchedBaseDiagnosticIndexes.size,
46874
- crossFileMatchCount
46761
+ newDiagnostics,
46762
+ fixedCount
46875
46763
  };
46876
46764
  };
46877
46765
  /**
@@ -46903,12 +46791,6 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
46903
46791
  };
46904
46792
  }).pipe(provide(Git.layerNode)));
46905
46793
  const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
46906
- const getBaselineDiffPlan = (directory, ref) => runPromise(gen(function* () {
46907
- return yield* (yield* Git).baselineDiffPlan({
46908
- directory,
46909
- ref
46910
- });
46911
- }).pipe(provide(Git.layerNode)));
46912
46794
  /**
46913
46795
  * Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
46914
46796
  * `files` with `--unified=0` against `baseRef` (or the index when `cached`),
@@ -47365,7 +47247,7 @@ const makeNoopConsole = () => ({
47365
47247
  });
47366
47248
  //#endregion
47367
47249
  //#region src/cli/utils/version.ts
47368
- const VERSION = "0.8.1-dev.fe241a9";
47250
+ const VERSION = "0.8.1";
47369
47251
  //#endregion
47370
47252
  //#region src/cli/utils/json-mode.ts
47371
47253
  let context = null;
@@ -47717,13 +47599,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47717
47599
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47718
47600
  * standard `SENTRY_RELEASE` override.
47719
47601
  */
47720
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.fe241a9`;
47602
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1`;
47721
47603
  /**
47722
47604
  * Deployment environment shown in Sentry's environment filter. Defaults to
47723
47605
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47724
47606
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47725
47607
  */
47726
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.fe241a9") ? "development" : "production");
47608
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1") ? "development" : "production");
47727
47609
  /**
47728
47610
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47729
47611
  * (set to `0` to disable tracing) and falls back to
@@ -60346,7 +60228,6 @@ const buildOutcomeAttributes = (input) => {
60346
60228
  new: summary.totalDiagnosticCount,
60347
60229
  fixed: result.baselineDelta.fixedCount,
60348
60230
  baseTotal: result.baselineDelta.baseTotalCount,
60349
- crossFileMatches: result.baselineDelta.crossFileMatchCount ?? null,
60350
60231
  degraded: false
60351
60232
  }));
60352
60233
  else if (input.gateExempt) attributes["baseline.degraded"] = true;
@@ -60460,49 +60341,28 @@ const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
60460
60341
  return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
60461
60342
  };
60462
60343
  //#endregion
60463
- //#region src/cli/utils/is-materializable-git-source.ts
60464
- const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
60465
- const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
60466
- //#endregion
60467
60344
  //#region src/cli/utils/materialize-baseline-files.ts
60468
60345
  /**
60469
- * Materializes the base side of the `ref` → worktree diff into
60470
- * `tempDirectory`, mirroring the project layout plus head's config files so
60471
- * both sides lint under the same rules. Git rename heuristics are disabled by
60472
- * the diff planner, so an old path is retained as a base deletion while its
60473
- * new path is a head addition. Missing head-only additions are expected;
60474
- * missing paths that the plan says must exist at base make `isComplete` false.
60346
+ * Materializes the `ref` version of `files` into `tempDirectory` (via
60347
+ * `git show <ref>:<path>`), mirroring the project layout + head's config files
60348
+ * so the base lints under the same rules as head. Reuses the shared,
60349
+ * zip-slip-guarded `materializeSourceTree`. Per-file read misses (e.g. a file
60350
+ * the PR added, absent at base) are skipped, which is exactly what we want:
60351
+ * a brand-new file has no base counterpart, so all its findings are "new".
60475
60352
  */
60476
60353
  const materializeBaselineFiles = (input) => runPromise(gen(function* () {
60477
60354
  const git = yield* Git;
60478
- const diffPlan = yield* git.baselineDiffPlan({
60355
+ return yield* materializeSourceTree({
60479
60356
  directory: input.directory,
60480
- ref: input.ref
60481
- });
60482
- const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
60483
- const headFiles = input.headFiles ?? diffPlan?.headFiles;
60484
- const untrackedFiles = diffPlan?.untrackedFiles ?? [];
60485
- if (baseFiles === void 0 || headFiles === void 0) return null;
60486
- const files = [...new Set([...input.files, ...baseFiles])];
60487
- const tree = yield* materializeSourceTree({
60488
- directory: input.directory,
60489
- files,
60357
+ files: input.files,
60490
60358
  tempDirectory: input.tempDirectory,
60491
60359
  readContent: (relativePath) => git.showRefContent({
60492
60360
  directory: input.directory,
60493
60361
  ref: input.ref,
60494
60362
  relativePath,
60495
60363
  options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
60496
- }).pipe(map$3((content) => content !== null && isMaterializableGitSource(content) ? content : null))
60364
+ })
60497
60365
  });
60498
- const materializedFiles = new Set(tree.materializedFiles);
60499
- return {
60500
- ...tree,
60501
- baseFiles,
60502
- headFiles,
60503
- isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
60504
- untrackedFiles
60505
- };
60506
60366
  }).pipe(provide(Git.layerNode)));
60507
60367
  /**
60508
60368
  * Resolves the commit a baseline scan should read base content from — the
@@ -60525,8 +60385,8 @@ const resolveMergeBaseRef = async (directory, ref) => {
60525
60385
  //#region src/cli/utils/read-source-line.ts
60526
60386
  /**
60527
60387
  * Builds a cached `(filePath, line) -> source text` reader rooted at
60528
- * `rootDirectory`, used as the fallback when construct-level diagnostic
60529
- * evidence cannot be read. Wraps core's
60388
+ * `rootDirectory`, used to fingerprint a diagnostic by the content of its
60389
+ * flagged line (see `computeDiagnosticDelta`). Wraps core's
60530
60390
  * `createNodeReadFileLinesSync` with per-file memoization + 1-indexed line
60531
60391
  * lookup; unreadable files / out-of-range lines return `null`.
60532
60392
  */
@@ -60541,338 +60401,6 @@ const createSourceLineReader = (rootDirectory) => {
60541
60401
  };
60542
60402
  };
60543
60403
  //#endregion
60544
- //#region src/cli/utils/read-diagnostic-evidence.ts
60545
- const SOURCE_EXTENSIONS = [
60546
- ".ts",
60547
- ".tsx",
60548
- ".js",
60549
- ".jsx",
60550
- ".mts",
60551
- ".cts",
60552
- ".mjs",
60553
- ".cjs"
60554
- ];
60555
- const COMPONENT_WRAPPER_NAMES = new Set(["memo", "forwardRef"]);
60556
- const getScriptKind = (filePath) => {
60557
- const extension = path.extname(filePath).toLowerCase();
60558
- if (extension === ".tsx") return ts.ScriptKind.TSX;
60559
- if (extension === ".jsx") return ts.ScriptKind.JSX;
60560
- if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return ts.ScriptKind.JS;
60561
- return ts.ScriptKind.TS;
60562
- };
60563
- const resolveSafeSourcePath = (rootDirectory, filePath) => {
60564
- try {
60565
- const resolvedRootDirectory = fs.realpathSync(rootDirectory);
60566
- const absolutePath = path.resolve(resolvedRootDirectory, filePath);
60567
- const relativePath = path.relative(resolvedRootDirectory, absolutePath);
60568
- if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
60569
- const stats = fs.lstatSync(absolutePath);
60570
- if (!stats.isFile() || stats.isSymbolicLink()) return null;
60571
- const realPath = fs.realpathSync(absolutePath);
60572
- const realRelativePath = path.relative(resolvedRootDirectory, realPath);
60573
- return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
60574
- } catch {
60575
- return null;
60576
- }
60577
- };
60578
- const readSourceRecord = (rootDirectory, filePath, state) => {
60579
- const normalizedFilePath = filePath.replace(/\\/g, "/");
60580
- if (state.recordByFilePath.has(normalizedFilePath)) return state.recordByFilePath.get(normalizedFilePath) ?? null;
60581
- const absolutePath = resolveSafeSourcePath(rootDirectory, normalizedFilePath);
60582
- if (absolutePath === null) {
60583
- state.recordByFilePath.set(normalizedFilePath, null);
60584
- return null;
60585
- }
60586
- try {
60587
- const sourceText = fs.readFileSync(absolutePath, "utf-8");
60588
- const record = {
60589
- absolutePath,
60590
- filePath: normalizedFilePath,
60591
- sourceFile: ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
60592
- sourceText
60593
- };
60594
- state.recordByFilePath.set(normalizedFilePath, record);
60595
- return record;
60596
- } catch {
60597
- state.recordByFilePath.set(normalizedFilePath, null);
60598
- return null;
60599
- }
60600
- };
60601
- const listSourceRecords = (rootDirectory, state) => {
60602
- if (state.sourceRecords !== null) return state.sourceRecords;
60603
- state.sourceRecords = listSourceFiles(rootDirectory).flatMap((filePath) => {
60604
- const record = readSourceRecord(rootDirectory, filePath, state);
60605
- return record === null ? [] : [record];
60606
- });
60607
- return state.sourceRecords;
60608
- };
60609
- const unwrapExpression = (expression) => {
60610
- let currentExpression = expression;
60611
- while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression)) currentExpression = currentExpression.expression;
60612
- return currentExpression;
60613
- };
60614
- const getCalleeName = (expression) => {
60615
- const unwrappedExpression = unwrapExpression(expression);
60616
- if (ts.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
60617
- if (ts.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
60618
- return null;
60619
- };
60620
- const isComponentWrapperCall = (node) => {
60621
- const wrapperName = getCalleeName(node.expression);
60622
- return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
60623
- };
60624
- const getFunctionName = (node) => {
60625
- if (ts.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
60626
- if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
60627
- let componentExpression = node;
60628
- while (true) {
60629
- const parent = componentExpression.parent;
60630
- if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) {
60631
- componentExpression = parent;
60632
- continue;
60633
- }
60634
- if (ts.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
60635
- componentExpression = parent;
60636
- continue;
60637
- }
60638
- if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
60639
- return node.name?.text ?? null;
60640
- }
60641
- }
60642
- return null;
60643
- };
60644
- const isComponentFunction = (node) => ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
60645
- const isDefaultExportedComponent = (record, node, componentName) => {
60646
- if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true;
60647
- return record.sourceFile.statements.some((statement) => ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === componentName);
60648
- };
60649
- const findEnclosingComponent = (record, diagnostic) => {
60650
- if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
60651
- const lineIndex = diagnostic.line - 1;
60652
- const lineStarts = record.sourceFile.getLineStarts();
60653
- const lineStart = lineStarts[lineIndex];
60654
- if (lineStart === void 0) return null;
60655
- const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
60656
- const lineEnd = Math.max(lineStart, nextLineStart - 1);
60657
- const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
60658
- const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
60659
- let component = null;
60660
- let componentWidth = Number.POSITIVE_INFINITY;
60661
- const visit = (node) => {
60662
- if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
60663
- if (isComponentFunction(node)) {
60664
- const name = getFunctionName(node);
60665
- const width = node.end - node.getStart(record.sourceFile);
60666
- if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
60667
- component = {
60668
- isDefaultExport: isDefaultExportedComponent(record, node, name),
60669
- name,
60670
- node,
60671
- record
60672
- };
60673
- componentWidth = width;
60674
- }
60675
- }
60676
- ts.forEachChild(node, visit);
60677
- };
60678
- visit(record.sourceFile);
60679
- return component;
60680
- };
60681
- const moduleSpecifierTargetsComponent = (callerRecord, moduleSpecifier, componentRecord) => {
60682
- if (!moduleSpecifier.startsWith(".")) return false;
60683
- const unresolvedPath = path.resolve(path.dirname(callerRecord.absolutePath), moduleSpecifier);
60684
- const specifiedExtension = path.extname(unresolvedPath).toLowerCase();
60685
- const sourcePathWithoutExtension = SOURCE_EXTENSIONS.includes(specifiedExtension) ? unresolvedPath.slice(0, -specifiedExtension.length) : unresolvedPath;
60686
- return [
60687
- unresolvedPath,
60688
- ...SOURCE_EXTENSIONS.map((extension) => `${sourcePathWithoutExtension}${extension}`),
60689
- ...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
60690
- ].includes(componentRecord.absolutePath);
60691
- };
60692
- const getComponentTagNames = (record, component) => {
60693
- if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
60694
- const tagNames = /* @__PURE__ */ new Set();
60695
- for (const statement of record.sourceFile.statements) {
60696
- if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
60697
- const importClause = statement.importClause;
60698
- if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
60699
- const namedBindings = importClause?.namedBindings;
60700
- if (namedBindings !== void 0 && ts.isNamedImports(namedBindings)) {
60701
- for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
60702
- }
60703
- }
60704
- return tagNames;
60705
- };
60706
- const getTransparentCall = (functionNode) => {
60707
- if (functionNode.body === void 0) return null;
60708
- if (!ts.isBlock(functionNode.body)) {
60709
- const expression = unwrapExpression(functionNode.body);
60710
- return ts.isCallExpression(expression) ? expression : null;
60711
- }
60712
- if (functionNode.body.statements.length !== 1) return null;
60713
- const statement = functionNode.body.statements[0];
60714
- if (statement === void 0) return null;
60715
- const expression = ts.isExpressionStatement(statement) || ts.isReturnStatement(statement) ? statement.expression : void 0;
60716
- if (expression === void 0) return null;
60717
- const unwrappedExpression = unwrapExpression(expression);
60718
- return ts.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
60719
- };
60720
- const resolveTransparentTarget = (functionNode) => {
60721
- const call = getTransparentCall(functionNode);
60722
- if (call === null) return null;
60723
- const target = unwrapExpression(call.expression);
60724
- if (!ts.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
60725
- for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
60726
- const argument = call.arguments[parameterIndex];
60727
- const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
60728
- if (!ts.isIdentifier(parameter.name) || unwrappedArgument === null || !ts.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
60729
- }
60730
- return target.text;
60731
- };
60732
- const getForwardingFunction = (declaration) => {
60733
- if (ts.isFunctionDeclaration(declaration)) return declaration;
60734
- if (!ts.isVariableDeclaration(declaration) || declaration.initializer === void 0) return null;
60735
- const initializer = unwrapExpression(declaration.initializer);
60736
- if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) return initializer;
60737
- if (ts.isCallExpression(initializer) && getCalleeName(initializer.expression) === "useCallback") {
60738
- const callback = initializer.arguments[0];
60739
- if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) return callback;
60740
- }
60741
- return null;
60742
- };
60743
- const getDeclaredBindingName = (node) => {
60744
- if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isFunctionDeclaration(node)) && node.name !== void 0 && ts.isIdentifier(node.name)) return node.name.text;
60745
- if (ts.isImportClause(node)) return node.name?.text ?? null;
60746
- if (ts.isImportSpecifier(node) || ts.isNamespaceImport(node)) return node.name.text;
60747
- return null;
60748
- };
60749
- const findBindingScope = (node) => {
60750
- let currentNode = node;
60751
- while (currentNode !== void 0) {
60752
- if (ts.isParameter(currentNode) && ts.isFunctionLike(currentNode.parent)) return currentNode.parent;
60753
- if (ts.isBlock(currentNode) || ts.isModuleBlock(currentNode) || ts.isSourceFile(currentNode)) return currentNode;
60754
- currentNode = currentNode.parent;
60755
- }
60756
- return node.getSourceFile();
60757
- };
60758
- const findBindingDeclaration = (record, binding) => {
60759
- const usagePosition = binding.getStart(record.sourceFile);
60760
- const candidates = [];
60761
- const visit = (node) => {
60762
- if (getDeclaredBindingName(node) === binding.text) {
60763
- const scope = findBindingScope(node);
60764
- const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
60765
- const isVisibleDeclaration = ts.isFunctionDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isImportClause(node) || ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || node.getStart(record.sourceFile) < usagePosition;
60766
- if (isVisibleScope && isVisibleDeclaration) candidates.push({
60767
- declaration: node,
60768
- scope
60769
- });
60770
- }
60771
- ts.forEachChild(node, visit);
60772
- };
60773
- visit(record.sourceFile);
60774
- let nearestCandidate = null;
60775
- let nearestScopeWidth = Number.POSITIVE_INFINITY;
60776
- let isNearestScopeAmbiguous = false;
60777
- for (const candidate of candidates) {
60778
- const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
60779
- if (scopeWidth < nearestScopeWidth) {
60780
- nearestCandidate = candidate;
60781
- nearestScopeWidth = scopeWidth;
60782
- isNearestScopeAmbiguous = false;
60783
- } else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
60784
- }
60785
- if (isNearestScopeAmbiguous) return null;
60786
- const declaration = nearestCandidate?.declaration;
60787
- return declaration !== void 0 && (ts.isVariableDeclaration(declaration) || ts.isFunctionDeclaration(declaration)) ? declaration : null;
60788
- };
60789
- const resolveHandlerBinding = (record, binding) => {
60790
- const declaration = findBindingDeclaration(record, binding);
60791
- if (declaration === null) return binding.text;
60792
- const forwardingFunction = getForwardingFunction(declaration);
60793
- const target = forwardingFunction === null ? null : resolveTransparentTarget(forwardingFunction);
60794
- return target === null || target === binding.text ? binding.text : target;
60795
- };
60796
- const getJsxTagName = (node) => ts.isIdentifier(node.tagName) ? node.tagName.text : null;
60797
- const getHandlerBinding = (node, propName) => {
60798
- for (const property of node.attributes.properties) {
60799
- if (!ts.isJsxAttribute(property) || property.name.getText() !== propName) continue;
60800
- const initializer = property.initializer;
60801
- if (initializer === void 0 || !ts.isJsxExpression(initializer)) return null;
60802
- if (initializer.expression === void 0) return null;
60803
- const expression = unwrapExpression(initializer.expression);
60804
- return ts.isIdentifier(expression) ? expression : null;
60805
- }
60806
- return null;
60807
- };
60808
- const getForwardedHandlerAliases = (rootDirectory, component, propNames, state) => {
60809
- const cacheKey = `${component.record.filePath}\0${component.name}\0${[...propNames].sort().join("\0")}`;
60810
- const cachedAliases = state.aliasesByComponent.get(cacheKey);
60811
- if (cachedAliases !== void 0) return cachedAliases;
60812
- const bindingsByPropName = /* @__PURE__ */ new Map();
60813
- const unresolvedPropNames = /* @__PURE__ */ new Set();
60814
- let callsiteCount = 0;
60815
- for (const record of listSourceRecords(rootDirectory, state)) {
60816
- const tagNames = getComponentTagNames(record, component);
60817
- if (tagNames.size === 0) continue;
60818
- const visit = (node) => {
60819
- if (record === component.record && node === component.node) return;
60820
- if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
60821
- const tagName = getJsxTagName(node);
60822
- if (tagName !== null && tagNames.has(tagName)) {
60823
- callsiteCount += 1;
60824
- for (const propName of propNames) {
60825
- const binding = getHandlerBinding(node, propName);
60826
- if (binding === null) {
60827
- unresolvedPropNames.add(propName);
60828
- continue;
60829
- }
60830
- const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
60831
- bindings.add(resolveHandlerBinding(record, binding));
60832
- bindingsByPropName.set(propName, bindings);
60833
- }
60834
- }
60835
- }
60836
- ts.forEachChild(node, visit);
60837
- };
60838
- visit(record.sourceFile);
60839
- }
60840
- const aliases = /* @__PURE__ */ new Map();
60841
- if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
60842
- if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
60843
- const [bindingName] = bindings;
60844
- if (bindingName !== void 0) aliases.set(propName, bindingName);
60845
- }
60846
- state.aliasesByComponent.set(cacheKey, aliases);
60847
- return aliases;
60848
- };
60849
- const normalizeForwardedHandlers = (evidence, aliases) => {
60850
- let normalizedEvidence = evidence;
60851
- for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
60852
- return normalizedEvidence;
60853
- };
60854
- const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
60855
- const state = {
60856
- aliasesByComponent: /* @__PURE__ */ new Map(),
60857
- recordByFilePath: /* @__PURE__ */ new Map(),
60858
- sourceRecords: null
60859
- };
60860
- return (diagnostic) => {
60861
- const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
60862
- if (record === null || !Number.isInteger(diagnostic.line)) return null;
60863
- const sourceLines = record.sourceText.split(/\r?\n/);
60864
- const startLineIndex = Math.max(0, diagnostic.line - 1);
60865
- const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
60866
- const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
60867
- if (!options.resolveForwardedHandlers) return evidence;
60868
- const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
60869
- if (propNames.size === 0) return evidence;
60870
- const component = findEnclosingComponent(record, diagnostic);
60871
- if (component === null) return evidence;
60872
- return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
60873
- };
60874
- };
60875
- //#endregion
60876
60404
  //#region src/cli/utils/build-no-score-message.ts
60877
60405
  const ENTERPRISE_CONTACT_HINT = `Want something custom to your company? Contact us at ${ENTERPRISE_CONTACT_URL}.`;
60878
60406
  const buildNoScoreMessage = (isScoreDisabled) => {
@@ -62116,8 +61644,6 @@ const runBaselineComparison = async (params) => {
62116
61644
  directory: params.directory,
62117
61645
  ref: params.baselineRef,
62118
61646
  files: params.options.includePaths,
62119
- baseFiles: params.baseFiles,
62120
- headFiles: params.headFiles,
62121
61647
  tempDirectory
62122
61648
  }).catch((error) => {
62123
61649
  rmSync(tempDirectory, {
@@ -62126,24 +61652,7 @@ const runBaselineComparison = async (params) => {
62126
61652
  });
62127
61653
  throw error;
62128
61654
  });
62129
- if (snapshot === null) {
62130
- rmSync(tempDirectory, {
62131
- recursive: true,
62132
- force: true
62133
- });
62134
- return null;
62135
- }
62136
61655
  try {
62137
- if (!snapshot.isComplete) return null;
62138
- const analyzedHeadFiles = new Set(params.headAnalyzedFiles.map(toForwardSlashes));
62139
- const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
62140
- const trackedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
62141
- const expectedHeadFiles = new Set(trackedHeadFiles);
62142
- for (const filePath of params.options.includePaths) {
62143
- const normalizedFilePath = toForwardSlashes(filePath);
62144
- if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
62145
- }
62146
- if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
62147
61656
  const baseLayers = buildRuntimeLayers({
62148
61657
  directory: snapshot.tempDirectory,
62149
61658
  hasConfigOverride: true,
@@ -62159,7 +61668,7 @@ const runBaselineComparison = async (params) => {
62159
61668
  });
62160
61669
  const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
62161
61670
  directory: snapshot.tempDirectory,
62162
- includePaths: snapshot.materializedFiles,
61671
+ includePaths: params.options.includePaths,
62163
61672
  customRulesOnly: params.options.customRulesOnly,
62164
61673
  respectInlineDisables: params.options.respectInlineDisables,
62165
61674
  warnings: params.options.warnings,
@@ -62176,22 +61685,18 @@ const runBaselineComparison = async (params) => {
62176
61685
  deadlineEpochMs: params.deadlineEpochMs ?? void 0
62177
61686
  }, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
62178
61687
  if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
62179
- const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
62180
61688
  const delta = computeDiagnosticDelta({
62181
61689
  headDiagnostics: params.headDiagnostics,
62182
61690
  baseDiagnostics: baseOutput.diagnostics,
62183
61691
  readHeadLine: createSourceLineReader(params.directory),
62184
- readBaseLine: createSourceLineReader(snapshot.tempDirectory),
62185
- readHeadEvidence: createDiagnosticEvidenceReader(params.directory, { resolveForwardedHandlers: true }),
62186
- readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
61692
+ readBaseLine: createSourceLineReader(snapshot.tempDirectory)
62187
61693
  });
62188
61694
  return {
62189
61695
  displayDiagnostics: delta.newDiagnostics,
62190
61696
  baselineDelta: {
62191
61697
  baseRef: params.baselineRef,
62192
- fixedCount: hasUnscannedUntrackedSourceFiles ? 0 : delta.fixedCount,
62193
- baseTotalCount: baseOutput.diagnostics.length,
62194
- crossFileMatchCount: delta.crossFileMatchCount
61698
+ fixedCount: delta.fixedCount,
61699
+ baseTotalCount: baseOutput.diagnostics.length
62195
61700
  }
62196
61701
  };
62197
61702
  } finally {
@@ -62298,9 +61803,6 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
62298
61803
  headDiagnostics: output.diagnostics,
62299
61804
  resolvedNodeBinaryPath,
62300
61805
  baselineRef: options.baseline.ref,
62301
- baseFiles: options.baseline.baseFiles,
62302
- headFiles: options.baseline.headFiles,
62303
- headAnalyzedFiles: output.analyzedFiles,
62304
61806
  deadlineEpochMs
62305
61807
  });
62306
61808
  if (comparison) {
@@ -64585,25 +64087,20 @@ const resolveProjectRelativeDirectory = (rootDirectory, projectDirectory) => {
64585
64087
  return relativeProjectDirectory;
64586
64088
  };
64587
64089
  //#endregion
64588
- //#region src/cli/utils/resolve-project-source-file-paths.ts
64589
- const resolveProjectSourceFilePaths = (rootDirectory, projectDirectory, filePaths) => {
64090
+ //#region src/cli/utils/resolve-project-diff-include-paths.ts
64091
+ const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
64590
64092
  const relativeProjectDirectory = resolveProjectRelativeDirectory(rootDirectory, projectDirectory);
64591
64093
  if (relativeProjectDirectory === null) return [];
64592
- const sourceFilePaths = filterSourceFiles([...filePaths]);
64593
- if (relativeProjectDirectory.length === 0) return sourceFilePaths;
64094
+ const changedSourceFiles = filterSourceFiles(diffInfo.changedFiles);
64095
+ if (relativeProjectDirectory.length === 0) return changedSourceFiles;
64594
64096
  const projectPrefix = `${relativeProjectDirectory}/`;
64595
- return sourceFilePaths.flatMap((filePath) => {
64097
+ return changedSourceFiles.flatMap((filePath) => {
64596
64098
  const normalizedFilePath = toForwardSlashes(filePath);
64597
64099
  if (!normalizedFilePath.startsWith(projectPrefix)) return [];
64598
64100
  const projectRelativePath = normalizedFilePath.slice(projectPrefix.length);
64599
64101
  return projectRelativePath.length > 0 ? [projectRelativePath] : [];
64600
64102
  });
64601
64103
  };
64602
- //#endregion
64603
- //#region src/cli/utils/resolve-project-diff-include-paths.ts
64604
- const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
64605
- return resolveProjectSourceFilePaths(rootDirectory, projectDirectory, diffInfo.changedFiles);
64606
- };
64607
64104
  /**
64608
64105
  * Re-keys repo-relative changed line ranges (from `getChangedLineRanges`) to
64609
64106
  * the scanned project, mirroring `resolveProjectDiffIncludePaths`'s boundary
@@ -65142,7 +64639,6 @@ const inspectAction = async (directory, flags) => {
65142
64639
  const isDiffMode = scope !== "full";
65143
64640
  const comparisonBaseRef = isDiffMode && diffInfo && !diffInfo.isCurrentChanges ? diffInfo.baseSha ? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseSha) : diffInfo.diffBaseRef ?? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseBranch) : null;
65144
64641
  const baselineRef = scope === "changed" ? comparisonBaseRef : null;
65145
- const baselineDiffPlan = baselineRef === null ? null : await getBaselineDiffPlan(resolvedDirectory, baselineRef);
65146
64642
  const linesBaseRef = diffInfo?.isCurrentChanges ? "HEAD" : comparisonBaseRef;
65147
64643
  const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
65148
64644
  directory: resolvedDirectory,
@@ -65173,13 +64669,10 @@ const inspectAction = async (directory, flags) => {
65173
64669
  const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
65174
64670
  let includePaths;
65175
64671
  let supplyChainManifestChanged = false;
65176
- const projectBaselineBaseFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.baseFiles);
65177
- const projectBaselineHeadFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.headFiles);
65178
64672
  if (isDiffMode) {
65179
64673
  const changedSourceFiles = diffInfo === null ? [] : resolveProjectDiffIncludePaths(resolvedDirectory, scanDirectory, diffInfo);
65180
64674
  supplyChainManifestChanged = supplyChainEnabled && diffInfo !== null && projectManifestChanged(resolvedDirectory, scanDirectory, diffInfo);
65181
- const hasBaselineOnlyFiles = (projectBaselineBaseFiles?.length ?? 0) > 0;
65182
- if (changedSourceFiles.length === 0 && !supplyChainManifestChanged && !hasBaselineOnlyFiles) {
64675
+ if (changedSourceFiles.length === 0 && !supplyChainManifestChanged) {
65183
64676
  if (!isQuiet) {
65184
64677
  cliLogger.dim(`No changed source files in ${scanDirectory}, skipping.`);
65185
64678
  cliLogger.break();
@@ -65187,7 +64680,6 @@ const inspectAction = async (directory, flags) => {
65187
64680
  return null;
65188
64681
  }
65189
64682
  includePaths = [...changedSourceFiles];
65190
- if (includePaths.length === 0 && hasBaselineOnlyFiles) includePaths.push(...projectBaselineBaseFiles ?? []);
65191
64683
  if (supplyChainManifestChanged) includePaths.push("package.json");
65192
64684
  }
65193
64685
  if (!isQuiet && !isMultiProject) cliLogger.dim(" ");
@@ -65199,11 +64691,7 @@ const inspectAction = async (directory, flags) => {
65199
64691
  configSourceDirectory: projectConfigSourceDirectory ?? void 0,
65200
64692
  suppressRendering: isMultiProject,
65201
64693
  concurrentScan: isMultiProject,
65202
- baseline: baselineRef !== null && projectBaselineBaseFiles !== null && projectBaselineHeadFiles !== null ? {
65203
- ref: baselineRef,
65204
- baseFiles: projectBaselineBaseFiles,
65205
- headFiles: projectBaselineHeadFiles
65206
- } : void 0,
64694
+ baseline: baselineRef ? { ref: baselineRef } : void 0,
65207
64695
  changedLineRanges: scope === "lines" && changedLineRanges !== null ? resolveProjectChangedLineRanges(resolvedDirectory, scanDirectory, changedLineRanges) : void 0,
65208
64696
  supplyChainManifestChanged
65209
64697
  });
@@ -66403,4 +65891,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
66403
65891
  export {};
66404
65892
 
66405
65893
  //# sourceMappingURL=cli.js.map
66406
- //# debugId=24153a3f-aef9-53fd-8dff-9b016a9613fb
65894
+ //# debugId=941df082-c8b4-5084-baae-33ec22d4e55f
package/dist/index.d.ts CHANGED
@@ -9030,12 +9030,11 @@ interface Diagnostic {
9030
9030
  * Set when the finding's identity is the flagged element itself (a missing
9031
9031
  * attribute, a wrong element) rather than the flagged line's text — every
9032
9032
  * Accessibility-category finding, plus rules that opt in via their
9033
- * `matchByOccurrence` metadata flag. After strict evidence matching,
9034
- * `computeDiagnosticDelta` may match these by same-file `(plugin/rule,
9035
- * message)` occurrence count, so reformatting the flagged element doesn't
9036
- * reclassify a pre-existing finding as new. Resolved at diagnostic creation,
9037
- * where rule metadata is available. Absent means diagnosed-source-sensitive
9038
- * matching (the default).
9033
+ * `matchByOccurrence` metadata flag. `computeDiagnosticDelta` matches these
9034
+ * by `(filePath, plugin/rule)` occurrence count, so reformatting the flagged
9035
+ * line doesn't reclassify a pre-existing finding as new. Resolved at
9036
+ * diagnostic creation, where rule metadata is available. Absent means
9037
+ * line-text-sensitive matching (the default).
9039
9038
  */
9040
9039
  matchByOccurrence?: boolean;
9041
9040
  /**
@@ -9848,8 +9847,7 @@ interface InspectResult {
9848
9847
  baselineDelta?: {
9849
9848
  /** The commit the base content was read from (resolved merge-base). */baseRef: string; /** Findings present at base but gone at head — resolved by the change. */
9850
9849
  fixedCount: number; /** Total findings at base (over the same files), for context. */
9851
- baseTotalCount: number; /** Pre-existing findings matched after moving to a different file. */
9852
- crossFileMatchCount?: number;
9850
+ baseTotalCount: number;
9853
9851
  };
9854
9852
  }
9855
9853
  /**
@@ -10265,7 +10263,15 @@ declare const buildJsonReport: (input: BuildJsonReportInput) => JsonReportV3$1;
10265
10263
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
10266
10264
  */
10267
10265
  declare const getDiffInfo: (directory: string, explicitBaseBranch?: string, includeUntracked?: boolean) => Promise<DiffInfo | null>;
10268
- declare const filterSourceFiles: (filePaths: ReadonlyArray<string>) => string[];
10266
+ declare const filterSourceFiles: (filePaths: string[]) => string[];
10267
+ /**
10268
+ * Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
10269
+ * `files` with `--unified=0` against `baseRef` (or the index when `cached`),
10270
+ * returning per-file changed line ranges relative to `directory`. Returns
10271
+ * `null` when the ranges can't be computed (git unavailable / unsafe ref /
10272
+ * non-zero exit) so the caller degrades to file-level scope; an empty array
10273
+ * means git succeeded but the files added no lines.
10274
+ */
10269
10275
  //#endregion
10270
10276
  //#region src/summarize-diagnostics.d.ts
10271
10277
  declare const summarizeDiagnostics: (diagnostics: Diagnostic[], worstScore?: number | null, worstScoreLabel?: string | null) => JsonReportSummary; //#endregion
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]="14f17993-c8fc-5c43-a518-2b2055f5d836")}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]="3121a4e4-8a4a-55ea-ade8-47d0bf576a2f")}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";
@@ -39090,35 +39090,6 @@ const parseGithubViewerPermission = (stdout) => {
39090
39090
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
39091
39091
  };
39092
39092
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
39093
- const parseBaselineDiffPlan = (value) => {
39094
- const entries = splitNullSeparated(value);
39095
- const baseFiles = /* @__PURE__ */ new Set();
39096
- const headFiles = /* @__PURE__ */ new Set();
39097
- for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
39098
- const status = entries[entryIndex];
39099
- const filePath = entries[entryIndex + 1];
39100
- if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
39101
- if (status === "A") {
39102
- headFiles.add(filePath);
39103
- continue;
39104
- }
39105
- if (status === "D") {
39106
- baseFiles.add(filePath);
39107
- continue;
39108
- }
39109
- if (status === "M" || status === "T") {
39110
- baseFiles.add(filePath);
39111
- headFiles.add(filePath);
39112
- continue;
39113
- }
39114
- return null;
39115
- }
39116
- return {
39117
- baseFiles: [...baseFiles],
39118
- headFiles: [...headFiles],
39119
- untrackedFiles: []
39120
- };
39121
- };
39122
39093
  const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
39123
39094
  /**
39124
39095
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
@@ -39321,45 +39292,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
39321
39292
  githubViewerPermission,
39322
39293
  branchExists,
39323
39294
  mergeBase,
39324
- baselineDiffPlan: (input) => {
39325
- if (!isSafeGitRevision(input.ref)) return succeed$2(null);
39326
- return gen(function* () {
39327
- const unmerged = yield* runGit(input.directory, [
39328
- "diff",
39329
- "--no-ext-diff",
39330
- "-z",
39331
- "--name-only",
39332
- "--diff-filter=U",
39333
- "--relative"
39334
- ]);
39335
- if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
39336
- const result = yield* runGit(input.directory, [
39337
- "diff",
39338
- "--no-ext-diff",
39339
- "--no-textconv",
39340
- "--no-renames",
39341
- "-z",
39342
- "--name-status",
39343
- "--relative",
39344
- input.ref
39345
- ]);
39346
- if (result.status !== 0) return null;
39347
- const plan = parseBaselineDiffPlan(result.stdout);
39348
- if (plan === null) return null;
39349
- const untracked = yield* runGit(input.directory, [
39350
- "ls-files",
39351
- "--others",
39352
- "--exclude-standard",
39353
- "-z"
39354
- ]);
39355
- if (untracked.status !== 0) return null;
39356
- return {
39357
- baseFiles: plan.baseFiles,
39358
- headFiles: plan.headFiles,
39359
- untrackedFiles: splitNullSeparated(untracked.stdout)
39360
- };
39361
- }).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
39362
- },
39363
39295
  diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
39364
39296
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
39365
39297
  if (explicitBaseBranch !== void 0) {
@@ -39509,7 +39441,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
39509
39441
  githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
39510
39442
  branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
39511
39443
  mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
39512
- baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
39513
39444
  diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
39514
39445
  stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
39515
39446
  showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
@@ -40847,17 +40778,12 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
40847
40778
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
40848
40779
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
40849
40780
  const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
40850
- const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
40851
40781
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
40852
40782
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
40853
40783
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40854
40784
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
40855
40785
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
40856
40786
  };
40857
- const buildSetStateInEffectMessage = (reasonSummary) => {
40858
- const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40859
- return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
40860
- };
40861
40787
  const PLUGIN_CATEGORY_MAP = {
40862
40788
  react: "Bugs",
40863
40789
  "react-hooks": "Bugs",
@@ -40915,10 +40841,6 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
40915
40841
  message: REACT_ERROR_BOUNDARY_MESSAGE,
40916
40842
  help: reasonDetail || help
40917
40843
  };
40918
- if (rule === "set-state-in-effect") return {
40919
- message: buildSetStateInEffectMessage(reasonSummary.trim()),
40920
- help: reasonDetail || help
40921
- };
40922
40844
  return {
40923
40845
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
40924
40846
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -43244,19 +43166,12 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
43244
43166
  */
43245
43167
  const materializeSourceTree = (input) => gen(function* () {
43246
43168
  const materializedFiles = [];
43247
- const unmaterializedFiles = [];
43248
43169
  const resolvedTempDirectory = path.resolve(input.tempDirectory);
43249
43170
  for (const relativePath of input.files) {
43250
43171
  const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
43251
- if (content === null) {
43252
- unmaterializedFiles.push(relativePath);
43253
- continue;
43254
- }
43172
+ if (content === null) continue;
43255
43173
  const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
43256
- if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
43257
- unmaterializedFiles.push(relativePath);
43258
- continue;
43259
- }
43174
+ if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
43260
43175
  yield* sync(() => {
43261
43176
  fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
43262
43177
  fs.writeFileSync(candidateTargetPath, content);
@@ -43273,7 +43188,6 @@ const materializeSourceTree = (input) => gen(function* () {
43273
43188
  return {
43274
43189
  tempDirectory: input.tempDirectory,
43275
43190
  materializedFiles,
43276
- unmaterializedFiles,
43277
43191
  cleanup: () => {
43278
43192
  try {
43279
43193
  fs.rmSync(input.tempDirectory, {
@@ -43837,4 +43751,4 @@ const toJsonReport = (result, options) => buildJsonReport({
43837
43751
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
43838
43752
 
43839
43753
  //# sourceMappingURL=index.js.map
43840
- //# debugId=14f17993-c8fc-5c43-a518-2b2055f5d836
43754
+ //# debugId=3121a4e4-8a4a-55ea-ade8-47d0bf576a2f
package/dist/lsp.js CHANGED
@@ -39068,35 +39068,6 @@ const parseGithubViewerPermission = (stdout) => {
39068
39068
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
39069
39069
  };
39070
39070
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
39071
- const parseBaselineDiffPlan = (value) => {
39072
- const entries = splitNullSeparated(value);
39073
- const baseFiles = /* @__PURE__ */ new Set();
39074
- const headFiles = /* @__PURE__ */ new Set();
39075
- for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
39076
- const status = entries[entryIndex];
39077
- const filePath = entries[entryIndex + 1];
39078
- if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
39079
- if (status === "A") {
39080
- headFiles.add(filePath);
39081
- continue;
39082
- }
39083
- if (status === "D") {
39084
- baseFiles.add(filePath);
39085
- continue;
39086
- }
39087
- if (status === "M" || status === "T") {
39088
- baseFiles.add(filePath);
39089
- headFiles.add(filePath);
39090
- continue;
39091
- }
39092
- return null;
39093
- }
39094
- return {
39095
- baseFiles: [...baseFiles],
39096
- headFiles: [...headFiles],
39097
- untrackedFiles: []
39098
- };
39099
- };
39100
39071
  const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
39101
39072
  /**
39102
39073
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
@@ -39299,45 +39270,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
39299
39270
  githubViewerPermission,
39300
39271
  branchExists,
39301
39272
  mergeBase,
39302
- baselineDiffPlan: (input) => {
39303
- if (!isSafeGitRevision(input.ref)) return succeed$2(null);
39304
- return gen(function* () {
39305
- const unmerged = yield* runGit(input.directory, [
39306
- "diff",
39307
- "--no-ext-diff",
39308
- "-z",
39309
- "--name-only",
39310
- "--diff-filter=U",
39311
- "--relative"
39312
- ]);
39313
- if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
39314
- const result = yield* runGit(input.directory, [
39315
- "diff",
39316
- "--no-ext-diff",
39317
- "--no-textconv",
39318
- "--no-renames",
39319
- "-z",
39320
- "--name-status",
39321
- "--relative",
39322
- input.ref
39323
- ]);
39324
- if (result.status !== 0) return null;
39325
- const plan = parseBaselineDiffPlan(result.stdout);
39326
- if (plan === null) return null;
39327
- const untracked = yield* runGit(input.directory, [
39328
- "ls-files",
39329
- "--others",
39330
- "--exclude-standard",
39331
- "-z"
39332
- ]);
39333
- if (untracked.status !== 0) return null;
39334
- return {
39335
- baseFiles: plan.baseFiles,
39336
- headFiles: plan.headFiles,
39337
- untrackedFiles: splitNullSeparated(untracked.stdout)
39338
- };
39339
- }).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
39340
- },
39341
39273
  diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
39342
39274
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
39343
39275
  if (explicitBaseBranch !== void 0) {
@@ -39487,7 +39419,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
39487
39419
  githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
39488
39420
  branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
39489
39421
  mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
39490
- baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
39491
39422
  diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
39492
39423
  stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
39493
39424
  showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
@@ -40825,17 +40756,12 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
40825
40756
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
40826
40757
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
40827
40758
  const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
40828
- const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
40829
40759
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
40830
40760
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
40831
40761
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40832
40762
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
40833
40763
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
40834
40764
  };
40835
- const buildSetStateInEffectMessage = (reasonSummary) => {
40836
- const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40837
- return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
40838
- };
40839
40765
  const PLUGIN_CATEGORY_MAP = {
40840
40766
  react: "Bugs",
40841
40767
  "react-hooks": "Bugs",
@@ -40893,10 +40819,6 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
40893
40819
  message: REACT_ERROR_BOUNDARY_MESSAGE,
40894
40820
  help: reasonDetail || help
40895
40821
  };
40896
- if (rule === "set-state-in-effect") return {
40897
- message: buildSetStateInEffectMessage(reasonSummary.trim()),
40898
- help: reasonDetail || help
40899
- };
40900
40822
  return {
40901
40823
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
40902
40824
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -43222,19 +43144,12 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
43222
43144
  */
43223
43145
  const materializeSourceTree = (input) => gen(function* () {
43224
43146
  const materializedFiles = [];
43225
- const unmaterializedFiles = [];
43226
43147
  const resolvedTempDirectory = path.resolve(input.tempDirectory);
43227
43148
  for (const relativePath of input.files) {
43228
43149
  const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
43229
- if (content === null) {
43230
- unmaterializedFiles.push(relativePath);
43231
- continue;
43232
- }
43150
+ if (content === null) continue;
43233
43151
  const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
43234
- if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
43235
- unmaterializedFiles.push(relativePath);
43236
- continue;
43237
- }
43152
+ if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
43238
43153
  yield* sync(() => {
43239
43154
  fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
43240
43155
  fs.writeFileSync(candidateTargetPath, content);
@@ -43251,7 +43166,6 @@ const materializeSourceTree = (input) => gen(function* () {
43251
43166
  return {
43252
43167
  tempDirectory: input.tempDirectory,
43253
43168
  materializedFiles,
43254
- unmaterializedFiles,
43255
43169
  cleanup: () => {
43256
43170
  try {
43257
43171
  fs.rmSync(input.tempDirectory, {
@@ -45457,5 +45371,5 @@ const startLanguageServer = () => {
45457
45371
  };
45458
45372
  //#endregion
45459
45373
  export { startLanguageServer };
45460
- !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]="ecc1704a-4f8c-583e-93b2-78c6feb9cfd6")}catch(e){}}();
45461
- //# debugId=ecc1704a-4f8c-583e-93b2-78c6feb9cfd6
45374
+ !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]="0f56accc-b639-5b90-a1a3-5faa727907dc")}catch(e){}}();
45375
+ //# debugId=0f56accc-b639-5b90-a1a3-5faa727907dc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.8.1-dev.fe241a9",
3
+ "version": "0.8.1",
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.8.1",
69
- "oxlint-plugin-react-doctor": "0.8.1-dev.fe241a9"
69
+ "oxlint-plugin-react-doctor": "0.8.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/babel__code-frame": "^7.27.0",