react-doctor 0.7.9-dev.f441f59 → 0.8.0-dev.c0d9d38

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]="5c4df375-a5c4-57d2-9076-4e54cbb8f3bd")}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]="4433c20d-e5c5-50db-ab5b-0ff72ef6f226")}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";
@@ -36242,7 +36242,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
36242
36242
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
36243
36243
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
36244
36244
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
36245
- const CORE_PACKAGE_VERSION = "0.7.9";
36245
+ const CORE_PACKAGE_VERSION = "0.8.0";
36246
36246
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
36247
36247
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
36248
36248
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -36252,6 +36252,53 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
36252
36252
  const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
36253
36253
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
36254
36254
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
36255
+ const lastSourceMappingUrl = (fileContent) => {
36256
+ const lastReference = [...fileContent.matchAll(/\/\/# sourceMappingURL=(\S+)/g)].at(-1);
36257
+ return lastReference ? lastReference[1] : null;
36258
+ };
36259
+ const mapTargetsSource = (mapContent, input) => {
36260
+ const parsedMap = JSON.parse(mapContent);
36261
+ if (typeof parsedMap !== "object" || parsedMap === null) return false;
36262
+ const { file, sources } = parsedMap;
36263
+ if (file !== input.expectedFile) return false;
36264
+ if (!Array.isArray(sources) || sources.length !== 1 || typeof sources[0] !== "string") return false;
36265
+ return Path.posix.normalize(Path.posix.join(input.mapDirectory, sources[0])) === input.sourcePath;
36266
+ };
36267
+ const quartetProvesEmit = (stem, sourcePath, readFileText) => {
36268
+ try {
36269
+ const emitBaseName = Path.posix.basename(stem);
36270
+ const mapDirectory = Path.posix.dirname(stem);
36271
+ return lastSourceMappingUrl(readFileText(`${stem}.js`)) === `${emitBaseName}.js.map` && lastSourceMappingUrl(readFileText(`${stem}.d.ts`)) === `${emitBaseName}.d.ts.map` && mapTargetsSource(readFileText(`${stem}.js.map`), {
36272
+ expectedFile: `${emitBaseName}.js`,
36273
+ mapDirectory,
36274
+ sourcePath
36275
+ }) && mapTargetsSource(readFileText(`${stem}.d.ts.map`), {
36276
+ expectedFile: `${emitBaseName}.d.ts`,
36277
+ mapDirectory,
36278
+ sourcePath
36279
+ });
36280
+ } catch {
36281
+ return false;
36282
+ }
36283
+ };
36284
+ const collectTypeScriptEmitDuplicateJsPaths = (input) => {
36285
+ const untracked = new Set(input.untrackedPaths);
36286
+ const duplicateJsPaths = /* @__PURE__ */ new Set();
36287
+ for (const jsPath of untracked) {
36288
+ if (!jsPath.endsWith(".js")) continue;
36289
+ const stem = jsPath.slice(0, -3);
36290
+ if (![
36291
+ jsPath,
36292
+ `${stem}.js.map`,
36293
+ `${stem}.d.ts`,
36294
+ `${stem}.d.ts.map`
36295
+ ].every((emitPath) => untracked.has(emitPath) && !input.trackedPaths.has(emitPath))) continue;
36296
+ const sourcePath = [`${stem}.ts`, `${stem}.tsx`].find((candidatePath) => input.trackedPaths.has(candidatePath));
36297
+ if (!sourcePath) continue;
36298
+ if (quartetProvesEmit(stem, sourcePath, input.readFileText)) duplicateJsPaths.add(jsPath);
36299
+ }
36300
+ return duplicateJsPaths;
36301
+ };
36255
36302
  const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
36256
36303
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
36257
36304
  const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
@@ -36319,20 +36366,29 @@ const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
36319
36366
  }
36320
36367
  return entries;
36321
36368
  };
36322
- const listSourceFilesViaGit = (rootDirectory) => {
36369
+ const gitLsFiles = (rootDirectory, flags) => {
36323
36370
  const result = spawnSync("git", [
36324
36371
  "ls-files",
36325
36372
  "-z",
36326
- "--cached",
36327
- "--others",
36328
- "--exclude-standard"
36373
+ ...flags
36329
36374
  ], {
36330
36375
  cwd: rootDirectory,
36331
36376
  encoding: "utf-8",
36332
36377
  maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
36333
36378
  });
36334
36379
  if (result.error || result.status !== 0) return null;
36335
- return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
36380
+ return result.stdout.split("\0").filter((filePath) => filePath.length > 0);
36381
+ };
36382
+ const listSourceFilesViaGit = (rootDirectory) => {
36383
+ const trackedPaths = gitLsFiles(rootDirectory, ["--cached"]);
36384
+ const untrackedPaths = gitLsFiles(rootDirectory, ["--others", "--exclude-standard"]);
36385
+ if (trackedPaths === null || untrackedPaths === null) return null;
36386
+ const emitDuplicateJsPaths = collectTypeScriptEmitDuplicateJsPaths({
36387
+ trackedPaths: new Set(trackedPaths),
36388
+ untrackedPaths,
36389
+ readFileText: (relativePath) => NFS.readFileSync(Path.resolve(rootDirectory, relativePath), "utf-8")
36390
+ });
36391
+ return [...trackedPaths, ...untrackedPaths].filter((filePath) => isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath) && !emitDuplicateJsPaths.has(filePath));
36336
36392
  };
