react-doctor 0.9.0 → 0.9.1-dev.5d2f66d

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.
Files changed (4) hide show
  1. package/dist/cli.js +351 -32
  2. package/dist/index.js +347 -28
  3. package/dist/lsp.js +348 -29
  4. package/package.json +6 -6
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]="702b5b0d-3117-5622-b161-1cb17deb6636")}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]="6d9ce7c4-f6fc-597c-8340-3d408ed860e3")}catch(e){}}();
3
3
  import { i as __toESM$1, r as __require, t as __commonJSMin$1 } from "./chunk-ChKA3wi6.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";
@@ -61791,7 +61791,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
61791
61791
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
61792
61792
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
61793
61793
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
61794
- const CORE_PACKAGE_VERSION = "0.9.0";
61794
+ const CORE_PACKAGE_VERSION = "0.9.1";
61795
61795
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
61796
61796
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
61797
61797
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -62182,6 +62182,7 @@ const frameworkMergeRank = (framework) => {
62182
62182
  return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
62183
62183
  };
62184
62184
  const REACT_COMPILER_LINT_PACKAGES = new Set(["eslint-plugin-react-compiler"]);
62185
+ const REACT_COMPILER_RUNTIME_PACKAGES = new Set(["react-compiler-runtime"]);
62185
62186
  const NEXT_CONFIG_FILENAMES = [
62186
62187
  "next.config.js",
62187
62188
  "next.config.mjs",
@@ -62284,7 +62285,7 @@ const resolveImportedConfigFile = (fromFilePath, moduleSpecifier) => {
62284
62285
  ]).find(isFile) ?? null;
62285
62286
  };
62286
62287
  const parseConfigSourceFile = (filePath, content) => filePath.endsWith(".json") || Path.basename(filePath) === ".babelrc" ? ts.parseJsonText(filePath, content) : ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
62287
- const getStaticPropertyName$1 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
62288
+ const getStaticPropertyName$2 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
62288
62289
  const isCommonJsConfigExportAssignment = (node, sourceFile) => ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && (node.left.getText(sourceFile) === "module.exports" || node.left.getText(sourceFile) === "exports.default");
62289
62290
  const hasExportModifier = (node) => ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
