react-doctor 0.8.1-dev.d970055 → 0.8.1-dev.f4aa821

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]="c24708ed-97a3-59cc-81f7-4cf277ab29f2")}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]="1b59ca76-9135-54e6-9391-ce7fec27b699")}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";
@@ -37335,7 +37335,7 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
37335
37335
  if (nextMajor === null) return false;
37336
37336
  return nextMajor < currentMajor;
37337
37337
  };
37338
- const evaluateManifestFacts = (facts, packageJson, directory) => {
37338
+ const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
37339
37339
  if (facts.expo.version === null) {
37340
37340
  const spec = getDependencySpec(packageJson, "expo");
37341
37341
  if (spec !== null) facts.expo = {
@@ -37361,9 +37361,31 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
37361
37361
  const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
37362
37362
  if (spec !== null) facts.reanimatedVersion = spec;
37363
37363
  }
37364
+ if (facts.valtioVersion === null) {
37365
+ const spec = getDependencySpec(packageJson, "valtio");
37366
+ if (spec !== null) facts.valtioVersion = spec;
37367
+ }
37364
37368
  facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
37365
37369
  facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
37366
37370
  facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
37371
+ const remotionSpec = getDependencySpec(packageJson, "remotion");
37372
+ if (remotionSpec !== null) {
37373
+ facts.hasRemotionDependency = true;
37374
+ const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
37375
+ rootDirectory,
37376
+ rootPackageJson,
37377
+ packageName: "remotion",
37378
+ version: remotionSpec
37379
+ });
37380
+ const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
37381
+ if (remotionMajorVersion === null) {
37382
+ facts.hasUnknownRemotionVersion = true;
37383
+ facts.remotionVersion = null;
37384
+ } else if (!facts.hasUnknownRemotionVersion) {
37385
+ const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
37386
+ if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
37387
+ }
37388
+ }
37367
37389
  };
37368
37390
  const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
37369
37391
  const facts = {
@@ -37383,12 +37405,16 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
37383
37405
  version: null,
37384
37406
  sourceDirectory: null
37385
37407
  },
37408
+ valtioVersion: null,
37386
37409
  hasReactNativeAwarePackage: false,
37387
37410
  hasReanimatedAwarePackage: false,
37388
37411
  hasSsrDependency: false,
37412
+ hasRemotionDependency: false,
37413
+ hasUnknownRemotionVersion: false,
37414
+ remotionVersion: null,
37389
37415
  reanimatedVersion: null
37390
37416
  };
37391
- evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
37417
+ evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
37392
37418
  let isReactGroupSettled = !collectReactGroup;
37393
37419
  const visitedDirectories = /* @__PURE__ */ new Set();
37394
37420
  for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
@@ -37397,7 +37423,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
37397
37423
  if (visitedDirectories.has(workspaceDirectory)) continue;
37398
37424
  visitedDirectories.add(workspaceDirectory);
37399
37425
  const workspacePackageJson = readPackageJson$1(Path.join(workspaceDirectory, "package.json"));
37400
- evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
37426
+ evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
37401
37427
  const info = extractDependencyInfo(workspacePackageJson);
37402
37428
  if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
37403
37429
  if (isReactGroupSettled) continue;
@@ -37623,6 +37649,11 @@ const discoverProjectWithoutPackageJson = (directory) => {
37623
37649
  hasReactCompiler: false,
37624
37650
  hasReactCompilerLintPlugin: false,
37625
37651
  hasTanStackQuery: false,
37652
+ valtioVersion: null,
37653
+ valtioMajorVersion: null,
37654
+ hasRemotion: false,
37655
+ remotionVersion: null,
37656
+ remotionMajorVersion: null,
37626
37657
  hasSsrDependency: false,
37627
37658
  preactVersion: null,
37628
37659
  preactMajorVersion: null,
@@ -37729,7 +37760,14 @@ const discoverProject = (directory) => {
37729
37760
  packageName: "next",
37730
37761
  version: workspaceFacts.next.version
37731
37762
  }) : null;
37763
+ const valtioVersion = resolveCatalogBackedDependencyVersion({
37764
+ rootDirectory: directory,
37765
+ rootPackageJson: packageJson,
37766
+ packageName: "valtio",
37767
+ version: workspaceFacts.valtioVersion
37768
+ });
37732
37769
  const preactVersion = getPreactVersion(packageJson);
