react-doctor 0.8.1-dev.7818922 → 0.8.1-dev.7eca0ee

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9030,11 +9030,12 @@ 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. `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).
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).
9038
9039
  */
9039
9040
  matchByOccurrence?: boolean;
9040
9041
  /**
@@ -9542,11 +9543,35 @@ interface ProjectInfo {
9542
9543
  zodVersion: string | null;
9543
9544
  /** Parsed major from `zodVersion`, or `null` when absent/unparseable. Mirrors `reactMajorVersion`. */
9544
9545
  zodMajorVersion: number | null;
9546
+ /** Declared MobX core version spec. Optional so existing `ProjectInfo` consumers remain source-compatible. */
9547
+ mobxVersion?: string | null;
9548
+ /** Parsed major from `mobxVersion`, or `null` when absent/unparseable. */
9549
+ mobxMajorVersion?: number | null;
9550
+ hasMobxReact?: boolean;
9551
+ hasMobxReactLite?: boolean;
9552
+ hasMobxStateTree?: boolean;
9553
+ hasMobxReactObserver?: boolean;
9554
+ /** Declared Zustand version spec. Optional so existing `ProjectInfo` consumers remain source-compatible. */
9555
+ zustandVersion?: string | null;
9556
+ /** Parsed major from `zustandVersion`, or `null` when absent/unparseable. */
9557
+ zustandMajorVersion?: number | null;
9545
9558
  framework: Framework;
9546
9559
  hasTypeScript: boolean;
9547
9560
  hasReactCompiler: boolean;
9548
9561
  hasReactCompilerLintPlugin?: boolean;
9549
9562
  hasTanStackQuery: boolean;
9563
+ hasRemotion?: boolean;
9564
+ remotionVersion?: string | null;
9565
+ remotionMajorVersion?: number | null;
9566
+ /**
9567
+ * The declared `valtio` version spec, or `null` when no package in the
9568
+ * analyzed project declares Valtio. `useSnapshot` has kept the same
9569
+ * render-read contract across Valtio 1 and 2, so presence alone drives the
9570
+ * `valtio` capability and its parsed major-version ladder.
9571
+ */
9572
+ valtioVersion: string | null;
9573
+ /** Parsed major from `valtioVersion`, or `null` when absent/unparseable. */
9574
+ valtioMajorVersion: number | null;
9550
9575
  /**
9551
9576
  * `true` when the project or a workspace declares a Vite-based SSR runtime
9552
9577
  * such as React Router's Node adapter, Vike, or vite-plugin-ssr.
@@ -9847,7 +9872,8 @@ interface InspectResult {
9847
9872
  baselineDelta?: {
9848
9873
  /** 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. */
9849
9874
  fixedCount: number; /** Total findings at base (over the same files), for context. */
9850
- baseTotalCount: number;
9875
+ baseTotalCount: number; /** Pre-existing findings matched after moving to a different file. */
9876
+ crossFileMatchCount?: number;
9851
9877
  };
9852
9878
  }
9853
9879
  /**
@@ -10263,15 +10289,7 @@ declare const buildJsonReport: (input: BuildJsonReportInput) => JsonReportV3$1;
10263
10289
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
10264
10290
  */
10265
10291
  declare const getDiffInfo: (directory: string, explicitBaseBranch?: string, includeUntracked?: boolean) => Promise<DiffInfo | null>;
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
- */
10292
+ declare const filterSourceFiles: (filePaths: ReadonlyArray<string>) => string[];
10275
10293
  //#endregion
10276
10294
  //#region src/summarize-diagnostics.d.ts
10277
10295
  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]="3121a4e4-8a4a-55ea-ade8-47d0bf576a2f")}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]="6aa1c6e1-8fcc-50d4-a076-3e864425bdc5")}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";
@@ -34121,6 +34121,10 @@ const listWorkspacePackages = (rootDirectory) => {
34121
34121
  return packages;
34122
34122
  };
34123
34123
  const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
34124
+ const MOBX_REACT_PACKAGE_NAME = "mobx-react";
34125
+ const MOBX_REACT_LITE_PACKAGE_NAME = "mobx-react-lite";
34126
+ const MOBX_STATE_TREE_PACKAGE_NAME = "mobx-state-tree";
34127
+ const MOBX_REACT_OBSERVER_PACKAGE_NAME = "mobx-react-observer";
34124
34128
  const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
34125
34129
  const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
34126
34130
  const dependencyDeclaration = getDependencyDeclaration({
@@ -34139,7 +34143,18 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
34139
34143
  if (nextMajor === null) return false;
34140
34144
  return nextMajor < currentMajor;
34141
34145
  };
34142
- const evaluateManifestFacts = (facts, packageJson, directory) => {
34146
+ const shouldReplaceSupportedDependencyFact = (currentFact, nextFact, latestSupportedMajor) => {
34147
+ if (currentFact.version === null) return true;
34148
+ if (nextFact.version === null) return false;
34149
+ const currentMajor = getLowestDependencyMajor(currentFact.version);
34150
+ const nextMajor = getLowestDependencyMajor(nextFact.version);
34151
+ if (currentMajor === null) return false;
34152
+ if (nextMajor === null) return true;
34153
+ if (currentMajor > latestSupportedMajor) return false;
34154
+ if (nextMajor > latestSupportedMajor) return true;
34155
+ return nextMajor < currentMajor;
34156
+ };
34157
+ const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
34143
34158
  if (facts.expo.version === null) {
34144
34159
  const spec = getDependencySpec(packageJson, "expo");
34145
34160
  if (spec !== null) facts.expo = {
@@ -34161,13 +34176,65 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
34161
34176
  sourceDirectory: directory
34162
34177
  };
34163
34178
  }
34179
+ const mobxSpec = getDependencySpec(packageJson, "mobx");
34180
+ if (mobxSpec !== null) {
34181
+ const nextMobxFact = {
34182
+ version: resolveCatalogBackedDependencyVersion({
34183
+ rootDirectory: directory,
34184
+ rootPackageJson: packageJson,
34185
+ packageName: "mobx",
34186
+ version: mobxSpec
34187
+ }),
34188
+ sourceDirectory: directory
34189
+ };
34190
+ if (shouldReplaceSupportedDependencyFact(facts.mobx, nextMobxFact, 6)) facts.mobx = nextMobxFact;
34191
+ }
34192
+ facts.hasMobxReact = facts.hasMobxReact || getDependencySpec(packageJson, MOBX_REACT_PACKAGE_NAME) !== null;
34193
+ facts.hasMobxReactLite = facts.hasMobxReactLite || getDependencySpec(packageJson, MOBX_REACT_LITE_PACKAGE_NAME) !== null;
34194
+ facts.hasMobxStateTree = facts.hasMobxStateTree || getDependencySpec(packageJson, MOBX_STATE_TREE_PACKAGE_NAME) !== null;
34195
+ facts.hasMobxReactObserver = facts.hasMobxReactObserver || getDependencySpec(packageJson, MOBX_REACT_OBSERVER_PACKAGE_NAME) !== null;
34196
+ const zustandSpec = getDependencySpec(packageJson, "zustand");
34197
+ if (zustandSpec !== null) {
34198
+ const nextZustandFact = {
34199
+ version: resolveCatalogBackedDependencyVersion({
34200
+ rootDirectory: directory,
34201
+ rootPackageJson: packageJson,
34202
+ packageName: "zustand",
34203
+ version: zustandSpec
34204
+ }),
34205
+ sourceDirectory: directory
34206
+ };
34207
+ if (shouldReplaceSupportedDependencyFact(facts.zustand, nextZustandFact, 5)) facts.zustand = nextZustandFact;
34208
+ }
34164
34209
  if (facts.reanimatedVersion === null) {
34165
34210
  const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
34166
34211
  if (spec !== null) facts.reanimatedVersion = spec;
34167
34212
  }
34213
+ if (facts.valtioVersion === null) {
34214
+ const spec = getDependencySpec(packageJson, "valtio");
34215
+ if (spec !== null) facts.valtioVersion = spec;
34216
+ }
34168
34217
  facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34169
34218
  facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34170
34219
  facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
34220
+ const remotionSpec = getDependencySpec(packageJson, "remotion");
34221
+ if (remotionSpec !== null) {
34222
+ facts.hasRemotionDependency = true;
34223
+ const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
34224
+ rootDirectory,
34225
+ rootPackageJson,
34226
+ packageName: "remotion",
34227
+ version: remotionSpec
34228
+ });
34229
+ const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
34230
+ if (remotionMajorVersion === null) {
34231
+ facts.hasUnknownRemotionVersion = true;
34232
+ facts.remotionVersion = null;
34233
+ } else if (!facts.hasUnknownRemotionVersion) {
34234
+ const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
34235
+ if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
34236
+ }
34237
+ }
34171
34238
  };
34172
34239
  const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34173
34240
  const facts = {
@@ -34187,12 +34254,28 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34187
34254
  version: null,
34188
34255
  sourceDirectory: null
34189
34256
  },
34257
+ valtioVersion: null,
34258
+ mobx: {
34259
+ version: null,
34260
+ sourceDirectory: null
34261
+ },
34262
+ hasMobxReact: false,
34263
+ hasMobxReactLite: false,
34264
+ hasMobxStateTree: false,
34265
+ hasMobxReactObserver: false,
34266
+ zustand: {
34267
+ version: null,
34268
+ sourceDirectory: null
34269
+ },
34190
34270
  hasReactNativeAwarePackage: false,
34191
34271
  hasReanimatedAwarePackage: false,
34192
34272
  hasSsrDependency: false,
34273
+ hasRemotionDependency: false,
34274
+ hasUnknownRemotionVersion: false,
34275
+ remotionVersion: null,
34193
34276
  reanimatedVersion: null
34194
34277
  };
34195
- evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
34278
+ evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
34196
34279
  let isReactGroupSettled = !collectReactGroup;
34197
34280
  const visitedDirectories = /* @__PURE__ */ new Set();
34198
34281
  for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
@@ -34201,7 +34284,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34201
34284
  if (visitedDirectories.has(workspaceDirectory)) continue;
34202
34285
  visitedDirectories.add(workspaceDirectory);
34203
34286
  const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
34204
- evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
34287
+ evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
34205
34288
  const info = extractDependencyInfo(workspacePackageJson);
34206
34289
  if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
34207
34290
  if (isReactGroupSettled) continue;
@@ -34425,11 +34508,24 @@ const discoverProjectWithoutPackageJson = (directory) => {
34425
34508
  tailwindVersion: null,
34426
34509
  zodVersion: null,
34427
34510
  zodMajorVersion: null,
34511
+ mobxVersion: null,
34512
+ mobxMajorVersion: null,
34513
+ hasMobxReact: false,
34514
+ hasMobxReactLite: false,
34515
+ hasMobxStateTree: false,
34516
+ hasMobxReactObserver: false,
34517
+ zustandVersion: null,
34518
+ zustandMajorVersion: null,
34428
34519
  framework: "unknown",
34429
34520
  hasTypeScript: hasOwnTsConfig,
34430
34521
  hasReactCompiler: false,
34431
34522
  hasReactCompilerLintPlugin: false,
34432
34523
  hasTanStackQuery: false,
34524
+ valtioVersion: null,
34525
+ valtioMajorVersion: null,
34526
+ hasRemotion: false,
34527
+ remotionVersion: null,
34528
+ remotionMajorVersion: null,
34433
34529
  hasSsrDependency: false,
34434
34530
  preactVersion: null,
34435
34531
  preactMajorVersion: null,
@@ -34528,6 +34624,12 @@ const discoverProject = (directory) => {
34528
34624
  packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
34529
34625
  version: workspaceFacts.shopifyFlashList.version
34530
34626
  }) : null;
34627
+ const zustandVersion = resolveCatalogBackedDependencyVersion({
34628
+ rootDirectory: directory,
34629
+ rootPackageJson: packageJson,
34630
+ packageName: "zustand",
34631
+ version: workspaceFacts.zustand.version
34632
+ });
34531
34633
  const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
34532
34634
  const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
34533
34635
  const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
@@ -34536,7 +34638,20 @@ const discoverProject = (directory) => {
34536
34638
  packageName: "next",
34537
34639
  version: workspaceFacts.next.version
34538
34640
  }) : null;
34641
+ const valtioVersion = resolveCatalogBackedDependencyVersion({
34642
+ rootDirectory: directory,
34643
+ rootPackageJson: packageJson,
34644
+ packageName: "valtio",
34645
+ version: workspaceFacts.valtioVersion
34646
+ });
34647
+ const mobxVersion = resolveCatalogBackedDependencyVersion({
34648
+ rootDirectory: directory,
34649
+ rootPackageJson: packageJson,
34650
+ packageName: "mobx",
34651
+ version: workspaceFacts.mobx.version
34652
+ });
34539
34653
  const preactVersion = getPreactVersion(packageJson);
34654
+ const remotionVersion = workspaceFacts.remotionVersion;
34540
34655
  const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
34541
34656
  const projectInfo = {
34542
34657
  rootDirectory: directory,
@@ -34546,11 +34661,24 @@ const discoverProject = (directory) => {
34546
34661
  tailwindVersion,
34547
34662
  zodVersion,
34548
34663
  zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
34664
+ mobxVersion,
34665
+ mobxMajorVersion: mobxVersion === null ? null : getLowestDependencyMajor(mobxVersion),
34666
+ hasMobxReact: workspaceFacts.hasMobxReact,
34667
+ hasMobxReactLite: workspaceFacts.hasMobxReactLite,
34668
+ hasMobxStateTree: workspaceFacts.hasMobxStateTree,
34669
+ hasMobxReactObserver: workspaceFacts.hasMobxReactObserver,
34670
+ zustandVersion,
34671
+ zustandMajorVersion: zustandVersion === null ? null : getLowestDependencyMajor(zustandVersion),
34549
34672
  framework,
34550
34673
  hasTypeScript,
34551
34674
  hasReactCompiler: detectReactCompiler(directory, packageJson),
34552
34675
  hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
34553
34676
  hasTanStackQuery: hasTanStackQuery(packageJson),
34677
+ valtioVersion,
34678
+ valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
34679
+ hasRemotion: workspaceFacts.hasRemotionDependency,
34680
+ remotionVersion,
34681
+ remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
34554
34682
  hasSsrDependency: workspaceFacts.hasSsrDependency,
34555
34683
  preactVersion,
34556
34684
  preactMajorVersion: parseReactMajor(preactVersion),
@@ -34784,7 +34912,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34784
34912
  "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
34785
34913
  "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
34786
34914
  "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
34787
- "react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
34788
34915
  "react/jsx-no-undef": "react-doctor/jsx-no-undef",
34789
34916
  "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
34790
34917
  "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
@@ -37071,9 +37198,25 @@ const BABEL_CONFIG_FILE_NAMES = [
37071
37198
  ".babelrc.json"
37072
37199
  ];
37073
37200
  const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
37201
+ const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
37202
+ const REACT_NATIVE_PACKAGE = "react-native";
37203
+ const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
37204
+ const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
37074
37205
  const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
37075
37206
  const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
37207
+ const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
37208
+ const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
37209
+ const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
37210
+ if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
37211
+ const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
37212
+ if (normalizedVersionRange === null) return false;
37213
+ const minimumVersion = import_semver.minVersion(normalizedVersionRange);
37214
+ return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
37215
+ };
37216
+ const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
37076
37217
  const checkReactNativeMetroBabelPreset = (rootDirectory) => {
37218
+ const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
37219
+ const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
37077
37220
  for (const fileName of BABEL_CONFIG_FILE_NAMES) {
37078
37221
  const filePath = Path.join(rootDirectory, fileName);
37079
37222
  if (!isFile(filePath)) continue;
@@ -37083,7 +37226,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
37083
37226
  } catch {
37084
37227
  continue;
37085
37228
  }
37086
- if (contents.includes(LEGACY_PRESET_SPEC)) return [buildReactNativeDiagnostic({
37229
+ if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
37087
37230
  filePath: fileName,
37088
37231
  rule: "rn-no-metro-babel-preset",
37089
37232
  severity: "error",
@@ -37106,12 +37249,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
37106
37249
  return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
37107
37250
  };
37108
37251
  const RULE_KEY = "no-vulnerable-react-server-components";
37109
- const DEPENDENCY_SECTIONS = [
37110
- "dependencies",
37111
- "devDependencies",
37112
- "peerDependencies",
37113
- "optionalDependencies"
37114
- ];
37115
37252
  const REACT_RSC_ADVISORIES_BY_MINOR = {
37116
37253
  "19.0": {
37117
37254
  rceFixedVersion: "19.0.1",
@@ -37158,20 +37295,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
37158
37295
  for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
37159
37296
  return [...directories];
37160
37297
  };
37161
- const readDeclaredSpec = (packageJson, packageName) => {
37162
- for (const section of DEPENDENCY_SECTIONS) {
37163
- const spec = packageJson[section]?.[packageName];
37164
- if (typeof spec === "string") return spec;
37165
- }
37166
- return null;
37167
- };
37168
37298
  const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
37169
37299
  const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
37170
37300
  if (isFile(manifestPath)) {
37171
37301
  const installedVersion = import_semver.valid(readPackageJson(manifestPath).version ?? null);
37172
37302
  if (installedVersion !== null) return installedVersion;
37173
37303
  }
37174
- const candidateSpecs = [readDeclaredSpec(readPackageJson(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
37304
+ const candidateSpecs = [getDependencySpec(readPackageJson(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
37175
37305
  for (const spec of candidateSpecs) {
37176
37306
  const pinnedVersion = spec === null ? null : import_semver.valid(spec);
37177
37307
  if (pinnedVersion !== null) return pinnedVersion;
@@ -37851,9 +37981,24 @@ const buildCapabilities = (project) => {
37851
37981
  })) capabilities.add("tailwind:3.4");
37852
37982
  if (project.zodVersion !== null) capabilities.add("zod");
37853
37983
  if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
37984
+ if (project.mobxVersion !== void 0 && project.mobxVersion !== null || project.hasMobxReact === true || project.hasMobxReactLite === true || project.hasMobxStateTree === true || project.hasMobxReactObserver === true) capabilities.add("mobx");
37985
+ if (project.hasMobxReact === true) capabilities.add("mobx-react");
37986
+ if (project.hasMobxReactLite === true) capabilities.add("mobx-react-lite");
37987
+ if (project.hasMobxReact === true || project.hasMobxReactLite === true) capabilities.add("mobx-react-binding");
37988
+ if (project.hasMobxStateTree === true) capabilities.add("mobx-state-tree");
37989
+ if (project.hasMobxReactObserver === true) capabilities.add("mobx-react-observer");
37990
+ if (project.mobxMajorVersion !== void 0 && project.mobxMajorVersion !== null && project.mobxMajorVersion >= 4 && project.mobxMajorVersion <= 6) addMajorLadder(capabilities, "mobx", project.mobxMajorVersion, 4, 6);
37991
+ if (project.zustandVersion !== void 0 && project.zustandVersion !== null) capabilities.add("zustand");
37992
+ if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addMajorLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
37854
37993
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
37855
37994
  if (project.hasReactCompiler) capabilities.add("react-compiler");
37856
37995
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
37996
+ if (project.valtioVersion !== null) capabilities.add("valtio");
37997
+ addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
37998
+ if (project.hasRemotion) {
37999
+ capabilities.add("remotion");
38000
+ addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
38001
+ }
37857
38002
  if (project.hasTypeScript) capabilities.add("typescript");
37858
38003
  if (project.preactVersion !== null) capabilities.add("preact");
37859
38004
  addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
@@ -39090,6 +39235,35 @@ const parseGithubViewerPermission = (stdout) => {
39090
39235
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
39091
39236
  };
39092
39237
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
39238
+ const parseBaselineDiffPlan = (value) => {
39239
+ const entries = splitNullSeparated(value);
39240
+ const baseFiles = /* @__PURE__ */ new Set();
39241
+ const headFiles = /* @__PURE__ */ new Set();
39242
+ for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
39243
+ const status = entries[entryIndex];
39244
+ const filePath = entries[entryIndex + 1];
39245
+ if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
39246
+ if (status === "A") {
39247
+ headFiles.add(filePath);
39248
+ continue;
39249
+ }
39250
+ if (status === "D") {
39251
+ baseFiles.add(filePath);
39252
+ continue;
39253
+ }
39254
+ if (status === "M" || status === "T") {
39255
+ baseFiles.add(filePath);
39256
+ headFiles.add(filePath);
39257
+ continue;
39258
+ }
39259
+ return null;
39260
+ }
39261
+ return {
39262
+ baseFiles: [...baseFiles],
39263
+ headFiles: [...headFiles],
39264
+ untrackedFiles: []
39265
+ };
39266
+ };
39093
39267
  const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
39094
39268
  /**
39095
39269
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
@@ -39292,6 +39466,45 @@ var Git = class Git extends Service()("react-doctor/Git") {
39292
39466
  githubViewerPermission,
39293
39467
  branchExists,
39294
39468
  mergeBase,
39469
+ baselineDiffPlan: (input) => {
39470
+ if (!isSafeGitRevision(input.ref)) return succeed$2(null);
39471
+ return gen(function* () {
39472
+ const unmerged = yield* runGit(input.directory, [
39473
+ "diff",
39474
+ "--no-ext-diff",
39475
+ "-z",
39476
+ "--name-only",
39477
+ "--diff-filter=U",
39478
+ "--relative"
39479
+ ]);
39480
+ if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
39481
+ const result = yield* runGit(input.directory, [
39482
+ "diff",
39483
+ "--no-ext-diff",
39484
+ "--no-textconv",
39485
+ "--no-renames",
39486
+ "-z",
39487
+ "--name-status",
39488
+ "--relative",
39489
+ input.ref
39490
+ ]);
39491
+ if (result.status !== 0) return null;
39492
+ const plan = parseBaselineDiffPlan(result.stdout);
39493
+ if (plan === null) return null;
39494
+ const untracked = yield* runGit(input.directory, [
39495
+ "ls-files",
39496
+ "--others",
39497
+ "--exclude-standard",
39498
+ "-z"
39499
+ ]);
39500
+ if (untracked.status !== 0) return null;
39501
+ return {
39502
+ baseFiles: plan.baseFiles,
39503
+ headFiles: plan.headFiles,
39504
+ untrackedFiles: splitNullSeparated(untracked.stdout)
39505
+ };
39506
+ }).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
39507
+ },
39295
39508
  diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
39296
39509
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
39297
39510
  if (explicitBaseBranch !== void 0) {
@@ -39441,6 +39654,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
39441
39654
  githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
39442
39655
  branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
39443
39656
  mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
39657
+ baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
39444
39658
  diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
39445
39659
  stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
39446
39660
  showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
@@ -40778,12 +40992,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
40778
40992
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
40779
40993
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
40780
40994
  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`.";