62290
62291
  const getRequireModuleSpecifier = (expression) => {
@@ -62348,7 +62349,7 @@ const getImportBinding = (sourceFile, bindingName) => {
62348
62349
  const moduleSpecifier = getRequireModuleSpecifier(declaration.initializer);
62349
62350
  if (bindingElement && moduleSpecifier) return {
62350
62351
  moduleSpecifier,
62351
- exportName: bindingElement.propertyName ? getStaticPropertyName$1(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
62352
+ exportName: bindingElement.propertyName ? getStaticPropertyName$2(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
62352
62353
  isNamespace: false
62353
62354
  };
62354
62355
  }
@@ -62509,7 +62510,7 @@ const getReactCompilerFlagState = (expression, analysis) => {
62509
62510
  }
62510
62511
  if (!ts.isObjectLiteralExpression(unwrappedExpression)) return null;
62511
62512
  for (const property of [...unwrappedExpression.properties].reverse()) {
62512
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
62513
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
62513
62514
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.name, analysis) };
62514
62515
  if (ts.isSpreadAssignment(property)) {
62515
62516
  const spreadState = getReactCompilerFlagState(property.expression, analysis);
@@ -62535,8 +62536,8 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
62535
62536
  }
62536
62537
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
62537
62538
  for (const property of [...resolvedExpression.properties].reverse()) {
62538
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === propertyName) return property.initializer;
62539
- if (ts.isMethodDeclaration(property) && getStaticPropertyName$1(property.name) === propertyName) return property;
62539
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === propertyName) return property.initializer;
62540
+ if (ts.isMethodDeclaration(property) && getStaticPropertyName$2(property.name) === propertyName) return property;
62540
62541
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
62541
62542
  if (ts.isSpreadAssignment(property)) {
62542
62543
  const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
@@ -62567,7 +62568,7 @@ const configExpressionMayDefineProperty = (expression, propertyName, analysis) =
62567
62568
  return resolvedExpression.properties.some((property) => {
62568
62569
  if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
62569
62570
  if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
62570
- const staticPropertyName = getStaticPropertyName$1(property.name);
62571
+ const staticPropertyName = getStaticPropertyName$2(property.name);
62571
62572
  return staticPropertyName === null || staticPropertyName === propertyName;
62572
62573
  }
62573
62574
  return false;
@@ -62586,7 +62587,7 @@ const getExportedConfigNodes = (sourceFile, exportName) => {
62586
62587
  continue;
62587
62588
  }
62588
62589
  if (ts.isExpressionStatement(statement) && isCommonJsConfigExportAssignment(statement.expression, sourceFile) && exportName !== "default" && ts.isObjectLiteralExpression(statement.expression.right)) for (const property of [...statement.expression.right.properties].reverse()) {
62589
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === exportName) {
62590
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === exportName) {
62590
62591
  exportedNodes.push(property.initializer);
62591
62592
  break;
62592
62593
  }
@@ -62745,10 +62746,10 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
62745
62746
  const reactCompilerFlagState = excludedPropertyNames?.has("reactCompiler") ? null : getReactCompilerFlagState(node, analysis);
62746
62747
  if (reactCompilerFlagState?.isEnabled) return true;
62747
62748
  for (const [propertyIndex, property] of node.properties.entries()) if (ts.isPropertyAssignment(property)) {
62748
- const propertyName = getStaticPropertyName$1(property.name);
62749
+ const propertyName = getStaticPropertyName$2(property.name);
62749
62750
  if (propertyName && excludedPropertyNames?.has(propertyName)) continue;
62750
62751
  if (propertyName === "reactCompiler") continue;
62751
- if ((propertyName === "plugins" || propertyName === "presets") && node.properties.slice(propertyIndex + 1).some((laterProperty) => ts.isSpreadAssignment(laterProperty) && configExpressionMayDefineProperty(laterProperty.expression, propertyName, analysis) || (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && getStaticPropertyName$1(laterProperty.name) === propertyName)) continue;
62752
+ if ((propertyName === "plugins" || propertyName === "presets") && node.properties.slice(propertyIndex + 1).some((laterProperty) => ts.isSpreadAssignment(laterProperty) && configExpressionMayDefineProperty(laterProperty.expression, propertyName, analysis) || (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && getStaticPropertyName$2(laterProperty.name) === propertyName)) continue;
62752
62753
  const propertyAllowsCompilerTransform = propertyName === "plugins" || propertyName === "presets";
62753
62754
  if (propertyName === "extends" && ts.isStringLiteralLike(property.initializer) && property.initializer.text.startsWith(".")) {
62754
62755
  const extendedFilePath = resolveImportedConfigFile(analysis.filePath, property.initializer.text);
@@ -62758,12 +62759,12 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
62758
62759
  } else if (ts.isShorthandPropertyAssignment(property)) {
62759
62760
  if (excludedPropertyNames?.has(property.name.text)) continue;
62760
62761
  if (property.name.text === "reactCompiler") continue;
62761
- if ((property.name.text === "plugins" || property.name.text === "presets") && node.properties.slice(propertyIndex + 1).some((laterProperty) => ts.isSpreadAssignment(laterProperty) && configExpressionMayDefineProperty(laterProperty.expression, property.name.text, analysis) || (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && getStaticPropertyName$1(laterProperty.name) === property.name.text)) continue;
62762
+ if ((property.name.text === "plugins" || property.name.text === "presets") && node.properties.slice(propertyIndex + 1).some((laterProperty) => ts.isSpreadAssignment(laterProperty) && configExpressionMayDefineProperty(laterProperty.expression, property.name.text, analysis) || (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && getStaticPropertyName$2(laterProperty.name) === property.name.text)) continue;
62762
62763
  const propertyAllowsCompilerTransform = property.name.text === "plugins" || property.name.text === "presets";
62763
62764
  if (analyzeConfigNode(property.name, analysis, propertyAllowsCompilerTransform, propertyAllowsCompilerTransform)) return true;
62764
62765
  } else if (ts.isSpreadAssignment(property)) {
62765
62766
  if (reactCompilerFlagState && getReactCompilerFlagState(property.expression, analysis) !== null) continue;
62766
- if (node.properties.slice(propertyIndex + 1).some((laterProperty) => (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && (getStaticPropertyName$1(laterProperty.name) === "plugins" || getStaticPropertyName$1(laterProperty.name) === "presets"))) continue;
62767
+ if (node.properties.slice(propertyIndex + 1).some((laterProperty) => (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && (getStaticPropertyName$2(laterProperty.name) === "plugins" || getStaticPropertyName$2(laterProperty.name) === "presets"))) continue;
62767
62768
  if (analyzeConfigNode(property.expression, analysis, allowCompilerTransform, false, excludedPropertyNames)) return true;
62768
62769
  }
62769
62770
  return false;
@@ -62888,7 +62889,7 @@ const hasCompilerConfigurationInAncestors = (directory) => {
62888
62889
  }
62889
62890
  return false;
62890
62891
  };
62891
- const detectReactCompiler = (directory, packageJson) => hasCompilerConfiguration(directory, packageJson) || hasCompilerConfigurationInAncestors(directory);
62892
+ const detectReactCompiler = (directory, packageJson) => hasCompilerPackage(packageJson, REACT_COMPILER_RUNTIME_PACKAGES) || hasCompilerPackageInAncestors(directory, REACT_COMPILER_RUNTIME_PACKAGES) || hasCompilerConfiguration(directory, packageJson) || hasCompilerConfigurationInAncestors(directory);
62892
62893
  const detectReactCompilerLintPlugin = (directory, packageJson) => hasCompilerPackage(packageJson, REACT_COMPILER_LINT_PACKAGES) || hasCompilerPackageInAncestors(directory, REACT_COMPILER_LINT_PACKAGES);
62893
62894
  const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
62894
62895
  const filePath = Path.join(directory, filename);
@@ -66976,6 +66977,321 @@ const buildReactNativeDiagnostic = (input) => ({
66976
66977
  column: input.column ?? 0,
66977
66978
  category: input.category ?? "Correctness"
66978
66979
  });
66980
+ const ANDROID_APP_BUILD_FILE_NAMES = ["build.gradle", "build.gradle.kts"];
66981
+ const BUILD_TYPES_BLOCK_PATTERN = /\bbuildTypes\s*\{/g;
66982
+ const RELEASE_STRING_MARKER = "__RD_RELEASE__";
66983
+ const RELEASE_BLOCK_PATTERNS = [/\brelease\s*\{/g, /\b(?:getByName|named)\s*\(\s*(?:["']release["']|__RD_RELEASE__)\s*\)\s*\{/g];
66984
+ const MINIFY_ASSIGNMENT_PATTERN = /\b(?:isMinifyEnabled|minifyEnabled)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
66985
+ const SHRINK_RESOURCES_ASSIGNMENT_PATTERN = /\b(?:isShrinkResources|shrinkResources)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
66986
+ const stripGradleComments = (contents) => {
66987
+ let result = "";
66988
+ let index = 0;
66989
+ let quote = null;
66990
+ while (index < contents.length) {
66991
+ const character = contents[index];
66992
+ const nextCharacter = contents[index + 1];
66993
+ if (quote !== null) {
66994
+ result += character;
66995
+ if (character === "\\") {
66996
+ result += nextCharacter ?? "";
66997
+ index += 2;
66998
+ continue;
66999
+ }
67000
+ if (character === quote) quote = null;
67001
+ index += 1;
67002
+ continue;
67003
+ }
67004
+ if (character === "\"" || character === "'") {
67005
+ quote = character;
67006
+ result += character;
67007
+ index += 1;
67008
+ continue;
67009
+ }
67010
+ if (character === "/" && nextCharacter === "/") {
67011
+ while (index < contents.length && contents[index] !== "\n") {
67012
+ result += " ";
67013
+ index += 1;
67014
+ }
67015
+ continue;
67016
+ }
67017
+ if (character === "/" && nextCharacter === "*") {
67018
+ result += " ";
67019
+ index += 2;
67020
+ while (index < contents.length && !(contents[index] === "*" && contents[index + 1] === "/")) {
67021
+ result += contents[index] === "\n" ? "\n" : " ";
67022
+ index += 1;
67023
+ }
67024
+ if (index < contents.length) {
67025
+ result += " ";
67026
+ index += 2;
67027
+ }
67028
+ continue;
67029
+ }
67030
+ result += character;
67031
+ index += 1;
67032
+ }
67033
+ return result;
67034
+ };
67035
+ const maskGradleStrings = (contents) => {
67036
+ let result = "";
67037
+ let index = 0;
67038
+ let quote = null;
67039
+ while (index < contents.length) {
67040
+ const character = contents[index];
67041
+ if (quote === null) {
67042
+ if (character === "\"" || character === "'") {
67043
+ quote = character;
67044
+ result += " ";
67045
+ } else result += character;
67046
+ index += 1;
67047
+ continue;
67048
+ }
67049
+ if (character === "\\") {
67050
+ result += " ";
67051
+ index += 2;
67052
+ continue;
67053
+ }
67054
+ if (character === quote) quote = null;
67055
+ result += character === "\n" ? "\n" : " ";
67056
+ index += 1;
67057
+ }
67058
+ return result;
67059
+ };
67060
+ const findBlockContents = (contents, pattern) => {
67061
+ const blocks = [];
67062
+ pattern.lastIndex = 0;
67063
+ let match = pattern.exec(contents);
67064
+ while (match !== null) {
67065
+ const openingBraceIndex = pattern.lastIndex - 1;
67066
+ let depth = 1;
67067
+ let index = openingBraceIndex + 1;
67068
+ let quote = null;
67069
+ while (index < contents.length && depth > 0) {
67070
+ const character = contents[index];
67071
+ if (quote !== null) {
67072
+ if (character === "\\") {
67073
+ index += 2;
67074
+ continue;
67075
+ }
67076
+ if (character === quote) quote = null;
67077
+ } else if (character === "\"" || character === "'") quote = character;
67078
+ else if (character === "{") depth += 1;
67079
+ else if (character === "}") depth -= 1;
67080
+ index += 1;
67081
+ }
67082
+ if (depth === 0) blocks.push(contents.slice(openingBraceIndex + 1, index - 1));
67083
+ match = pattern.exec(contents);
67084
+ }
67085
+ return blocks;
67086
+ };
67087
+ const getFinalStaticBoolean = (contents, pattern) => {
67088
+ pattern.lastIndex = 0;
67089
+ let finalValue = null;
67090
+ let match = pattern.exec(contents);
67091
+ while (match !== null) {
67092
+ const staticValue = match[1]?.trim().replace(/\)$/, "").trim();
67093
+ if (staticValue === "true") finalValue = true;
67094
+ else if (staticValue === "false") finalValue = false;
67095
+ else finalValue = null;
67096
+ match = pattern.exec(contents);
67097
+ }
67098
+ return finalValue;
67099
+ };
67100
+ const getReleaseBlocks = (contents) => findBlockContents(contents, BUILD_TYPES_BLOCK_PATTERN).flatMap((buildTypesBlock) => RELEASE_BLOCK_PATTERNS.flatMap((pattern) => findBlockContents(buildTypesBlock, pattern)));
67101
+ const readAndroidReleaseShrinking = (rootDirectory) => {
67102
+ for (const buildFileName of ANDROID_APP_BUILD_FILE_NAMES) {
67103
+ const filePath = Path.posix.join("android", "app", buildFileName);
67104
+ const absoluteFilePath = Path.join(rootDirectory, filePath);
67105
+ if (!isFile(absoluteFilePath)) continue;
67106
+ let contents;
67107
+ try {
67108
+ contents = stripGradleComments(NFS.readFileSync(absoluteFilePath, "utf-8"));
67109
+ } catch {
67110
+ return null;
67111
+ }
67112
+ const releaseBlocks = getReleaseBlocks(maskGradleStrings(contents.replace(/(["'])release\1/g, RELEASE_STRING_MARKER)));
67113
+ const [releaseBlock] = releaseBlocks;
67114
+ if (releaseBlock === void 0 || releaseBlocks.length !== 1) return null;
67115
+ const hasDisabledMinification = getFinalStaticBoolean(releaseBlock, MINIFY_ASSIGNMENT_PATTERN) === false;
67116
+ const hasDisabledResourceShrinking = getFinalStaticBoolean(releaseBlock, SHRINK_RESOURCES_ASSIGNMENT_PATTERN) === false;
67117
+ if (!hasDisabledMinification && !hasDisabledResourceShrinking) return null;
67118
+ return {
67119
+ filePath,
67120
+ hasDisabledMinification,
67121
+ hasDisabledResourceShrinking
67122
+ };
67123
+ }
67124
+ return null;
67125
+ };
67126
+ const checkReactNativeAndroidReleaseShrinking = (rootDirectory) => {
67127
+ const releaseShrinking = readAndroidReleaseShrinking(rootDirectory);
67128
+ if (releaseShrinking === null) return [];
67129
+ const disabledFeatures = [releaseShrinking.hasDisabledMinification ? "code minification" : null, releaseShrinking.hasDisabledResourceShrinking ? "resource shrinking" : null].filter((feature) => feature !== null);
67130
+ return [buildReactNativeDiagnostic({
67131
+ filePath: releaseShrinking.filePath,
67132
+ rule: "rn-android-release-shrinking-disabled",
67133
+ category: "Performance",
67134
+ message: `The Android release build explicitly disables ${disabledFeatures.join(" and ")}, leaving unreachable code or resources in the shipped app.`,
67135
+ help: "Enable release code minification and resource shrinking, then test the optimized build and add keep rules for libraries that require them."
67136
+ })];
67137
+ };
67138
+ const getTypescriptScriptKind = (fileName) => {
67139
+ if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
67140
+ if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
67141
+ if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
67142
+ return ts.ScriptKind.JS;
67143
+ };
67144
+ const unwrapTypescriptExpression = (expression) => {
67145
+ let currentExpression = expression;
67146
+ while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
67147
+ return currentExpression;
67148
+ };
67149
+ const BABEL_CONFIG_FILE_NAMES$1 = [
67150
+ "babel.config.js",
67151
+ "babel.config.cjs",
67152
+ "babel.config.mjs",
67153
+ "babel.config.ts",
67154
+ "babel.config.cts",
67155
+ "babel.config.json",
67156
+ ".babelrc",
67157
+ ".babelrc.js",
67158
+ ".babelrc.cjs",
67159
+ ".babelrc.mjs",
67160
+ ".babelrc.cts",
67161
+ ".babelrc.json"
67162
+ ];
67163
+ const readUnknownPluginNames = (value) => {
67164
+ if (!Array.isArray(value)) return null;
67165
+ const pluginNames = [];
67166
+ for (const entry of value) {
67167
+ if (typeof entry === "string") {
67168
+ pluginNames.push(entry);
67169
+ continue;
67170
+ }
67171
+ if (Array.isArray(entry) && typeof entry[0] === "string") {
67172
+ pluginNames.push(entry[0]);
67173
+ continue;
67174
+ }
67175
+ return null;
67176
+ }
67177
+ return pluginNames;
67178
+ };
67179
+ const getStaticPropertyName$1 = (propertyName) => {
67180
+ if (ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName)) return propertyName.text;
67181
+ return null;
67182
+ };
67183
+ const getStaticPluginName = (expression) => {
67184
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
67185
+ if (ts.isStringLiteralLike(unwrappedExpression)) return unwrappedExpression.text;
67186
+ if (!ts.isArrayLiteralExpression(unwrappedExpression)) return null;
67187
+ const pluginExpression = unwrappedExpression.elements[0];
67188
+ return pluginExpression && ts.isStringLiteralLike(pluginExpression) ? pluginExpression.text : null;
67189
+ };
67190
+ const getPluginNamesFromObject = (objectExpression) => {
67191
+ if (objectExpression.properties.some((property) => ts.isSpreadAssignment(property))) return null;
67192
+ const pluginProperties = objectExpression.properties.filter((property) => ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "plugins");
67193
+ if (pluginProperties.length !== 1) return pluginProperties.length === 0 ? [] : null;
67194
+ const pluginListExpression = unwrapTypescriptExpression(pluginProperties[0].initializer);
67195
+ if (!ts.isArrayLiteralExpression(pluginListExpression)) return null;
67196
+ const pluginNames = [];
67197
+ for (const element of pluginListExpression.elements) {
67198
+ if (ts.isSpreadElement(element)) return null;
67199
+ const pluginName = getStaticPluginName(element);
67200
+ if (pluginName === null) return null;
67201
+ pluginNames.push(pluginName);
67202
+ }
67203
+ return pluginNames;
67204
+ };
67205
+ const isModuleExports = (expression) => ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "module" && expression.name.text === "exports" || ts.isElementAccessExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "module" && expression.argumentExpression !== void 0 && ts.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === "exports";
67206
+ const getDirectExportedObject = (sourceFile) => {
67207
+ const exportedExpressions = [];
67208
+ for (const statement of sourceFile.statements) {
67209
+ if (ts.isExportAssignment(statement)) {
67210
+ exportedExpressions.push(statement.expression);
67211
+ continue;
67212
+ }
67213
+ if (ts.isExpressionStatement(statement) && ts.isBinaryExpression(statement.expression) && statement.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken && isModuleExports(statement.expression.left)) exportedExpressions.push(statement.expression.right);
67214
+ }
67215
+ if (exportedExpressions.length !== 1) return null;
67216
+ const exportedExpression = unwrapTypescriptExpression(exportedExpressions[0]);
67217
+ return ts.isObjectLiteralExpression(exportedExpression) ? exportedExpression : null;
67218
+ };
67219
+ const parseConfigPluginNames = (filePath, contents) => {
67220
+ try {
67221
+ const parsedJson = JSON.parse(contents);
67222
+ return isPlainObject(parsedJson) ? readUnknownPluginNames(parsedJson.plugins) : null;
67223
+ } catch {
67224
+ if (ts.transpileModule(contents, {
67225
+ fileName: filePath,
67226
+ reportDiagnostics: true,
67227
+ compilerOptions: { allowJs: true }
67228
+ }).diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) return null;
67229
+ const exportedObject = getDirectExportedObject(ts.createSourceFile(filePath, contents, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filePath)));
67230
+ return exportedObject === null ? null : getPluginNamesFromObject(exportedObject);
67231
+ }
67232
+ };
67233
+ const readStaticBabelPluginNames = (rootDirectory) => {
67234
+ for (const fileName of BABEL_CONFIG_FILE_NAMES$1) {
67235
+ const absoluteFilePath = Path.join(rootDirectory, fileName);
67236
+ if (!isFile(absoluteFilePath)) continue;
67237
+ try {
67238
+ return {
67239
+ filePath: fileName,
67240
+ pluginNames: parseConfigPluginNames(fileName, NFS.readFileSync(absoluteFilePath, "utf-8"))
67241
+ };
67242
+ } catch {
67243
+ return {
67244
+ filePath: fileName,
67245
+ pluginNames: null
67246
+ };
67247
+ }
67248
+ }
67249
+ const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
67250
+ if (!isPlainObject(packageJson.babel)) return null;
67251
+ return {
67252
+ filePath: "package.json",
67253
+ pluginNames: readUnknownPluginNames(packageJson.babel.plugins)
67254
+ };
67255
+ };
67256
+ const REACT_COMPILER_PLUGIN_NAMES = new Set(["babel-plugin-react-compiler", "react-compiler"]);
67257
+ const REANIMATED_PLUGIN_NAME = "react-native-reanimated/plugin";
67258
+ const WORKLETS_PLUGIN_NAME = "react-native-worklets/plugin";
67259
+ const checkReactCompilerPluginOrder = (filePath, pluginNames) => {
67260
+ if (pluginNames.findIndex((pluginName) => REACT_COMPILER_PLUGIN_NAMES.has(pluginName)) <= 0) return [];
67261
+ return [buildReactNativeDiagnostic({
67262
+ filePath,
67263
+ rule: "rn-react-compiler-plugin-first",
67264
+ category: "Performance",
67265
+ message: "The React Compiler Babel plugin is not first, so an earlier transform can change component or hook code before the compiler analyzes it.",
67266
+ help: "Move `babel-plugin-react-compiler` to the first entry in the Babel `plugins` array."
67267
+ })];
67268
+ };
67269
+ const checkReanimatedPluginOrder = (project, filePath, pluginNames) => {
67270
+ const reanimatedMajorVersion = project.reanimatedVersion === null || project.reanimatedVersion === void 0 ? null : getLowestDependencyMajor(project.reanimatedVersion);
67271
+ if (reanimatedMajorVersion === null || reanimatedMajorVersion < 4) return [];
67272
+ const diagnostics = [];
67273
+ if (pluginNames.includes(REANIMATED_PLUGIN_NAME)) diagnostics.push(buildReactNativeDiagnostic({
67274
+ filePath,
67275
+ rule: "rn-reanimated-worklets-plugin-last",
67276
+ category: "Performance",
67277
+ message: "This Reanimated 4 project still uses `react-native-reanimated/plugin`, which was renamed to the Worklets Babel plugin.",
67278
+ help: "Replace it with `react-native-worklets/plugin` and keep that plugin last in the Babel `plugins` array."
67279
+ }));
67280
+ const workletsPluginIndex = pluginNames.lastIndexOf(WORKLETS_PLUGIN_NAME);
67281
+ if (workletsPluginIndex !== -1 && workletsPluginIndex !== pluginNames.length - 1) diagnostics.push(buildReactNativeDiagnostic({
67282
+ filePath,
67283
+ rule: "rn-reanimated-worklets-plugin-last",
67284
+ category: "Performance",
67285
+ message: "`react-native-worklets/plugin` is not last in the Babel `plugins` array, so later transforms can prevent worklets from being compiled correctly.",
67286
+ help: "Move `react-native-worklets/plugin` to the final entry in the Babel `plugins` array."
67287
+ }));
67288
+ return diagnostics;
67289
+ };
67290
+ const checkReactNativeBabelPluginOrder = (rootDirectory, project) => {
67291
+ const babelPlugins = readStaticBabelPluginNames(rootDirectory);
67292
+ if (babelPlugins === null || babelPlugins.pluginNames === null) return [];
67293
+ return [...checkReactCompilerPluginOrder(babelPlugins.filePath, babelPlugins.pluginNames), ...checkReanimatedPluginOrder(project, babelPlugins.filePath, babelPlugins.pluginNames)];
67294
+ };
66979
67295
  const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
66980
67296
  const isBuilderBobLibrary = (packageJson) => {
66981
67297
  const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
@@ -67052,7 +67368,12 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
67052
67368
  const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
67053
67369
  const checkReactNativeProject = (rootDirectory, project) => {
67054
67370
  if (!isReactNativeProject(project)) return [];
67055
- return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
67371
+ return [
67372
+ ...checkReactNativeMetroBabelPreset(rootDirectory),
67373
+ ...checkReactNativeBabelPluginOrder(rootDirectory, project),
67374
+ ...checkReactNativeAndroidReleaseShrinking(rootDirectory),
67375
+ ...checkReactNativeLibraryDependencies(rootDirectory)
67376
+ ];
67056
67377
  };
67057
67378
  const RULE_KEY = "no-vulnerable-react-server-components";
67058
67379
  const REACT_RSC_ADVISORIES_BY_MINOR = {
@@ -67203,17 +67524,6 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
67203
67524
  }
67204
67525
  return diagnostics;
67205
67526
  };
67206
- const getTypescriptScriptKind = (fileName) => {
67207
- if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
67208
- if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
67209
- if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
67210
- return ts.ScriptKind.JS;
67211
- };
67212
- const unwrapTypescriptExpression = (expression) => {
67213
- let currentExpression = expression;
67214
- while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
67215
- return currentExpression;
67216
- };
67217
67527
  const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
67218
67528
  isAnimationFunction: false,
67219
67529
  isMotionComponent: false,
@@ -67769,7 +68079,11 @@ const buildCapabilities = (project) => {
67769
68079
  capabilities.add(project.framework);
67770
68080
  if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
67771
68081
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
67772
- if (project.expoVersion !== null) capabilities.add("expo");
68082
+ if (project.expoVersion !== null) {
68083
+ capabilities.add("expo");
68084
+ const expoSdkMajorVersion = getLowestDependencyMajor(project.expoVersion);
68085
+ if (expoSdkMajorVersion !== null && expoSdkMajorVersion >= 54) capabilities.add("expo:54");
68086
+ }
67773
68087
  if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
67774
68088
  if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
67775
68089
  if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
@@ -67833,6 +68147,11 @@ const buildCapabilities = (project) => {
67833
68147
  if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addVersionCapabilityLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
67834
68148
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
67835
68149
  if (project.hasReactCompiler) capabilities.add("react-compiler");
68150
+ if (project.reanimatedVersion !== void 0 && project.reanimatedVersion !== null) {
68151
+ capabilities.add("reanimated");
68152
+ const reanimatedMajorVersion = getLowestDependencyMajor(project.reanimatedVersion);
68153
+ if (reanimatedMajorVersion !== null && reanimatedMajorVersion >= 4) capabilities.add("reanimated:4");
68154
+ }
67836
68155
  if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
67837
68156
  if (project.styledComponentsVersion) {
67838
68157
  capabilities.add("styled-components");
@@ -73883,4 +74202,4 @@ const toJsonReport = (result, options) => buildJsonReport({
73883
74202
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
73884
74203
 
73885
74204
  //# sourceMappingURL=index.js.map
73886
- //# debugId=702b5b0d-3117-5622-b161-1cb17deb6636
74205
+ //# debugId=6d9ce7c4-f6fc-597c-8340-3d408ed860e3