37770
+ const remotionVersion = workspaceFacts.remotionVersion;
37733
37771
  const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
37734
37772
  const projectInfo = {
37735
37773
  rootDirectory: directory,
@@ -37744,6 +37782,11 @@ const discoverProject = (directory) => {
37744
37782
  hasReactCompiler: detectReactCompiler(directory, packageJson),
37745
37783
  hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
37746
37784
  hasTanStackQuery: hasTanStackQuery(packageJson),
37785
+ valtioVersion,
37786
+ valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
37787
+ hasRemotion: workspaceFacts.hasRemotionDependency,
37788
+ remotionVersion,
37789
+ remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
37747
37790
  hasSsrDependency: workspaceFacts.hasSsrDependency,
37748
37791
  preactVersion,
37749
37792
  preactMajorVersion: parseReactMajor(preactVersion),
@@ -37977,7 +38020,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
37977
38020
  "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
37978
38021
  "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
37979
38022
  "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
37980
- "react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
37981
38023
  "react/jsx-no-undef": "react-doctor/jsx-no-undef",
37982
38024
  "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
37983
38025
  "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
@@ -40299,9 +40341,25 @@ const BABEL_CONFIG_FILE_NAMES = [
40299
40341
  ".babelrc.json"
40300
40342
  ];
40301
40343
  const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
40344
+ const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
40345
+ const REACT_NATIVE_PACKAGE = "react-native";
40346
+ const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
40347
+ const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
40302
40348
  const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
40303
40349
  const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
40350
+ const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
40351
+ const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
40352
+ const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson$1(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
40353
+ if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
40354
+ const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
40355
+ if (normalizedVersionRange === null) return false;
40356
+ const minimumVersion = import_semver.minVersion(normalizedVersionRange);
40357
+ return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
40358
+ };
40359
+ const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
40304
40360
  const checkReactNativeMetroBabelPreset = (rootDirectory) => {
40361
+ const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
40362
+ const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
40305
40363
  for (const fileName of BABEL_CONFIG_FILE_NAMES) {
40306
40364
  const filePath = Path.join(rootDirectory, fileName);
40307
40365
  if (!isFile(filePath)) continue;
@@ -40311,7 +40369,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
40311
40369
  } catch {
40312
40370
  continue;
40313
40371
  }
40314
- if (contents.includes(LEGACY_PRESET_SPEC)) return [buildReactNativeDiagnostic({
40372
+ if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
40315
40373
  filePath: fileName,
40316
40374
  rule: "rn-no-metro-babel-preset",
40317
40375
  severity: "error",
@@ -40334,12 +40392,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
40334
40392
  return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
40335
40393
  };
40336
40394
  const RULE_KEY = "no-vulnerable-react-server-components";
40337
- const DEPENDENCY_SECTIONS = [
40338
- "dependencies",
40339
- "devDependencies",
40340
- "peerDependencies",
40341
- "optionalDependencies"
40342
- ];
40343
40395
  const REACT_RSC_ADVISORIES_BY_MINOR = {
40344
40396
  "19.0": {
40345
40397
  rceFixedVersion: "19.0.1",
@@ -40386,20 +40438,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
40386
40438
  for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
40387
40439
  return [...directories];
40388
40440
  };
40389
- const readDeclaredSpec = (packageJson, packageName) => {
40390
- for (const section of DEPENDENCY_SECTIONS) {
40391
- const spec = packageJson[section]?.[packageName];
40392
- if (typeof spec === "string") return spec;
40393
- }
40394
- return null;
40395
- };
40396
40441
  const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
40397
40442
  const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
40398
40443
  if (isFile(manifestPath)) {
40399
40444
  const installedVersion = import_semver.valid(readPackageJson$1(manifestPath).version ?? null);
40400
40445
  if (installedVersion !== null) return installedVersion;
40401
40446
  }
40402
- const candidateSpecs = [readDeclaredSpec(readPackageJson$1(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
40447
+ const candidateSpecs = [getDependencySpec(readPackageJson$1(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
40403
40448
  for (const spec of candidateSpecs) {
40404
40449
  const pinnedVersion = spec === null ? null : import_semver.valid(spec);
40405
40450
  if (pinnedVersion !== null) return pinnedVersion;
@@ -41082,6 +41127,12 @@ const buildCapabilities = (project) => {
41082
41127
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
41083
41128
  if (project.hasReactCompiler) capabilities.add("react-compiler");
41084
41129
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
41130
+ if (project.valtioVersion !== null) capabilities.add("valtio");
41131
+ addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
41132
+ if (project.hasRemotion) {
41133
+ capabilities.add("remotion");
41134
+ addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
41135
+ }
41085
41136
  if (project.hasTypeScript) capabilities.add("typescript");
41086
41137
  if (project.preactVersion !== null) capabilities.add("preact");
41087
41138
  addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
@@ -44091,12 +44142,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
44091
44142
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
44092
44143
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
44093
44144
  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`.";
44145
+ 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`.";
44094
44146
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
44095
44147
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
44096
44148
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
44097
44149
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
44098
44150
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
44099
44151
  };
44152
+ const buildSetStateInEffectMessage = (reasonSummary) => {
44153
+ const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
44154
+ return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
44155
+ };
44100
44156
  const PLUGIN_CATEGORY_MAP = {
44101
44157
  react: "Bugs",
44102
44158
  "react-hooks": "Bugs",
@@ -44154,6 +44210,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
44154
44210
  message: REACT_ERROR_BOUNDARY_MESSAGE,
44155
44211
  help: reasonDetail || help
44156
44212
  };
44213
+ if (rule === "set-state-in-effect") return {
44214
+ message: buildSetStateInEffectMessage(reasonSummary.trim()),
44215
+ help: reasonDetail || help
44216
+ };
44157
44217
  return {
44158
44218
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
44159
44219
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -47356,7 +47416,7 @@ const makeNoopConsole = () => ({
47356
47416
  });
47357
47417
  //#endregion
47358
47418
  //#region src/cli/utils/version.ts
47359
- const VERSION = "0.8.1-dev.d970055";
47419
+ const VERSION = "0.8.1-dev.f4aa821";
47360
47420
  //#endregion
47361
47421
  //#region src/cli/utils/json-mode.ts
47362
47422
  let context = null;
@@ -47708,13 +47768,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47708
47768
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47709
47769
  * standard `SENTRY_RELEASE` override.
47710
47770
  */
47711
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.d970055`;
47771
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.f4aa821`;
47712
47772
  /**
47713
47773
  * Deployment environment shown in Sentry's environment filter. Defaults to
47714
47774
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47715
47775
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47716
47776
  */
47717
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.d970055") ? "development" : "production");
47777
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.f4aa821") ? "development" : "production");
47718
47778
  /**
47719
47779
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47720
47780
  * (set to `0` to disable tracing) and falls back to
@@ -66394,4 +66454,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
66394
66454
  export {};
66395
66455
 
66396
66456
  //# sourceMappingURL=cli.js.map
66397
- //# debugId=c24708ed-97a3-59cc-81f7-4cf277ab29f2
66457
+ //# debugId=1b59ca76-9135-54e6-9391-ce7fec27b699
package/dist/index.d.ts CHANGED
@@ -9548,6 +9548,18 @@ interface ProjectInfo {
9548
9548
  hasReactCompiler: boolean;
9549
9549
  hasReactCompilerLintPlugin?: boolean;
9550
9550
  hasTanStackQuery: boolean;
9551
+ hasRemotion?: boolean;
9552
+ remotionVersion?: string | null;
9553
+ remotionMajorVersion?: number | null;
9554
+ /**
9555
+ * The declared `valtio` version spec, or `null` when no package in the
9556
+ * analyzed project declares Valtio. `useSnapshot` has kept the same
9557
+ * render-read contract across Valtio 1 and 2, so presence alone drives the
9558
+ * `valtio` capability and its parsed major-version ladder.
9559
+ */
9560
+ valtioVersion: string | null;
9561
+ /** Parsed major from `valtioVersion`, or `null` when absent/unparseable. */
9562
+ valtioMajorVersion: number | null;
9551
9563
  /**
9552
9564
  * `true` when the project or a workspace declares a Vite-based SSR runtime
9553
9565
  * such as React Router's Node adapter, Vike, or vite-plugin-ssr.
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]="f645a3f8-ff1c-5f09-a174-8281db00eaa4")}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]="92325f1f-c593-5b4c-af5a-c8b7177212e1")}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";
@@ -34139,7 +34139,7 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
34139
34139
  if (nextMajor === null) return false;
34140
34140
  return nextMajor < currentMajor;
34141
34141
  };
34142
- const evaluateManifestFacts = (facts, packageJson, directory) => {
34142
+ const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
34143
34143
  if (facts.expo.version === null) {
34144
34144
  const spec = getDependencySpec(packageJson, "expo");
34145
34145
  if (spec !== null) facts.expo = {
@@ -34165,9 +34165,31 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
34165
34165
  const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
34166
34166
  if (spec !== null) facts.reanimatedVersion = spec;
34167
34167
  }
34168
+ if (facts.valtioVersion === null) {
34169
+ const spec = getDependencySpec(packageJson, "valtio");
34170
+ if (spec !== null) facts.valtioVersion = spec;
34171
+ }
34168
34172
  facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34169
34173
  facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34170
34174
  facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
34175
+ const remotionSpec = getDependencySpec(packageJson, "remotion");
34176
+ if (remotionSpec !== null) {
34177
+ facts.hasRemotionDependency = true;
34178
+ const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
34179
+ rootDirectory,
34180
+ rootPackageJson,
34181
+ packageName: "remotion",
34182
+ version: remotionSpec
34183
+ });
34184
+ const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
34185
+ if (remotionMajorVersion === null) {
34186
+ facts.hasUnknownRemotionVersion = true;
34187
+ facts.remotionVersion = null;
34188
+ } else if (!facts.hasUnknownRemotionVersion) {
34189
+ const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
34190
+ if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
34191
+ }
34192
+ }
34171
34193
  };
34172
34194
  const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34173
34195
  const facts = {
@@ -34187,12 +34209,16 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34187
34209
  version: null,
34188
34210
  sourceDirectory: null
34189
34211
  },
34212
+ valtioVersion: null,
34190
34213
  hasReactNativeAwarePackage: false,
34191
34214
  hasReanimatedAwarePackage: false,
34192
34215
  hasSsrDependency: false,
34216
+ hasRemotionDependency: false,
34217
+ hasUnknownRemotionVersion: false,
34218
+ remotionVersion: null,
34193
34219
  reanimatedVersion: null
34194
34220
  };
34195
- evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
34221
+ evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
34196
34222
  let isReactGroupSettled = !collectReactGroup;
34197
34223
  const visitedDirectories = /* @__PURE__ */ new Set();
34198
34224
  for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
@@ -34201,7 +34227,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34201
34227
  if (visitedDirectories.has(workspaceDirectory)) continue;
34202
34228
  visitedDirectories.add(workspaceDirectory);
34203
34229
  const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
34204
- evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
34230
+ evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
34205
34231
  const info = extractDependencyInfo(workspacePackageJson);
34206
34232
  if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
34207
34233
  if (isReactGroupSettled) continue;
@@ -34430,6 +34456,11 @@ const discoverProjectWithoutPackageJson = (directory) => {
34430
34456
  hasReactCompiler: false,
34431
34457
  hasReactCompilerLintPlugin: false,
34432
34458
  hasTanStackQuery: false,
34459
+ valtioVersion: null,
34460
+ valtioMajorVersion: null,
34461
+ hasRemotion: false,
34462
+ remotionVersion: null,
34463
+ remotionMajorVersion: null,
34433
34464
  hasSsrDependency: false,
34434
34465
  preactVersion: null,
34435
34466
  preactMajorVersion: null,
@@ -34536,7 +34567,14 @@ const discoverProject = (directory) => {
34536
34567
  packageName: "next",
34537
34568
  version: workspaceFacts.next.version
34538
34569
  }) : null;
34570
+ const valtioVersion = resolveCatalogBackedDependencyVersion({
34571
+ rootDirectory: directory,
34572
+ rootPackageJson: packageJson,
34573
+ packageName: "valtio",
34574
+ version: workspaceFacts.valtioVersion
34575
+ });
34539
34576
  const preactVersion = getPreactVersion(packageJson);
34577
+ const remotionVersion = workspaceFacts.remotionVersion;
34540
34578
  const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
34541
34579
  const projectInfo = {
34542
34580
  rootDirectory: directory,
@@ -34551,6 +34589,11 @@ const discoverProject = (directory) => {
34551
34589
  hasReactCompiler: detectReactCompiler(directory, packageJson),
34552
34590
  hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
34553
34591
  hasTanStackQuery: hasTanStackQuery(packageJson),
34592
+ valtioVersion,
34593
+ valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
34594
+ hasRemotion: workspaceFacts.hasRemotionDependency,
34595
+ remotionVersion,
34596
+ remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
34554
34597
  hasSsrDependency: workspaceFacts.hasSsrDependency,
34555
34598
  preactVersion,
34556
34599
  preactMajorVersion: parseReactMajor(preactVersion),
@@ -34784,7 +34827,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34784
34827
  "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
34785
34828
  "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
34786
34829
  "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
34787
- "react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
34788
34830
  "react/jsx-no-undef": "react-doctor/jsx-no-undef",
34789
34831
  "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
34790
34832
  "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
@@ -37071,9 +37113,25 @@ const BABEL_CONFIG_FILE_NAMES = [
37071
37113
  ".babelrc.json"
37072
37114
  ];
37073
37115
  const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
37116
+ const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
37117
+ const REACT_NATIVE_PACKAGE = "react-native";
37118
+ const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
37119
+ const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
37074
37120
  const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
37075
37121
  const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
37122
+ const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
37123
+ const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
37124
+ const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
37125
+ if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
37126
+ const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
37127
+ if (normalizedVersionRange === null) return false;
37128
+ const minimumVersion = import_semver.minVersion(normalizedVersionRange);
37129
+ return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
37130
+ };
37131
+ const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
37076
37132
  const checkReactNativeMetroBabelPreset = (rootDirectory) => {
37133
+ const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
37134
+ const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
37077
37135
  for (const fileName of BABEL_CONFIG_FILE_NAMES) {
37078
37136
  const filePath = Path.join(rootDirectory, fileName);
37079
37137
  if (!isFile(filePath)) continue;
@@ -37083,7 +37141,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
37083
37141
  } catch {
37084
37142
  continue;
37085
37143
  }
37086
- if (contents.includes(LEGACY_PRESET_SPEC)) return [buildReactNativeDiagnostic({
37144
+ if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
37087
37145
  filePath: fileName,
37088
37146
  rule: "rn-no-metro-babel-preset",
37089
37147
  severity: "error",
@@ -37106,12 +37164,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
37106
37164
  return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
37107
37165
  };
37108
37166
  const RULE_KEY = "no-vulnerable-react-server-components";
37109
- const DEPENDENCY_SECTIONS = [
37110
- "dependencies",
37111
- "devDependencies",
37112
- "peerDependencies",
37113
- "optionalDependencies"
37114
- ];
37115
37167
  const REACT_RSC_ADVISORIES_BY_MINOR = {
37116
37168
  "19.0": {
37117
37169
  rceFixedVersion: "19.0.1",
@@ -37158,20 +37210,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
37158
37210
  for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
37159
37211
  return [...directories];
37160
37212
  };
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
37213
  const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
37169
37214
  const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
37170
37215
  if (isFile(manifestPath)) {
37171
37216
  const installedVersion = import_semver.valid(readPackageJson(manifestPath).version ?? null);
37172
37217
  if (installedVersion !== null) return installedVersion;
37173
37218
  }
37174
- const candidateSpecs = [readDeclaredSpec(readPackageJson(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
37219
+ const candidateSpecs = [getDependencySpec(readPackageJson(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
37175
37220
  for (const spec of candidateSpecs) {
37176
37221
  const pinnedVersion = spec === null ? null : import_semver.valid(spec);
37177
37222
  if (pinnedVersion !== null) return pinnedVersion;
@@ -37854,6 +37899,12 @@ const buildCapabilities = (project) => {
37854
37899
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
37855
37900
  if (project.hasReactCompiler) capabilities.add("react-compiler");
37856
37901
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
37902
+ if (project.valtioVersion !== null) capabilities.add("valtio");
37903
+ addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
37904
+ if (project.hasRemotion) {
37905
+ capabilities.add("remotion");
37906
+ addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
37907
+ }
37857
37908
  if (project.hasTypeScript) capabilities.add("typescript");
37858
37909
  if (project.preactVersion !== null) capabilities.add("preact");
37859
37910
  addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
@@ -40847,12 +40898,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
40847
40898
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
40848
40899
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
40849
40900
  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`.";
40901
+ 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`.";
40850
40902
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
40851
40903
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
40852
40904
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40853
40905
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
40854
40906
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
40855
40907
  };
40908
+ const buildSetStateInEffectMessage = (reasonSummary) => {
40909
+ const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40910
+ return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
40911
+ };
40856
40912
  const PLUGIN_CATEGORY_MAP = {
40857
40913
  react: "Bugs",
40858
40914
  "react-hooks": "Bugs",
@@ -40910,6 +40966,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
40910
40966
  message: REACT_ERROR_BOUNDARY_MESSAGE,
40911
40967
  help: reasonDetail || help
40912
40968
  };
40969
+ if (rule === "set-state-in-effect") return {
40970
+ message: buildSetStateInEffectMessage(reasonSummary.trim()),
40971
+ help: reasonDetail || help
40972
+ };
40913
40973
  return {
40914
40974
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
40915
40975
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -43828,4 +43888,4 @@ const toJsonReport = (result, options) => buildJsonReport({
43828
43888
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
43829
43889
 
43830
43890
  //# sourceMappingURL=index.js.map
43831
- //# debugId=f645a3f8-ff1c-5f09-a174-8281db00eaa4
43891
+ //# debugId=92325f1f-c593-5b4c-af5a-c8b7177212e1
package/dist/lsp.js CHANGED
@@ -34191,7 +34191,7 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
34191
34191
  if (nextMajor === null) return false;
34192
34192
  return nextMajor < currentMajor;
34193
34193
  };
34194
- const evaluateManifestFacts = (facts, packageJson, directory) => {
34194
+ const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
34195
34195
  if (facts.expo.version === null) {
34196
34196
  const spec = getDependencySpec(packageJson, "expo");
34197
34197
  if (spec !== null) facts.expo = {
@@ -34217,9 +34217,31 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
34217
34217
  const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
34218
34218
  if (spec !== null) facts.reanimatedVersion = spec;
34219
34219
  }
34220
+ if (facts.valtioVersion === null) {
34221
+ const spec = getDependencySpec(packageJson, "valtio");
34222
+ if (spec !== null) facts.valtioVersion = spec;
34223
+ }
34220
34224
  facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34221
34225
  facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34222
34226
  facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
34227
+ const remotionSpec = getDependencySpec(packageJson, "remotion");
34228
+ if (remotionSpec !== null) {
34229
+ facts.hasRemotionDependency = true;
34230
+ const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
34231
+ rootDirectory,
34232
+ rootPackageJson,
34233
+ packageName: "remotion",
34234
+ version: remotionSpec
34235
+ });
34236
+ const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
34237
+ if (remotionMajorVersion === null) {
34238
+ facts.hasUnknownRemotionVersion = true;
34239
+ facts.remotionVersion = null;
34240
+ } else if (!facts.hasUnknownRemotionVersion) {
34241
+ const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
34242
+ if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
34243
+ }
34244
+ }
34223
34245
  };
34224
34246
  const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34225
34247
  const facts = {
@@ -34239,12 +34261,16 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34239
34261
  version: null,
34240
34262
  sourceDirectory: null
34241
34263
  },
34264
+ valtioVersion: null,
34242
34265
  hasReactNativeAwarePackage: false,
34243
34266
  hasReanimatedAwarePackage: false,
34244
34267
  hasSsrDependency: false,
34268
+ hasRemotionDependency: false,
34269
+ hasUnknownRemotionVersion: false,
34270
+ remotionVersion: null,
34245
34271
  reanimatedVersion: null
34246
34272
  };
34247
- evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
34273
+ evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
34248
34274
  let isReactGroupSettled = !collectReactGroup;
34249
34275
  const visitedDirectories = /* @__PURE__ */ new Set();
34250
34276
  for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
@@ -34253,7 +34279,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
34253
34279
  if (visitedDirectories.has(workspaceDirectory)) continue;
34254
34280
  visitedDirectories.add(workspaceDirectory);
34255
34281
  const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
34256
- evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
34282
+ evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
34257
34283
  const info = extractDependencyInfo(workspacePackageJson);
34258
34284
  if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
34259
34285
  if (isReactGroupSettled) continue;
@@ -34482,6 +34508,11 @@ const discoverProjectWithoutPackageJson = (directory) => {
34482
34508
  hasReactCompiler: false,
34483
34509
  hasReactCompilerLintPlugin: false,
34484
34510
  hasTanStackQuery: false,
34511
+ valtioVersion: null,
34512
+ valtioMajorVersion: null,
34513
+ hasRemotion: false,
34514
+ remotionVersion: null,
34515
+ remotionMajorVersion: null,
34485
34516
  hasSsrDependency: false,
34486
34517
  preactVersion: null,
34487
34518
  preactMajorVersion: null,
@@ -34588,7 +34619,14 @@ const discoverProject = (directory) => {
34588
34619
  packageName: "next",
34589
34620
  version: workspaceFacts.next.version
34590
34621
  }) : null;
34622
+ const valtioVersion = resolveCatalogBackedDependencyVersion({
34623
+ rootDirectory: directory,
34624
+ rootPackageJson: packageJson,
34625
+ packageName: "valtio",
34626
+ version: workspaceFacts.valtioVersion
34627
+ });
34591
34628
  const preactVersion = getPreactVersion(packageJson);
34629
+ const remotionVersion = workspaceFacts.remotionVersion;
34592
34630
  const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
34593
34631
  const projectInfo = {
34594
34632
  rootDirectory: directory,
@@ -34603,6 +34641,11 @@ const discoverProject = (directory) => {
34603
34641
  hasReactCompiler: detectReactCompiler(directory, packageJson),
34604
34642
  hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
34605
34643
  hasTanStackQuery: hasTanStackQuery(packageJson),
34644
+ valtioVersion,
34645
+ valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
34646
+ hasRemotion: workspaceFacts.hasRemotionDependency,
34647
+ remotionVersion,
34648
+ remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
34606
34649
  hasSsrDependency: workspaceFacts.hasSsrDependency,
34607
34650
  preactVersion,
34608
34651
  preactMajorVersion: parseReactMajor(preactVersion),
@@ -34836,7 +34879,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34836
34879
  "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
34837
34880
  "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
34838
34881
  "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
34839
- "react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
34840
34882
  "react/jsx-no-undef": "react-doctor/jsx-no-undef",
34841
34883
  "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
34842
34884
  "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
@@ -37049,9 +37091,25 @@ const BABEL_CONFIG_FILE_NAMES = [
37049
37091
  ".babelrc.json"
37050
37092
  ];
37051
37093
  const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
37094
+ const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
37095
+ const REACT_NATIVE_PACKAGE = "react-native";
37096
+ const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
37097
+ const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
37052
37098
  const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
37053
37099
  const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
37100
+ const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
37101
+ const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
37102
+ const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
37103
+ if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
37104
+ const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
37105
+ if (normalizedVersionRange === null) return false;
37106
+ const minimumVersion = import_semver.minVersion(normalizedVersionRange);
37107
+ return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
37108
+ };
37109
+ const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
37054
37110
  const checkReactNativeMetroBabelPreset = (rootDirectory) => {
37111
+ const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
37112
+ const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
37055
37113
  for (const fileName of BABEL_CONFIG_FILE_NAMES) {
37056
37114
  const filePath = Path.join(rootDirectory, fileName);
37057
37115
  if (!isFile(filePath)) continue;
@@ -37061,7 +37119,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
37061
37119
  } catch {
37062
37120
  continue;
37063
37121
  }
37064
- if (contents.includes(LEGACY_PRESET_SPEC)) return [buildReactNativeDiagnostic({
37122
+ if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
37065
37123
  filePath: fileName,
37066
37124
  rule: "rn-no-metro-babel-preset",
37067
37125
  severity: "error",
@@ -37084,12 +37142,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
37084
37142
  return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
37085
37143
  };
37086
37144
  const RULE_KEY = "no-vulnerable-react-server-components";
37087
- const DEPENDENCY_SECTIONS = [
37088
- "dependencies",
37089
- "devDependencies",
37090
- "peerDependencies",
37091
- "optionalDependencies"
37092
- ];
37093
37145
  const REACT_RSC_ADVISORIES_BY_MINOR = {
37094
37146
  "19.0": {
37095
37147
  rceFixedVersion: "19.0.1",
@@ -37136,20 +37188,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
37136
37188
  for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
37137
37189
  return [...directories];
37138
37190
  };
37139
- const readDeclaredSpec = (packageJson, packageName) => {
37140
- for (const section of DEPENDENCY_SECTIONS) {
37141
- const spec = packageJson[section]?.[packageName];
37142
- if (typeof spec === "string") return spec;
37143
- }
37144
- return null;
37145
- };
37146
37191
  const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
37147
37192
  const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
37148
37193
  if (isFile(manifestPath)) {
37149
37194
  const installedVersion = import_semver.valid(readPackageJson(manifestPath).version ?? null);
37150
37195
  if (installedVersion !== null) return installedVersion;
37151
37196
  }
37152
- const candidateSpecs = [readDeclaredSpec(readPackageJson(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
37197
+ const candidateSpecs = [getDependencySpec(readPackageJson(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
37153
37198
  for (const spec of candidateSpecs) {
37154
37199
  const pinnedVersion = spec === null ? null : import_semver.valid(spec);
37155
37200
  if (pinnedVersion !== null) return pinnedVersion;
@@ -37832,6 +37877,12 @@ const buildCapabilities = (project) => {
37832
37877
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
37833
37878
  if (project.hasReactCompiler) capabilities.add("react-compiler");
37834
37879
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
37880
+ if (project.valtioVersion !== null) capabilities.add("valtio");
37881
+ addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
37882
+ if (project.hasRemotion) {
37883
+ capabilities.add("remotion");
37884
+ addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
37885
+ }
37835
37886
  if (project.hasTypeScript) capabilities.add("typescript");
37836
37887
  if (project.preactVersion !== null) capabilities.add("preact");
37837
37888
  addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
@@ -40825,12 +40876,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
40825
40876
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
40826
40877
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
40827
40878
  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`.";
40879
+ 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`.";
40828
40880
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
40829
40881
  const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
40830
40882
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40831
40883
  if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
40832
40884
  return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
40833
40885
  };
40886
+ const buildSetStateInEffectMessage = (reasonSummary) => {
40887
+ const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
40888
+ return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
40889
+ };
40834
40890
  const PLUGIN_CATEGORY_MAP = {
40835
40891
  react: "Bugs",
40836
40892
  "react-hooks": "Bugs",
@@ -40888,6 +40944,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
40888
40944
  message: REACT_ERROR_BOUNDARY_MESSAGE,
40889
40945
  help: reasonDetail || help
40890
40946
  };
40947
+ if (rule === "set-state-in-effect") return {
40948
+ message: buildSetStateInEffectMessage(reasonSummary.trim()),
40949
+ help: reasonDetail || help
40950
+ };
40891
40951
  return {
40892
40952
  message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
40893
40953
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
@@ -45448,5 +45508,5 @@ const startLanguageServer = () => {
45448
45508
  };
45449
45509
  //#endregion
45450
45510
  export { startLanguageServer };
45451
- !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]="4fa679df-c187-5989-bc8a-a96791a6f02c")}catch(e){}}();
45452
- //# debugId=4fa679df-c187-5989-bc8a-a96791a6f02c
45511
+ !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]="91af1a13-aefd-5ec0-bbb6-9e2cb5c458ee")}catch(e){}}();
45512
+ //# debugId=91af1a13-aefd-5ec0-bbb6-9e2cb5c458ee
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.8.1-dev.d970055",
3
+ "version": "0.8.1-dev.f4aa821",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -65,7 +65,7 @@
65
65
  "vscode-languageserver-textdocument": "^1.0.12",
66
66
  "vscode-uri": "^3.1.0",
67
67
  "yaml": "^2.9.0",
68
- "oxlint-plugin-react-doctor": "0.8.1-dev.d970055",
68
+ "oxlint-plugin-react-doctor": "0.8.1-dev.f4aa821",
69
69
  "deslop-js": "0.8.1"
70
70
  },
71
71
  "devDependencies": {
@@ -75,8 +75,8 @@
75
75
  "commander": "^14.0.3",
76
76
  "ora": "^9.4.0",
77
77
  "@react-doctor/api": "0.8.1",
78
- "@react-doctor/core": "0.8.1",
79
- "@react-doctor/language-server": "0.8.1"
78
+ "@react-doctor/language-server": "0.8.1",
79
+ "@react-doctor/core": "0.8.1"
80
80
  },
81
81
  "engines": {
82
82
  "node": "^20.19.0 || >=22.13.0"