36337
36393
  const listSourceFilesViaFilesystem = (rootDirectory) => {
36338
36394
  const filePaths = [];
@@ -39172,8 +39228,10 @@ const isDiagnosticSurface = (value) => typeof value === "string" && DIAGNOSTIC_S
39172
39228
  * to the local CLI so developers see them while editing, but they're
39173
39229
  * removed from the PR comment surface, the score, and the CI gate so
39174
39230
  * they can't bury real React findings or fail a build over a Tailwind
39175
- * shorthand. Override per-surface via `config.surfaces.<surface>` to
39176
- * promote individual rules back in by tag, category, or rule id.
39231
+ * shorthand. Diagnostics from test and story files likewise remain in
39232
+ * raw output while staying out of the production-health score and CI
39233
+ * gate. Override per-surface via `config.surfaces.<surface>` to promote
39234
+ * individual rules back in by tag, category, or rule id.
39177
39235
  */
39178
39236
  const DEFAULT_SURFACE_EXCLUDED_TAGS = {
39179
39237
  cli: [],
@@ -42004,6 +42062,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
42004
42062
  if (resolved.includeRuleKeys.has(ruleKey)) return true;
42005
42063
  if (resolved.includeCategories.has(category)) return true;
42006
42064
  if (intersects(tags, resolved.includeTags)) return true;
42065
+ if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
42007
42066
  if (resolved.excludeRuleKeys.has(ruleKey)) return false;
42008
42067
  if (resolved.excludeCategories.has(category)) return false;
42009
42068
  if (intersects(tags, resolved.excludeTags)) return false;
@@ -42428,6 +42487,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42428
42487
  }
42429
42488
  const diff = yield* runGit(input.directory, [
42430
42489
  "diff",
42490
+ "--no-ext-diff",
42431
42491
  "-z",
42432
42492
  "--name-only",
42433
42493
  "--diff-filter=ACMR",
@@ -42474,6 +42534,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42474
42534
  if (resolvedCurrentBranch !== null && resolvedCurrentBranch === baseBranch) {
42475
42535
  const uncommitted = yield* runGit(directory, [
42476
42536
  "diff",
42537
+ "--no-ext-diff",
42477
42538
  "-z",
42478
42539
  "--name-only",
42479
42540
  "--diff-filter=ACMR",
@@ -42500,6 +42561,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42500
42561
  if (mergeBaseRef === null) return null;
42501
42562
  const diff = yield* runGit(directory, [
42502
42563
  "diff",
42564
+ "--no-ext-diff",
42503
42565
  "-z",
42504
42566
  "--name-only",
42505
42567
  "--diff-filter=ACMR",
@@ -42517,6 +42579,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42517
42579
  }).pipe(withSpan("Git.diffSelection")),
42518
42580
  stagedFilePaths: (directory) => runGit(directory, [
42519
42581
  "diff",
42582
+ "--no-ext-diff",
42520
42583
  "--cached",
42521
42584
  "-z",
42522
42585
  "--name-only",
@@ -42562,6 +42625,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42562
42625
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
42563
42626
  const result = yield* runGit(directory, [
42564
42627
  "diff",
42628
+ "--no-ext-diff",
42565
42629
  "--unified=0",
42566
42630
  "--diff-filter=ACMR",
42567
42631
  "--relative",
@@ -47159,7 +47223,7 @@ const makeNoopConsole = () => ({
47159
47223
  });
47160
47224
  //#endregion
47161
47225
  //#region src/cli/utils/version.ts
47162
- const VERSION = "0.7.9-dev.f441f59";
47226
+ const VERSION = "0.8.0-dev.c0d9d38";
47163
47227
  //#endregion
47164
47228
  //#region src/cli/utils/json-mode.ts
47165
47229
  let context = null;
@@ -47511,13 +47575,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47511
47575
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47512
47576
  * standard `SENTRY_RELEASE` override.
47513
47577
  */
47514
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.9-dev.f441f59`;
47578
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.0-dev.c0d9d38`;
47515
47579
  /**
47516
47580
  * Deployment environment shown in Sentry's environment filter. Defaults to
47517
47581
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47518
47582
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47519
47583
  */
47520
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.9-dev.f441f59") ? "development" : "production");
47584
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.0-dev.c0d9d38") ? "development" : "production");
47521
47585
  /**
47522
47586
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47523
47587
  * (set to `0` to disable tracing) and falls back to
@@ -60012,6 +60076,7 @@ const buildOutcomeAttributes = (input) => {
60012
60076
  const summary = summarizeDiagnostics(result.diagnostics);
60013
60077
  const blockingLevel = resolveTelemetryBlocking(input.userConfig);
60014
60078
  const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
60079
+ const gateDiagnosticSet = new Set(gateDiagnostics);
60015
60080
  const complete = isInspectResultComplete(result);
60016
60081
  const didLintHardFail = hasLintHardFailure(result);
60017
60082
  const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
@@ -60034,11 +60099,13 @@ const buildOutcomeAttributes = (input) => {
60034
60099
  const largestRuleBucket = buildRuleBlastRadii(result.diagnostics)[0] ?? null;
60035
60100
  let diagnosticsInTestFiles = 0;
60036
60101
  let diagnosticsInStoryFiles = 0;
60102
+ let nonProductionGateExcluded = 0;
60037
60103
  const diagnosticSiteCounts = /* @__PURE__ */ new Map();
60038
60104
  const findingsPerFixGroup = /* @__PURE__ */ new Map();
60039
60105
  for (const diagnostic of result.diagnostics) {
60040
60106
  if (diagnostic.fileContext === "test") diagnosticsInTestFiles += 1;
60041
60107
  if (diagnostic.fileContext === "story") diagnosticsInStoryFiles += 1;
60108
+ if (diagnostic.fileContext !== void 0 && !gateDiagnosticSet.has(diagnostic)) nonProductionGateExcluded += 1;
60042
60109
  const diagnosticSite = JSON.stringify([
60043
60110
  diagnostic.filePath,
60044
60111
  diagnostic.line,
@@ -60087,6 +60154,7 @@ const buildOutcomeAttributes = (input) => {
60087
60154
  affectedFiles: summary.affectedFileCount,
60088
60155
  inTestFiles: diagnosticsInTestFiles,
60089
60156
  inStoryFiles: diagnosticsInStoryFiles,
60157
+ nonProductionGateExcluded,
60090
60158
  distinctRules: countByRule.size,
60091
60159
  topRule,
60092
60160
  sameSiteOccurrences,
@@ -61926,7 +61994,9 @@ const finalizeAndRender = (input) => gen(function* () {
61926
61994
  yield* log(highlighter.gray(` ${demotedDiagnosticCount} demoted from the ${options.outputSurface} surface (e.g. design cleanup) — run \`npx react-doctor@latest .\` locally for the full list.`));
61927
61995
  yield* log("");
61928
61996
  }
61929
- const potentialScore = score ? yield* promise(() => computeProjectedScore([...printedDiagnostics], [...surfaceDiagnostics], score)) : null;
61997
+ const scoreDiagnostics = filterDiagnosticsForSurface([...diagnostics], "score", userConfig);
61998
+ const displayedScoreDiagnostics = filterDiagnosticsForSurface([...printedDiagnostics], "score", userConfig);
61999
+ const potentialScore = score ? yield* promise(() => computeProjectedScore(displayedScoreDiagnostics, scoreDiagnostics, score)) : null;
61930
62000
  const shouldShowShareLink = !options.noScore && options.share && !options.isCi;
61931
62001
  yield* pause;
61932
62002
  yield* printSummary({
@@ -63827,6 +63897,8 @@ const printMultiProjectSummary = (input) => gen(function* () {
63827
63897
  const categoryFilters = input.categoryFilters ?? /* @__PURE__ */ new Set();
63828
63898
  const animateRender = !verbose && canAnimateOnboarding(process.stdout);
63829
63899
  const displayDiagnostics = filterDiagnosticsByCategories(filterScansForSurface(completedScans, "cli"), categoryFilters);
63900
+ const scoreDiagnostics = new Set(filterScansForSurface(completedScans, "score"));
63901
+ const displayedScoreDiagnostics = displayDiagnostics.filter((diagnostic) => scoreDiagnostics.has(diagnostic));
63830
63902
  const projectRootByDiagnostic = /* @__PURE__ */ new WeakMap();
63831
63903
  for (const scan of completedScans) for (const diagnostic of scan.result.diagnostics) projectRootByDiagnostic.set(diagnostic, scan.result.project.rootDirectory);
63832
63904
  const resolveDiagnosticSourceRoot = (diagnostic) => projectRootByDiagnostic.get(diagnostic) ?? "";
@@ -63850,7 +63922,7 @@ const printMultiProjectSummary = (input) => gen(function* () {
63850
63922
  diagnostics: displayDiagnostics,
63851
63923
  elapsedMilliseconds: totalElapsedMilliseconds,
63852
63924
  scoreResult: aggregateScore,
63853
- potentialScore: lowestScoredScan ? yield* promise(() => computeProjectedScore(displayDiagnostics, filterScansForSurface([lowestScoredScan], "cli"), lowestScoredScan.result.score)) : null,
63925
+ potentialScore: lowestScoredScan ? yield* promise(() => computeProjectedScore(displayedScoreDiagnostics, filterScansForSurface([lowestScoredScan], "score"), lowestScoredScan.result.score)) : null,
63854
63926
  totalSourceFileCount,
63855
63927
  noScoreMessage: "Score unavailable.",
63856
63928
  verbose,
@@ -65795,4 +65867,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
65795
65867
  export {};
65796
65868
 
65797
65869
  //# sourceMappingURL=cli.js.map
65798
- //# debugId=5c4df375-a5c4-57d2-9076-4e54cbb8f3bd
65870
+ //# debugId=4433c20d-e5c5-50db-ab5b-0ff72ef6f226
package/dist/index.d.ts CHANGED
@@ -9056,8 +9056,10 @@ interface ReactDoctorIgnoreConfig {
9056
9056
  *
9057
9057
  * Defaults: design rules (tag `"design"`) are excluded from `prComment`,
9058
9058
  * `score`, and `ciFailure` so style cleanup doesn't dilute meaningful
9059
- * React findings. They remain in `cli` so locally-running developers
9060
- * still see the suggestion when they touch the file.
9059
+ * React findings. Diagnostics stamped with `fileContext: "test" | "story"`
9060
+ * are excluded from `score` and `ciFailure`. Both remain in `cli`, and an
9061
+ * explicit rule, category, or tag include promotes a diagnostic back onto
9062
+ * the selected surface.
9061
9063
  */
9062
9064
  type DiagnosticSurface = "cli" | "prComment" | "score" | "ciFailure";
9063
9065
  /**
@@ -9338,23 +9340,22 @@ interface ReactDoctorConfig {
9338
9340
  */
9339
9341
  adoptExistingLintConfig?: boolean;
9340
9342
  /**
9341
- * Per-surface include/exclude controls. Each `DiagnosticSurface` is
9342
- * resolved independently against rule tags, category, and id so a
9343
- * single rule can be visible locally yet hidden from PR comments,
9344
- * neutralized from the score, and excluded from the CI gate — all
9345
- * without touching the rule's severity or activation.
9343
+ * Per-surface include/exclude controls. Each `DiagnosticSurface` resolves
9344
+ * rule tags, categories, and identifiers independently. This lets you keep a rule
9345
+ * visible locally while hiding it from PR comments, the score, or the CI
9346
+ * gate. These controls don't change rule severity or activation.
9346
9347
  *
9347
9348
  * Defaults (applied before user overrides):
9348
9349
  *
9349
9350
  * - `prComment` excludes tag `"design"`
9350
9351
  * - `score` excludes tag `"design"`
9351
9352
  * - `ciFailure` excludes tag `"design"`
9353
+ * - `score` and `ciFailure` exclude test/story file contexts unless explicitly included
9352
9354
  *
9353
- * Pass any controls block (even an empty `{}`) to keep the default
9354
- * exclusions; the user's include/exclude entries layer on top.
9355
- * Include entries always win over exclude entries — handy for
9356
- * promoting a single high-signal `design-*` rule back into the
9357
- * score or PR-comment surface.
9355
+ * Pass any controls block, including an empty `{}`, to keep the defaults.
9356
+ * Your include/exclude entries layer on top. Includes always win over
9357
+ * excludes. For example, you can promote one high-signal `design-*` rule
9358
+ * back into the score or PR-comment surface.
9358
9359
  */
9359
9360
  surfaces?: Partial<Record<DiagnosticSurface, SurfaceControls>>;
9360
9361
  /**
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]="526cf949-6544-5cb8-bf6d-3f4d854b6678")}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]="81e065f1-a43b-51c2-ac53-2f38390032ed")}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";
@@ -33040,7 +33040,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
33040
33040
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
33041
33041
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
33042
33042
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
33043
- const CORE_PACKAGE_VERSION = "0.7.9";
33043
+ const CORE_PACKAGE_VERSION = "0.8.0";
33044
33044
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
33045
33045
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
33046
33046
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -33050,6 +33050,53 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
33050
33050
  const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
33051
33051
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
33052
33052
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
33053
+ const lastSourceMappingUrl = (fileContent) => {
33054
+ const lastReference = [...fileContent.matchAll(/\/\/# sourceMappingURL=(\S+)/g)].at(-1);
33055
+ return lastReference ? lastReference[1] : null;
33056
+ };
33057
+ const mapTargetsSource = (mapContent, input) => {
33058
+ const parsedMap = JSON.parse(mapContent);
33059
+ if (typeof parsedMap !== "object" || parsedMap === null) return false;
33060
+ const { file, sources } = parsedMap;
33061
+ if (file !== input.expectedFile) return false;
33062
+ if (!Array.isArray(sources) || sources.length !== 1 || typeof sources[0] !== "string") return false;
33063
+ return Path.posix.normalize(Path.posix.join(input.mapDirectory, sources[0])) === input.sourcePath;
33064
+ };
33065
+ const quartetProvesEmit = (stem, sourcePath, readFileText) => {
33066
+ try {
33067
+ const emitBaseName = Path.posix.basename(stem);
33068
+ const mapDirectory = Path.posix.dirname(stem);
33069
+ return lastSourceMappingUrl(readFileText(`${stem}.js`)) === `${emitBaseName}.js.map` && lastSourceMappingUrl(readFileText(`${stem}.d.ts`)) === `${emitBaseName}.d.ts.map` && mapTargetsSource(readFileText(`${stem}.js.map`), {
33070
+ expectedFile: `${emitBaseName}.js`,
33071
+ mapDirectory,
33072
+ sourcePath
33073
+ }) && mapTargetsSource(readFileText(`${stem}.d.ts.map`), {
33074
+ expectedFile: `${emitBaseName}.d.ts`,
33075
+ mapDirectory,
33076
+ sourcePath
33077
+ });
33078
+ } catch {
33079
+ return false;
33080
+ }
33081
+ };
33082
+ const collectTypeScriptEmitDuplicateJsPaths = (input) => {
33083
+ const untracked = new Set(input.untrackedPaths);
33084
+ const duplicateJsPaths = /* @__PURE__ */ new Set();
33085
+ for (const jsPath of untracked) {
33086
+ if (!jsPath.endsWith(".js")) continue;
33087
+ const stem = jsPath.slice(0, -3);
33088
+ if (![
33089
+ jsPath,
33090
+ `${stem}.js.map`,
33091
+ `${stem}.d.ts`,
33092
+ `${stem}.d.ts.map`
33093
+ ].every((emitPath) => untracked.has(emitPath) && !input.trackedPaths.has(emitPath))) continue;
33094
+ const sourcePath = [`${stem}.ts`, `${stem}.tsx`].find((candidatePath) => input.trackedPaths.has(candidatePath));
33095
+ if (!sourcePath) continue;
33096
+ if (quartetProvesEmit(stem, sourcePath, input.readFileText)) duplicateJsPaths.add(jsPath);
33097
+ }
33098
+ return duplicateJsPaths;
33099
+ };
33053
33100
  const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
33054
33101
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
33055
33102
  const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
@@ -33120,20 +33167,29 @@ const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
33120
33167
  }
33121
33168
  return entries;
33122
33169
  };
33123
- const listSourceFilesViaGit = (rootDirectory) => {
33170
+ const gitLsFiles = (rootDirectory, flags) => {
33124
33171
  const result = spawnSync("git", [
33125
33172
  "ls-files",
33126
33173
  "-z",
33127
- "--cached",
33128
- "--others",
33129
- "--exclude-standard"
33174
+ ...flags
33130
33175
  ], {
33131
33176
  cwd: rootDirectory,
33132
33177
  encoding: "utf-8",
33133
33178
  maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
33134
33179
  });
33135
33180
  if (result.error || result.status !== 0) return null;
33136
- return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
33181
+ return result.stdout.split("\0").filter((filePath) => filePath.length > 0);
33182
+ };
33183
+ const listSourceFilesViaGit = (rootDirectory) => {
33184
+ const trackedPaths = gitLsFiles(rootDirectory, ["--cached"]);
33185
+ const untrackedPaths = gitLsFiles(rootDirectory, ["--others", "--exclude-standard"]);
33186
+ if (trackedPaths === null || untrackedPaths === null) return null;
33187
+ const emitDuplicateJsPaths = collectTypeScriptEmitDuplicateJsPaths({
33188
+ trackedPaths: new Set(trackedPaths),
33189
+ untrackedPaths,
33190
+ readFileText: (relativePath) => NFS.readFileSync(Path.resolve(rootDirectory, relativePath), "utf-8")
33191
+ });
33192
+ return [...trackedPaths, ...untrackedPaths].filter((filePath) => isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath) && !emitDuplicateJsPaths.has(filePath));
33137
33193
  };
33138
33194
  const listSourceFilesViaFilesystem = (rootDirectory) => {
33139
33195
  const filePaths = [];
@@ -35972,8 +36028,10 @@ const isDiagnosticSurface = (value) => typeof value === "string" && DIAGNOSTIC_S
35972
36028
  * to the local CLI so developers see them while editing, but they're
35973
36029
  * removed from the PR comment surface, the score, and the CI gate so
35974
36030
  * they can't bury real React findings or fail a build over a Tailwind
35975
- * shorthand. Override per-surface via `config.surfaces.<surface>` to
35976
- * promote individual rules back in by tag, category, or rule id.
36031
+ * shorthand. Diagnostics from test and story files likewise remain in
36032
+ * raw output while staying out of the production-health score and CI
36033
+ * gate. Override per-surface via `config.surfaces.<surface>` to promote
36034
+ * individual rules back in by tag, category, or rule id.
35977
36035
  */
35978
36036
  const DEFAULT_SURFACE_EXCLUDED_TAGS = {
35979
36037
  cli: [],
@@ -38760,6 +38818,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
38760
38818
  if (resolved.includeRuleKeys.has(ruleKey)) return true;
38761
38819
  if (resolved.includeCategories.has(category)) return true;
38762
38820
  if (intersects(tags, resolved.includeTags)) return true;
38821
+ if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
38763
38822
  if (resolved.excludeRuleKeys.has(ruleKey)) return false;
38764
38823
  if (resolved.excludeCategories.has(category)) return false;
38765
38824
  if (intersects(tags, resolved.excludeTags)) return false;
@@ -39184,6 +39243,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39184
39243
  }
39185
39244
  const diff = yield* runGit(input.directory, [
39186
39245
  "diff",
39246
+ "--no-ext-diff",
39187
39247
  "-z",
39188
39248
  "--name-only",
39189
39249
  "--diff-filter=ACMR",
@@ -39230,6 +39290,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39230
39290
  if (resolvedCurrentBranch !== null && resolvedCurrentBranch === baseBranch) {
39231
39291
  const uncommitted = yield* runGit(directory, [
39232
39292
  "diff",
39293
+ "--no-ext-diff",
39233
39294
  "-z",
39234
39295
  "--name-only",
39235
39296
  "--diff-filter=ACMR",
@@ -39256,6 +39317,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39256
39317
  if (mergeBaseRef === null) return null;
39257
39318
  const diff = yield* runGit(directory, [
39258
39319
  "diff",
39320
+ "--no-ext-diff",
39259
39321
  "-z",
39260
39322
  "--name-only",
39261
39323
  "--diff-filter=ACMR",
@@ -39273,6 +39335,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39273
39335
  }).pipe(withSpan("Git.diffSelection")),
39274
39336
  stagedFilePaths: (directory) => runGit(directory, [
39275
39337
  "diff",
39338
+ "--no-ext-diff",
39276
39339
  "--cached",
39277
39340
  "-z",
39278
39341
  "--name-only",
@@ -39318,6 +39381,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39318
39381
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
39319
39382
  const result = yield* runGit(directory, [
39320
39383
  "diff",
39384
+ "--no-ext-diff",
39321
39385
  "--unified=0",
39322
39386
  "--diff-filter=ACMR",
39323
39387
  "--relative",
@@ -43663,4 +43727,4 @@ const toJsonReport = (result, options) => buildJsonReport({
43663
43727
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
43664
43728
 
43665
43729
  //# sourceMappingURL=index.js.map
43666
- //# debugId=526cf949-6544-5cb8-bf6d-3f4d854b6678
43730
+ //# debugId=81e065f1-a43b-51c2-ac53-2f38390032ed
package/dist/lsp.js CHANGED
@@ -33092,7 +33092,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
33092
33092
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
33093
33093
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
33094
33094
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
33095
- const CORE_PACKAGE_VERSION = "0.7.9";
33095
+ const CORE_PACKAGE_VERSION = "0.8.0";
33096
33096
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
33097
33097
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
33098
33098
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -33102,6 +33102,53 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
33102
33102
  const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
33103
33103
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
33104
33104
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
33105
+ const lastSourceMappingUrl = (fileContent) => {
33106
+ const lastReference = [...fileContent.matchAll(/\/\/# sourceMappingURL=(\S+)/g)].at(-1);
33107
+ return lastReference ? lastReference[1] : null;
33108
+ };
33109
+ const mapTargetsSource = (mapContent, input) => {
33110
+ const parsedMap = JSON.parse(mapContent);
33111
+ if (typeof parsedMap !== "object" || parsedMap === null) return false;
33112
+ const { file, sources } = parsedMap;
33113
+ if (file !== input.expectedFile) return false;
33114
+ if (!Array.isArray(sources) || sources.length !== 1 || typeof sources[0] !== "string") return false;
33115
+ return Path.posix.normalize(Path.posix.join(input.mapDirectory, sources[0])) === input.sourcePath;
33116
+ };
33117
+ const quartetProvesEmit = (stem, sourcePath, readFileText) => {
33118
+ try {
33119
+ const emitBaseName = Path.posix.basename(stem);
33120
+ const mapDirectory = Path.posix.dirname(stem);
33121
+ return lastSourceMappingUrl(readFileText(`${stem}.js`)) === `${emitBaseName}.js.map` && lastSourceMappingUrl(readFileText(`${stem}.d.ts`)) === `${emitBaseName}.d.ts.map` && mapTargetsSource(readFileText(`${stem}.js.map`), {
33122
+ expectedFile: `${emitBaseName}.js`,
33123
+ mapDirectory,
33124
+ sourcePath
33125
+ }) && mapTargetsSource(readFileText(`${stem}.d.ts.map`), {
33126
+ expectedFile: `${emitBaseName}.d.ts`,
33127
+ mapDirectory,
33128
+ sourcePath
33129
+ });
33130
+ } catch {
33131
+ return false;
33132
+ }
33133
+ };
33134
+ const collectTypeScriptEmitDuplicateJsPaths = (input) => {
33135
+ const untracked = new Set(input.untrackedPaths);
33136
+ const duplicateJsPaths = /* @__PURE__ */ new Set();
33137
+ for (const jsPath of untracked) {
33138
+ if (!jsPath.endsWith(".js")) continue;
33139
+ const stem = jsPath.slice(0, -3);
33140
+ if (![
33141
+ jsPath,
33142
+ `${stem}.js.map`,
33143
+ `${stem}.d.ts`,
33144
+ `${stem}.d.ts.map`
33145
+ ].every((emitPath) => untracked.has(emitPath) && !input.trackedPaths.has(emitPath))) continue;
33146
+ const sourcePath = [`${stem}.ts`, `${stem}.tsx`].find((candidatePath) => input.trackedPaths.has(candidatePath));
33147
+ if (!sourcePath) continue;
33148
+ if (quartetProvesEmit(stem, sourcePath, input.readFileText)) duplicateJsPaths.add(jsPath);
33149
+ }
33150
+ return duplicateJsPaths;
33151
+ };
33105
33152
  const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
33106
33153
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
33107
33154
  const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
@@ -33172,20 +33219,29 @@ const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
33172
33219
  }
33173
33220
  return entries;
33174
33221
  };
33175
- const listSourceFilesViaGit = (rootDirectory) => {
33222
+ const gitLsFiles = (rootDirectory, flags) => {
33176
33223
  const result = spawnSync("git", [
33177
33224
  "ls-files",
33178
33225
  "-z",
33179
- "--cached",
33180
- "--others",
33181
- "--exclude-standard"
33226
+ ...flags
33182
33227
  ], {
33183
33228
  cwd: rootDirectory,
33184
33229
  encoding: "utf-8",
33185
33230
  maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
33186
33231
  });
33187
33232
  if (result.error || result.status !== 0) return null;
33188
- return result.stdout.split("\0").filter((filePath) => filePath.length > 0 && isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath));
33233
+ return result.stdout.split("\0").filter((filePath) => filePath.length > 0);
33234
+ };
33235
+ const listSourceFilesViaGit = (rootDirectory) => {
33236
+ const trackedPaths = gitLsFiles(rootDirectory, ["--cached"]);
33237
+ const untrackedPaths = gitLsFiles(rootDirectory, ["--others", "--exclude-standard"]);
33238
+ if (trackedPaths === null || untrackedPaths === null) return null;
33239
+ const emitDuplicateJsPaths = collectTypeScriptEmitDuplicateJsPaths({
33240
+ trackedPaths: new Set(trackedPaths),
33241
+ untrackedPaths,
33242
+ readFileText: (relativePath) => NFS.readFileSync(Path.resolve(rootDirectory, relativePath), "utf-8")
33243
+ });
33244
+ return [...trackedPaths, ...untrackedPaths].filter((filePath) => isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath) && !emitDuplicateJsPaths.has(filePath));
33189
33245
  };
33190
33246
  const listSourceFilesViaFilesystem = (rootDirectory) => {
33191
33247
  const filePaths = [];
@@ -35998,8 +36054,10 @@ const isDiagnosticSurface = (value) => typeof value === "string" && DIAGNOSTIC_S
35998
36054
  * to the local CLI so developers see them while editing, but they're
35999
36055
  * removed from the PR comment surface, the score, and the CI gate so
36000
36056
  * they can't bury real React findings or fail a build over a Tailwind
36001
- * shorthand. Override per-surface via `config.surfaces.<surface>` to
36002
- * promote individual rules back in by tag, category, or rule id.
36057
+ * shorthand. Diagnostics from test and story files likewise remain in
36058
+ * raw output while staying out of the production-health score and CI
36059
+ * gate. Override per-surface via `config.surfaces.<surface>` to promote
36060
+ * individual rules back in by tag, category, or rule id.
36003
36061
  */
36004
36062
  const DEFAULT_SURFACE_EXCLUDED_TAGS = {
36005
36063
  cli: [],
@@ -38738,6 +38796,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
38738
38796
  if (resolved.includeRuleKeys.has(ruleKey)) return true;
38739
38797
  if (resolved.includeCategories.has(category)) return true;
38740
38798
  if (intersects(tags, resolved.includeTags)) return true;
38799
+ if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
38741
38800
  if (resolved.excludeRuleKeys.has(ruleKey)) return false;
38742
38801
  if (resolved.excludeCategories.has(category)) return false;
38743
38802
  if (intersects(tags, resolved.excludeTags)) return false;
@@ -39162,6 +39221,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39162
39221
  }
39163
39222
  const diff = yield* runGit(input.directory, [
39164
39223
  "diff",
39224
+ "--no-ext-diff",
39165
39225
  "-z",
39166
39226
  "--name-only",
39167
39227
  "--diff-filter=ACMR",
@@ -39208,6 +39268,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39208
39268
  if (resolvedCurrentBranch !== null && resolvedCurrentBranch === baseBranch) {
39209
39269
  const uncommitted = yield* runGit(directory, [
39210
39270
  "diff",
39271
+ "--no-ext-diff",
39211
39272
  "-z",
39212
39273
  "--name-only",
39213
39274
  "--diff-filter=ACMR",
@@ -39234,6 +39295,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39234
39295
  if (mergeBaseRef === null) return null;
39235
39296
  const diff = yield* runGit(directory, [
39236
39297
  "diff",
39298
+ "--no-ext-diff",
39237
39299
  "-z",
39238
39300
  "--name-only",
39239
39301
  "--diff-filter=ACMR",
@@ -39251,6 +39313,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39251
39313
  }).pipe(withSpan("Git.diffSelection")),
39252
39314
  stagedFilePaths: (directory) => runGit(directory, [
39253
39315
  "diff",
39316
+ "--no-ext-diff",
39254
39317
  "--cached",
39255
39318
  "-z",
39256
39319
  "--name-only",
@@ -39296,6 +39359,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39296
39359
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
39297
39360
  const result = yield* runGit(directory, [
39298
39361
  "diff",
39362
+ "--no-ext-diff",
39299
39363
  "--unified=0",
39300
39364
  "--diff-filter=ACMR",
39301
39365
  "--relative",
@@ -43253,7 +43317,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
43253
43317
  /** Display name used in client-facing messages and progress titles. */
43254
43318
  const SERVER_DISPLAY_NAME = "React Doctor";
43255
43319
  /** Server version reported in `serverInfo`; injected at build, `dev` from source. */
43256
- const SERVER_VERSION = "0.7.9";
43320
+ const SERVER_VERSION = "0.8.0";
43257
43321
  /** `Diagnostic.source` shown next to every published diagnostic. */
43258
43322
  const DIAGNOSTIC_SOURCE = "react-doctor";
43259
43323
  /**
@@ -45283,5 +45347,5 @@ const startLanguageServer = () => {
45283
45347
  };
45284
45348
  //#endregion
45285
45349
  export { startLanguageServer };
45286
- !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]="3eb1e331-d4d9-517d-a23d-3170e3d3203f")}catch(e){}}();
45287
- //# debugId=3eb1e331-d4d9-517d-a23d-3170e3d3203f
45350
+ !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]="9eca6678-56ba-5032-b04c-b1ed965dffb4")}catch(e){}}();
45351
+ //# debugId=9eca6678-56ba-5032-b04c-b1ed965dffb4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.7.9-dev.f441f59",
3
+ "version": "0.8.0-dev.c0d9d38",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -65,8 +65,8 @@
65
65
  "vscode-languageserver-textdocument": "^1.0.12",
66
66
  "vscode-uri": "^3.1.0",
67
67
  "yaml": "^2.9.0",
68
- "deslop-js": "0.7.9",
69
- "oxlint-plugin-react-doctor": "0.7.9-dev.f441f59"
68
+ "deslop-js": "0.8.0",
69
+ "oxlint-plugin-react-doctor": "0.8.0-dev.c0d9d38"
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.9",
78
- "@react-doctor/api": "0.7.9",
79
- "@react-doctor/language-server": "0.7.9"
77
+ "@react-doctor/core": "0.8.0",
78
+ "@react-doctor/api": "0.8.0",
79
+ "@react-doctor/language-server": "0.8.0"
80
80
  },
81
81
  "engines": {
82
82
  "node": "^20.19.0 || >=22.13.0"