react-doctor 0.2.9 → 0.2.11

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.
@@ -2272,11 +2272,13 @@ const FRAMEWORK_DISPLAY_NAMES = {
2272
2272
  gatsby: "Gatsby",
2273
2273
  expo: "Expo",
2274
2274
  "react-native": "React Native",
2275
+ preact: "Preact",
2275
2276
  unknown: "React"
2276
2277
  };
2277
2278
  const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
2278
2279
  const detectFramework = (dependencies) => {
2279
2280
  for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
2281
+ if (dependencies.preact && !dependencies.react) return "preact";
2280
2282
  return "unknown";
2281
2283
  };
2282
2284
  const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
@@ -2695,6 +2697,21 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
2695
2697
  framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
2696
2698
  };
2697
2699
  };
2700
+ const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => {
2701
+ if (predicate(rootPackageJson)) return true;
2702
+ const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
2703
+ if (patterns.length === 0) return false;
2704
+ const visitedDirectories = /* @__PURE__ */ new Set();
2705
+ for (const pattern of patterns) {
2706
+ const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
2707
+ for (const workspaceDirectory of directories) {
2708
+ if (visitedDirectories.has(workspaceDirectory)) continue;
2709
+ visitedDirectories.add(workspaceDirectory);
2710
+ if (predicate(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
2711
+ }
2712
+ }
2713
+ return false;
2714
+ };
2698
2715
  const NAMES = new Set([
2699
2716
  "react-native",
2700
2717
  "react-native-tvos",
@@ -2725,20 +2742,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
2725
2742
  if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
2726
2743
  return false;
2727
2744
  };
2728
- const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => {
2729
- if (isPackageJsonReactNativeAware(rootPackageJson)) return true;
2730
- const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
2731
- if (patterns.length === 0) return false;
2732
- const visitedDirectories = /* @__PURE__ */ new Set();
2733
- for (const pattern of patterns) {
2734
- const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
2735
- for (const workspaceDirectory of directories) {
2736
- if (visitedDirectories.has(workspaceDirectory)) continue;
2737
- visitedDirectories.add(workspaceDirectory);
2738
- if (isPackageJsonReactNativeAware(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
2739
- }
2740
- }
2741
- return false;
2745
+ const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
2746
+ const hasPreact = (packageJson) => {
2747
+ return "preact" in {
2748
+ ...packageJson.peerDependencies,
2749
+ ...packageJson.dependencies,
2750
+ ...packageJson.devDependencies
2751
+ };
2742
2752
  };
2743
2753
  const TANSTACK_QUERY_PACKAGES = new Set([
2744
2754
  "@tanstack/react-query",
@@ -2753,6 +2763,16 @@ const hasTanStackQuery = (packageJson) => {
2753
2763
  };
2754
2764
  return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
2755
2765
  };
2766
+ const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
2767
+ const isPackageJsonReanimatedAware = (packageJson) => {
2768
+ const allDependencies = {
2769
+ ...packageJson.peerDependencies,
2770
+ ...packageJson.dependencies,
2771
+ ...packageJson.devDependencies,
2772
+ ...packageJson.optionalDependencies
2773
+ };
2774
+ return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
2775
+ };
2756
2776
  const hasUpperBoundOnlyPeerRange = (range) => {
2757
2777
  if (typeof range !== "string") return false;
2758
2778
  const normalizedRange = normalizeDependencyVersion(range);
@@ -2777,7 +2797,8 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
2777
2797
  const REACT_DEPENDENCY_NAMES = new Set([
2778
2798
  "react",
2779
2799
  "react-native",
2780
- "next"
2800
+ "next",
2801
+ "preact"
2781
2802
  ]);
2782
2803
  const hasReactDependency = (packageJson) => {
2783
2804
  const allDependencies = {
@@ -2926,6 +2947,7 @@ const discoverProject = (directory) => {
2926
2947
  const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
2927
2948
  const sourceFileCount = countSourceFiles(directory);
2928
2949
  const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
2950
+ const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
2929
2951
  const projectInfo = {
2930
2952
  rootDirectory: directory,
2931
2953
  projectName,
@@ -2936,12 +2958,48 @@ const discoverProject = (directory) => {
2936
2958
  hasTypeScript,
2937
2959
  hasReactCompiler: detectReactCompiler(directory, packageJson),
2938
2960
  hasTanStackQuery: hasTanStackQuery(packageJson),
2961
+ hasPreact: hasPreact(packageJson),
2939
2962
  hasReactNativeWorkspace,
2963
+ hasReanimated,
2940
2964
  sourceFileCount
2941
2965
  };
2942
2966
  cachedProjectInfos.set(directory, projectInfo);
2943
2967
  return projectInfo;
2944
2968
  };
2969
+ const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
2970
+ const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
2971
+ const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
2972
+ const parseReactMajorMinor = (reactVersion) => {
2973
+ if (typeof reactVersion !== "string") return null;
2974
+ const trimmed = reactVersion.trim();
2975
+ if (trimmed.length === 0) return null;
2976
+ const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
2977
+ if (lowerBoundsOnly.length === 0) return null;
2978
+ const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
2979
+ if (majorMinorMatch) {
2980
+ const major = Number.parseInt(majorMinorMatch[1], 10);
2981
+ const minor = Number.parseInt(majorMinorMatch[2], 10);
2982
+ if (!Number.isFinite(major) || major <= 0) return null;
2983
+ if (!Number.isFinite(minor) || minor < 0) return null;
2984
+ return {
2985
+ major,
2986
+ minor
2987
+ };
2988
+ }
2989
+ const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
2990
+ if (!majorOnlyMatch) return null;
2991
+ const major = Number.parseInt(majorOnlyMatch[1], 10);
2992
+ if (!Number.isFinite(major) || major <= 0) return null;
2993
+ return {
2994
+ major,
2995
+ minor: 0
2996
+ };
2997
+ };
2998
+ const isReactAtLeast = (detected, required) => {
2999
+ if (detected === null) return true;
3000
+ if (detected.major !== required.major) return detected.major > required.major;
3001
+ return detected.minor >= required.minor;
3002
+ };
2945
3003
  const parseTailwindMajorMinor = (tailwindVersion) => {
2946
3004
  if (typeof tailwindVersion !== "string") return null;
2947
3005
  const trimmed = tailwindVersion.trim();
@@ -2972,6 +3030,7 @@ const isTailwindAtLeast = (detected, required) => {
2972
3030
  return detected.minor >= required.minor;
2973
3031
  };
2974
3032
  const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
3033
+ const MILLISECONDS_PER_SECOND = 1e3;
2975
3034
  const SCORE_API_URL = "https://www.react.doctor/api/score";
2976
3035
  const SHARE_BASE_URL = "https://www.react.doctor/share";
2977
3036
  const FETCH_TIMEOUT_MS = 1e4;
@@ -4491,6 +4550,59 @@ const collectIgnorePatterns = (rootDirectory) => {
4491
4550
  return patterns;
4492
4551
  };
4493
4552
  const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
4553
+ const DEAD_CODE_WORKER_SCRIPT = `
4554
+ const inputChunks = [];
4555
+ process.stdin.on("data", (chunk) => inputChunks.push(chunk));
4556
+ process.stdin.on("end", () => {
4557
+ const workerInput = JSON.parse(Buffer.concat(inputChunks).toString("utf8"));
4558
+
4559
+ const normalizeResult = (result) => ({
4560
+ unusedFiles: result.unusedFiles.map((unusedFile) => ({
4561
+ path: unusedFile.path,
4562
+ })),
4563
+ unusedExports: result.unusedExports.map((unusedExport) => ({
4564
+ path: unusedExport.path,
4565
+ name: unusedExport.name,
4566
+ line: unusedExport.line,
4567
+ column: unusedExport.column,
4568
+ isTypeOnly: unusedExport.isTypeOnly,
4569
+ })),
4570
+ unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
4571
+ name: unusedDependency.name,
4572
+ isDevDependency: unusedDependency.isDevDependency,
4573
+ })),
4574
+ circularDependencies: result.circularDependencies.map((cycle) => ({
4575
+ files: cycle.files,
4576
+ })),
4577
+ });
4578
+
4579
+ const serializeError = (error) =>
4580
+ error instanceof Error
4581
+ ? { name: error.name, message: error.message, stack: error.stack }
4582
+ : { message: String(error) };
4583
+
4584
+ const emit = (message) => {
4585
+ process.stdout.write(JSON.stringify(message), () => process.exit(0));
4586
+ };
4587
+
4588
+ (async () => {
4589
+ try {
4590
+ const { analyze, defineConfig } = await import(workerInput.deslopJsModuleSpecifier);
4591
+ const config = {
4592
+ rootDir: workerInput.rootDirectory,
4593
+ ...(workerInput.tsConfigPath ? { tsConfigPath: workerInput.tsConfigPath } : {}),
4594
+ ...(workerInput.ignorePatterns.length > 0
4595
+ ? { ignorePatterns: workerInput.ignorePatterns }
4596
+ : {}),
4597
+ };
4598
+ const result = await analyze(defineConfig(config));
4599
+ emit({ ok: true, result: normalizeResult(result) });
4600
+ } catch (error) {
4601
+ emit({ ok: false, error: serializeError(error) });
4602
+ }
4603
+ })();
4604
+ });
4605
+ `;
4494
4606
  const resolveTsConfigPath = (rootDirectory) => {
4495
4607
  for (const filename of TSCONFIG_FILENAMES$1) {
4496
4608
  const candidate = path.join(rootDirectory, filename);
@@ -4511,16 +4623,191 @@ const toRelativeFilePath = (rootDirectory, filePath) => {
4511
4623
  const relative = toRelativePath(filePath, rootDirectory);
4512
4624
  return relative.length > 0 ? relative : filePath.replace(/\\/g, "/");
4513
4625
  };
4626
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4627
+ const parseArray = (value, label) => {
4628
+ if (!Array.isArray(value)) throw new Error(`Dead-code worker returned invalid ${label}.`);
4629
+ return value;
4630
+ };
4631
+ const parseString = (value, label) => {
4632
+ if (typeof value !== "string") throw new Error(`Dead-code worker returned invalid ${label}.`);
4633
+ return value;
4634
+ };
4635
+ const parseNumber = (value, label) => {
4636
+ if (typeof value !== "number") throw new Error(`Dead-code worker returned invalid ${label}.`);
4637
+ return value;
4638
+ };
4639
+ const parseBoolean = (value, label) => {
4640
+ if (typeof value !== "boolean") throw new Error(`Dead-code worker returned invalid ${label}.`);
4641
+ return value;
4642
+ };
4643
+ const parseStringArray = (value, label) => {
4644
+ return parseArray(value, label).map((entry, index) => parseString(entry, `${label}[${index}]`));
4645
+ };
4646
+ const parseUnusedFiles = (value) => {
4647
+ const values = parseArray(value, "unusedFiles");
4648
+ const unusedFiles = [];
4649
+ for (const [index, entry] of values.entries()) {
4650
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedFiles[${index}].`);
4651
+ unusedFiles.push({ path: parseString(entry.path, `unusedFiles[${index}].path`) });
4652
+ }
4653
+ return unusedFiles;
4654
+ };
4655
+ const parseUnusedExports = (value) => {
4656
+ const values = parseArray(value, "unusedExports");
4657
+ const unusedExports = [];
4658
+ for (const [index, entry] of values.entries()) {
4659
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedExports[${index}].`);
4660
+ unusedExports.push({
4661
+ path: parseString(entry.path, `unusedExports[${index}].path`),
4662
+ name: parseString(entry.name, `unusedExports[${index}].name`),
4663
+ line: parseNumber(entry.line, `unusedExports[${index}].line`),
4664
+ column: parseNumber(entry.column, `unusedExports[${index}].column`),
4665
+ isTypeOnly: parseBoolean(entry.isTypeOnly, `unusedExports[${index}].isTypeOnly`)
4666
+ });
4667
+ }
4668
+ return unusedExports;
4669
+ };
4670
+ const parseUnusedDependencies = (value) => {
4671
+ const values = parseArray(value, "unusedDependencies");
4672
+ const unusedDependencies = [];
4673
+ for (const [index, entry] of values.entries()) {
4674
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedDependencies[${index}].`);
4675
+ unusedDependencies.push({
4676
+ name: parseString(entry.name, `unusedDependencies[${index}].name`),
4677
+ isDevDependency: parseBoolean(entry.isDevDependency, `unusedDependencies[${index}].isDevDependency`)
4678
+ });
4679
+ }
4680
+ return unusedDependencies;
4681
+ };
4682
+ const parseCircularDependencies = (value) => {
4683
+ const values = parseArray(value, "circularDependencies");
4684
+ const circularDependencies = [];
4685
+ for (const [index, entry] of values.entries()) {
4686
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid circularDependencies[${index}].`);
4687
+ circularDependencies.push({ files: parseStringArray(entry.files, `circularDependencies[${index}].files`) });
4688
+ }
4689
+ return circularDependencies;
4690
+ };
4691
+ const parseDeadCodeWorkerResult = (value) => {
4692
+ if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
4693
+ return {
4694
+ unusedFiles: parseUnusedFiles(value.unusedFiles),
4695
+ unusedExports: parseUnusedExports(value.unusedExports),
4696
+ unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
4697
+ circularDependencies: parseCircularDependencies(value.circularDependencies)
4698
+ };
4699
+ };
4700
+ const parseDeadCodeWorkerError = (value) => {
4701
+ if (!isRecord(value) || typeof value.message !== "string") return { message: "Dead-code worker failed." };
4702
+ return {
4703
+ ...typeof value.name === "string" ? { name: value.name } : {},
4704
+ message: value.message,
4705
+ ...typeof value.stack === "string" ? { stack: value.stack } : {}
4706
+ };
4707
+ };
4708
+ const parseDeadCodeWorkerMessage = (value) => {
4709
+ if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid message.");
4710
+ if (value.ok === true) return {
4711
+ ok: true,
4712
+ result: value.result
4713
+ };
4714
+ if (value.ok === false) return {
4715
+ ok: false,
4716
+ error: parseDeadCodeWorkerError(value.error)
4717
+ };
4718
+ throw new Error("Dead-code worker returned an invalid status.");
4719
+ };
4720
+ const buildDeadCodeWorkerError = (workerError) => {
4721
+ const error = new Error(workerError.message);
4722
+ if (workerError.name !== void 0) error.name = workerError.name;
4723
+ if (workerError.stack !== void 0) error.stack = workerError.stack;
4724
+ return error;
4725
+ };
4726
+ const createDeadCodeWorker = (input) => {
4727
+ const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
4728
+ stdio: [
4729
+ "pipe",
4730
+ "pipe",
4731
+ "pipe"
4732
+ ],
4733
+ windowsHide: true
4734
+ });
4735
+ const stdoutChunks = [];
4736
+ const stderrChunks = [];
4737
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
4738
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
4739
+ let didSettle = false;
4740
+ const result = new Promise((resolve, reject) => {
4741
+ const settle = (callback) => {
4742
+ if (didSettle) return;
4743
+ didSettle = true;
4744
+ callback();
4745
+ };
4746
+ child.once("error", (error) => {
4747
+ settle(() => reject(error));
4748
+ });
4749
+ child.once("close", (exitCode) => {
4750
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
4751
+ if (stdout.length === 0) {
4752
+ const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
4753
+ settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode ?? "null"}${stderr ? `: ${stderr}` : ""}.`)));
4754
+ return;
4755
+ }
4756
+ try {
4757
+ const parsedMessage = parseDeadCodeWorkerMessage(JSON.parse(stdout));
4758
+ if (parsedMessage.ok) {
4759
+ settle(() => resolve(parsedMessage.result));
4760
+ return;
4761
+ }
4762
+ settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
4763
+ } catch (error) {
4764
+ settle(() => reject(error));
4765
+ }
4766
+ });
4767
+ });
4768
+ child.stdin.on("error", () => {});
4769
+ child.stdin.end(JSON.stringify(input));
4770
+ return {
4771
+ result,
4772
+ terminate: () => {
4773
+ didSettle = true;
4774
+ child.kill("SIGKILL");
4775
+ }
4776
+ };
4777
+ };
4778
+ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
4779
+ let didSettle = false;
4780
+ const timeoutHandle = setTimeout(() => {
4781
+ if (didSettle) return;
4782
+ didSettle = true;
4783
+ handle.terminate?.();
4784
+ reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
4785
+ }, timeoutMs);
4786
+ timeoutHandle.unref?.();
4787
+ handle.result.then((value) => {
4788
+ if (didSettle) return;
4789
+ didSettle = true;
4790
+ clearTimeout(timeoutHandle);
4791
+ handle.terminate?.();
4792
+ resolve(value);
4793
+ }, (error) => {
4794
+ if (didSettle) return;
4795
+ didSettle = true;
4796
+ clearTimeout(timeoutHandle);
4797
+ handle.terminate?.();
4798
+ reject(error);
4799
+ });
4800
+ });
4514
4801
  const checkDeadCode = async (options) => {
4515
4802
  const { rootDirectory, userConfig } = options;
4516
4803
  if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
4517
- const { analyze, defineConfig } = await import("deslop-js");
4518
4804
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
4519
- const result = await analyze(defineConfig({
4520
- rootDir: rootDirectory,
4805
+ const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
4806
+ rootDirectory,
4521
4807
  tsConfigPath: resolveTsConfigPath(rootDirectory),
4522
- ...ignorePatterns.length > 0 ? { ignorePatterns } : {}
4523
- }));
4808
+ ignorePatterns,
4809
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
4810
+ }), options.workerTimeoutMs ?? 12e4));
4524
4811
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
4525
4812
  const diagnostics = [];
4526
4813
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -5144,7 +5431,15 @@ const buildCapabilities = (project) => {
5144
5431
  capabilities.add(project.framework);
5145
5432
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
5146
5433
  const reactMajor = project.reactMajorVersion;
5147
- if (reactMajor !== null) for (let major = 17; major <= reactMajor; major++) capabilities.add(`react:${major}`);
5434
+ if (reactMajor !== null) {
5435
+ for (let major = 17; major <= reactMajor; major++) capabilities.add(`react:${major}`);
5436
+ if (reactMajor >= 19) {
5437
+ if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
5438
+ major: 19,
5439
+ minor: 2
5440
+ })) capabilities.add("react:19.2");
5441
+ }
5442
+ }
5148
5443
  if (project.tailwindVersion !== null) {
5149
5444
  capabilities.add("tailwind");
5150
5445
  if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
@@ -5155,6 +5450,10 @@ const buildCapabilities = (project) => {
5155
5450
  if (project.hasReactCompiler) capabilities.add("react-compiler");
5156
5451
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
5157
5452
  if (project.hasTypeScript) capabilities.add("typescript");
5453
+ if (project.hasPreact) {
5454
+ capabilities.add("preact");
5455
+ if (project.reactVersion === null) capabilities.add("pure-preact");
5456
+ }
5158
5457
  return capabilities;
5159
5458
  };
5160
5459
  const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
@@ -5409,6 +5708,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
5409
5708
  if (!publicEnvPrefix) return fallbackRecommendation;
5410
5709
  return `Move secrets to server-only code. In ${formatFrameworkName(project.framework)}, only \`${publicEnvPrefix}\` env vars are exposed to the browser, and they must not contain secrets`;
5411
5710
  };
5711
+ const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
5712
+ const appendReanimatedSharedValueHint = (help, rule, project) => {
5713
+ if (rule !== "immutability") return help;
5714
+ if (!project.hasReanimated) return help;
5715
+ if (!help) return REANIMATED_SHARED_VALUE_HINT;
5716
+ return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
5717
+ };
5412
5718
  const REACT_MODULE_SOURCE = "react";
5413
5719
  const REQUIRE_IDENTIFIER = "require";
5414
5720
  const USE_IDENTIFIER = "use";
@@ -5732,7 +6038,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
5732
6038
  const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
5733
6039
  if (plugin === "react-hooks-js") return {
5734
6040
  message: REACT_COMPILER_MESSAGE,
5735
- help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
6041
+ help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
5736
6042
  };
5737
6043
  return {
5738
6044
  message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
@@ -6484,17 +6790,6 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6484
6790
  didFail: false,
6485
6791
  reason: null
6486
6792
  });
6487
- const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
6488
- const deadCodeFiber = yield* Effect.forkChild(shouldRunDeadCode ? Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
6489
- rootDirectory: scanDirectory,
6490
- userConfig: resolvedConfig.config
6491
- }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6492
- yield* Ref.set(deadCodeFailure, {
6493
- didFail: true,
6494
- reason: error.message
6495
- });
6496
- return Stream.empty;
6497
- })))))) : Effect.succeed([]));
6498
6793
  const scanProgress = yield* progressService.start("Scanning...");
6499
6794
  const scanStartTime = Date.now();
6500
6795
  let lastReportedTotalFileCount = 0;
@@ -6524,11 +6819,18 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6524
6819
  const lintCollected = yield* Stream.runCollect(applyPerElementPipeline(rawLintStream));
6525
6820
  const lintFailureState = yield* Ref.get(lintFailure);
6526
6821
  yield* afterLint(lintFailureState.didFail);
6527
- if (lintFailureState.didFail) {
6528
- yield* Fiber.interrupt(deadCodeFiber);
6529
- yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
6530
- }
6531
- const deadCodeCollected = lintFailureState.didFail ? [] : yield* Fiber.join(deadCodeFiber);
6822
+ if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
6823
+ const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
6824
+ const deadCodeCollected = lintFailureState.didFail || !shouldRunDeadCode ? [] : yield* scanProgress.update("Analyzing dead code...").pipe(Effect.andThen(Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
6825
+ rootDirectory: scanDirectory,
6826
+ userConfig: resolvedConfig.config
6827
+ }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6828
+ yield* Ref.set(deadCodeFailure, {
6829
+ didFail: true,
6830
+ reason: error.message
6831
+ });
6832
+ return Stream.empty;
6833
+ }))))))));
6532
6834
  const deadCodeFailureState = yield* Ref.get(deadCodeFailure);