40995
+ 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`.";
40781
40996
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
40782
40997
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
40783
40998
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40784
40999
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
40785
41000
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
40786
41001
  };
41002
+ const buildSetStateInEffectMessage = (reasonSummary) => {
41003
+ const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
41004
+ return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
41005
+ };
40787
41006
  const PLUGIN_CATEGORY_MAP = {
40788
41007
  react: "Bugs",
40789
41008
  "react-hooks": "Bugs",
@@ -40841,6 +41060,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
40841
41060
  message: REACT_ERROR_BOUNDARY_MESSAGE,
40842
41061
  help: reasonDetail || help
40843
41062
  };
41063
+ if (rule === "set-state-in-effect") return {
41064
+ message: buildSetStateInEffectMessage(reasonSummary.trim()),
41065
+ help: reasonDetail || help
41066
+ };
40844
41067
  return {
40845
41068
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
40846
41069
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -43166,12 +43389,19 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
43166
43389
  */
43167
43390
  const materializeSourceTree = (input) => gen(function* () {
43168
43391
  const materializedFiles = [];
43392
+ const unmaterializedFiles = [];
43169
43393
  const resolvedTempDirectory = path.resolve(input.tempDirectory);
43170
43394
  for (const relativePath of input.files) {
43171
43395
  const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
43172
- if (content === null) continue;
43396
+ if (content === null) {
43397
+ unmaterializedFiles.push(relativePath);
43398
+ continue;
43399
+ }
43173
43400
  const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
43174
- if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
43401
+ if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
43402
+ unmaterializedFiles.push(relativePath);
43403
+ continue;
43404
+ }
43175
43405
  yield* sync(() => {
43176
43406
  fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
43177
43407
  fs.writeFileSync(candidateTargetPath, content);
@@ -43188,6 +43418,7 @@ const materializeSourceTree = (input) => gen(function* () {
43188
43418
  return {
43189
43419
  tempDirectory: input.tempDirectory,
43190
43420
  materializedFiles,
43421
+ unmaterializedFiles,
43191
43422
  cleanup: () => {
43192
43423
  try {
43193
43424
  fs.rmSync(input.tempDirectory, {
@@ -43751,4 +43982,4 @@ const toJsonReport = (result, options) => buildJsonReport({
43751
43982
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
43752
43983
 
43753
43984
  //# sourceMappingURL=index.js.map
43754
- //# debugId=3121a4e4-8a4a-55ea-ade8-47d0bf576a2f
43985
+ //# debugId=6aa1c6e1-8fcc-50d4-a076-3e864425bdc5