6533
6835
  const scanElapsedSeconds = ((Date.now() - scanStartTime) / 1e3).toFixed(1);
6534
6836
  const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
@@ -7023,7 +7325,7 @@ var cli_logger_exports = /* @__PURE__ */ __exportAll({ cliLogger: () => cliLogge
7023
7325
  /**
7024
7326
  * Thin synchronous façade over Effect's `Console` module. Used by
7025
7327
  * the imperative CLI helper files (`select-projects`, `run-explain`,
7026
- * `install-skill`, the legacy paths in `cli/commands/inspect.ts`)
7328
+ * `install-react-doctor`, the legacy paths in `cli/commands/inspect.ts`)
7027
7329
  * that aren't yet Effect-typed. Every call drains into a single
7028
7330
  * `Console.*` Effect via `Effect.runSync`, so the underlying logging
7029
7331
  * pipeline is identical to the canonical `yield* Console.log(...)`
@@ -7056,4 +7358,4 @@ const cliLogger = {
7056
7358
  //#endregion
7057
7359
  export { isReactDoctorError as A, filterSourceFiles as C, groupBy as D, getDiffInfo as E, runInspect as F, toRelativePath as I, listWorkspacePackages as M, resolveScanTarget as N, highlighter as O, restoreLegacyThrow as P, filterDiagnosticsForSurface as S, formatReactDoctorError as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, layerOtlp as j, isMonorepoRoot as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, formatErrorChain as w, discoverReactSubprojects as x, buildJsonReport as y };
7058
7360
 
7059
- //# sourceMappingURL=cli-logger-BliQX9s8.js.map
7361
+ //# sourceMappingURL=cli-logger-pbFEieEc.js.map