react-doctor 0.9.1-dev.54ba416 → 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 +347 -29
  2. package/dist/index.js +344 -26
  3. package/dist/lsp.js +344 -26
  4. package/package.json +5 -5
package/dist/lsp.js CHANGED
@@ -62338,7 +62338,7 @@ const resolveImportedConfigFile = (fromFilePath, moduleSpecifier) => {
62338
62338
  ]).find(isFile) ?? null;
62339
62339
  };
62340
62340
  const parseConfigSourceFile = (filePath, content) => filePath.endsWith(".json") || Path.basename(filePath) === ".babelrc" ? ts.parseJsonText(filePath, content) : ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
62341
- const getStaticPropertyName$1 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
62341
+ const getStaticPropertyName$2 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
62342
62342
  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");
62343
62343
  const hasExportModifier = (node) => ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
62344
62344
  const getRequireModuleSpecifier = (expression) => {
@@ -62402,7 +62402,7 @@ const getImportBinding = (sourceFile, bindingName) => {
62402
62402
  const moduleSpecifier = getRequireModuleSpecifier(declaration.initializer);
62403
62403
  if (bindingElement && moduleSpecifier) return {
62404
62404
  moduleSpecifier,
62405
- exportName: bindingElement.propertyName ? getStaticPropertyName$1(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
62405
+ exportName: bindingElement.propertyName ? getStaticPropertyName$2(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
62406
62406
  isNamespace: false
62407
62407
  };
62408
62408
  }
@@ -62563,7 +62563,7 @@ const getReactCompilerFlagState = (expression, analysis) => {
62563
62563
  }
62564
62564
  if (!ts.isObjectLiteralExpression(unwrappedExpression)) return null;
62565
62565
  for (const property of [...unwrappedExpression.properties].reverse()) {
62566
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
62566
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
62567
62567
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.name, analysis) };
62568
62568
  if (ts.isSpreadAssignment(property)) {
62569
62569
  const spreadState = getReactCompilerFlagState(property.expression, analysis);
@@ -62589,8 +62589,8 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
62589
62589
  }
62590
62590
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
62591
62591
  for (const property of [...resolvedExpression.properties].reverse()) {
62592
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === propertyName) return property.initializer;
62593
- if (ts.isMethodDeclaration(property) && getStaticPropertyName$1(property.name) === propertyName) return property;
62592
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === propertyName) return property.initializer;
62593
+ if (ts.isMethodDeclaration(property) && getStaticPropertyName$2(property.name) === propertyName) return property;
62594
62594
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
62595
62595
  if (ts.isSpreadAssignment(property)) {
62596
62596
  const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
@@ -62621,7 +62621,7 @@ const configExpressionMayDefineProperty = (expression, propertyName, analysis) =
62621
62621
  return resolvedExpression.properties.some((property) => {
62622
62622
  if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
62623
62623
  if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
62624
- const staticPropertyName = getStaticPropertyName$1(property.name);
62624
+ const staticPropertyName = getStaticPropertyName$2(property.name);
62625
62625
  return staticPropertyName === null || staticPropertyName === propertyName;
62626
62626
  }
62627
62627
  return false;
@@ -62640,7 +62640,7 @@ const getExportedConfigNodes = (sourceFile, exportName) => {
62640
62640
  continue;
62641
62641
  }
62642
62642
  if (ts.isExpressionStatement(statement) && isCommonJsConfigExportAssignment(statement.expression, sourceFile) && exportName !== "default" && ts.isObjectLiteralExpression(statement.expression.right)) for (const property of [...statement.expression.right.properties].reverse()) {
62643
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === exportName) {
62643
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === exportName) {
62644
62644
  exportedNodes.push(property.initializer);
62645
62645
  break;
62646
62646
  }
@@ -62799,10 +62799,10 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
62799
62799
  const reactCompilerFlagState = excludedPropertyNames?.has("reactCompiler") ? null : getReactCompilerFlagState(node, analysis);
62800
62800
  if (reactCompilerFlagState?.isEnabled) return true;
62801
62801
  for (const [propertyIndex, property] of node.properties.entries()) if (ts.isPropertyAssignment(property)) {
62802
- const propertyName = getStaticPropertyName$1(property.name);
62802
+ const propertyName = getStaticPropertyName$2(property.name);
62803
62803
  if (propertyName && excludedPropertyNames?.has(propertyName)) continue;
62804
62804
  if (propertyName === "reactCompiler") continue;
62805
- 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;
62805
+ 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;
62806
62806
  const propertyAllowsCompilerTransform = propertyName === "plugins" || propertyName === "presets";
62807
62807
  if (propertyName === "extends" && ts.isStringLiteralLike(property.initializer) && property.initializer.text.startsWith(".")) {
62808
62808
  const extendedFilePath = resolveImportedConfigFile(analysis.filePath, property.initializer.text);
@@ -62812,12 +62812,12 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
62812
62812
  } else if (ts.isShorthandPropertyAssignment(property)) {
62813
62813
  if (excludedPropertyNames?.has(property.name.text)) continue;
62814
62814
  if (property.name.text === "reactCompiler") continue;
62815
- 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;
62815
+ 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;
62816
62816
  const propertyAllowsCompilerTransform = property.name.text === "plugins" || property.name.text === "presets";
62817
62817
  if (analyzeConfigNode(property.name, analysis, propertyAllowsCompilerTransform, propertyAllowsCompilerTransform)) return true;
62818
62818
  } else if (ts.isSpreadAssignment(property)) {
62819
62819
  if (reactCompilerFlagState && getReactCompilerFlagState(property.expression, analysis) !== null) continue;
62820
- 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;
62820
+ 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;
62821
62821
  if (analyzeConfigNode(property.expression, analysis, allowCompilerTransform, false, excludedPropertyNames)) return true;
62822
62822
  }
62823
62823
  return false;
@@ -66956,6 +66956,321 @@ const buildReactNativeDiagnostic = (input) => ({
66956
66956
  column: input.column ?? 0,
66957
66957
  category: input.category ?? "Correctness"
66958
66958
  });
66959
+ const ANDROID_APP_BUILD_FILE_NAMES = ["build.gradle", "build.gradle.kts"];
66960
+ const BUILD_TYPES_BLOCK_PATTERN = /\bbuildTypes\s*\{/g;
66961
+ const RELEASE_STRING_MARKER = "__RD_RELEASE__";
66962
+ const RELEASE_BLOCK_PATTERNS = [/\brelease\s*\{/g, /\b(?:getByName|named)\s*\(\s*(?:["']release["']|__RD_RELEASE__)\s*\)\s*\{/g];
66963
+ const MINIFY_ASSIGNMENT_PATTERN = /\b(?:isMinifyEnabled|minifyEnabled)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
66964
+ const SHRINK_RESOURCES_ASSIGNMENT_PATTERN = /\b(?:isShrinkResources|shrinkResources)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
66965
+ const stripGradleComments = (contents) => {
66966
+ let result = "";
66967
+ let index = 0;
66968
+ let quote = null;
66969
+ while (index < contents.length) {
66970
+ const character = contents[index];
66971
+ const nextCharacter = contents[index + 1];
66972
+ if (quote !== null) {
66973
+ result += character;
66974
+ if (character === "\\") {
66975
+ result += nextCharacter ?? "";
66976
+ index += 2;
66977
+ continue;
66978
+ }
66979
+ if (character === quote) quote = null;
66980
+ index += 1;
66981
+ continue;
66982
+ }
66983
+ if (character === "\"" || character === "'") {
66984
+ quote = character;
66985
+ result += character;
66986
+ index += 1;
66987
+ continue;
66988
+ }
66989
+ if (character === "/" && nextCharacter === "/") {
66990
+ while (index < contents.length && contents[index] !== "\n") {
66991
+ result += " ";
66992
+ index += 1;
66993
+ }
66994
+ continue;
66995
+ }
66996
+ if (character === "/" && nextCharacter === "*") {
66997
+ result += " ";
66998
+ index += 2;
66999
+ while (index < contents.length && !(contents[index] === "*" && contents[index + 1] === "/")) {
67000
+ result += contents[index] === "\n" ? "\n" : " ";
67001
+ index += 1;
67002
+ }
67003
+ if (index < contents.length) {
67004
+ result += " ";
67005
+ index += 2;
67006
+ }
67007
+ continue;
67008
+ }
67009
+ result += character;
67010
+ index += 1;
67011
+ }
67012
+ return result;
67013
+ };
67014
+ const maskGradleStrings = (contents) => {
67015
+ let result = "";
67016
+ let index = 0;
67017
+ let quote = null;
67018
+ while (index < contents.length) {
67019
+ const character = contents[index];
67020
+ if (quote === null) {
67021
+ if (character === "\"" || character === "'") {
67022
+ quote = character;
67023
+ result += " ";
67024
+ } else result += character;
67025
+ index += 1;
67026
+ continue;
67027
+ }
67028
+ if (character === "\\") {
67029
+ result += " ";
67030
+ index += 2;
67031
+ continue;
67032
+ }
67033
+ if (character === quote) quote = null;
67034
+ result += character === "\n" ? "\n" : " ";
67035
+ index += 1;
67036
+ }
67037
+ return result;
67038
+ };
67039
+ const findBlockContents = (contents, pattern) => {
67040
+ const blocks = [];
67041
+ pattern.lastIndex = 0;
67042
+ let match = pattern.exec(contents);
67043
+ while (match !== null) {
67044
+ const openingBraceIndex = pattern.lastIndex - 1;
67045
+ let depth = 1;
67046
+ let index = openingBraceIndex + 1;
67047
+ let quote = null;
67048
+ while (index < contents.length && depth > 0) {
67049
+ const character = contents[index];
67050
+ if (quote !== null) {
67051
+ if (character === "\\") {
67052
+ index += 2;
67053
+ continue;
67054
+ }
67055
+ if (character === quote) quote = null;
67056
+ } else if (character === "\"" || character === "'") quote = character;
67057
+ else if (character === "{") depth += 1;
67058
+ else if (character === "}") depth -= 1;
67059
+ index += 1;
67060
+ }
67061
+ if (depth === 0) blocks.push(contents.slice(openingBraceIndex + 1, index - 1));
67062
+ match = pattern.exec(contents);
67063
+ }
67064
+ return blocks;
67065
+ };
67066
+ const getFinalStaticBoolean = (contents, pattern) => {
67067
+ pattern.lastIndex = 0;
67068
+ let finalValue = null;
67069
+ let match = pattern.exec(contents);
67070
+ while (match !== null) {
67071
+ const staticValue = match[1]?.trim().replace(/\)$/, "").trim();
67072
+ if (staticValue === "true") finalValue = true;
67073
+ else if (staticValue === "false") finalValue = false;
67074
+ else finalValue = null;
67075
+ match = pattern.exec(contents);
67076
+ }
67077
+ return finalValue;
67078
+ };
67079
+ const getReleaseBlocks = (contents) => findBlockContents(contents, BUILD_TYPES_BLOCK_PATTERN).flatMap((buildTypesBlock) => RELEASE_BLOCK_PATTERNS.flatMap((pattern) => findBlockContents(buildTypesBlock, pattern)));
67080
+ const readAndroidReleaseShrinking = (rootDirectory) => {
67081
+ for (const buildFileName of ANDROID_APP_BUILD_FILE_NAMES) {
67082
+ const filePath = Path.posix.join("android", "app", buildFileName);
67083
+ const absoluteFilePath = Path.join(rootDirectory, filePath);
67084
+ if (!isFile(absoluteFilePath)) continue;
67085
+ let contents;
67086
+ try {
67087
+ contents = stripGradleComments(NFS.readFileSync(absoluteFilePath, "utf-8"));
67088
+ } catch {
67089
+ return null;
67090
+ }
67091
+ const releaseBlocks = getReleaseBlocks(maskGradleStrings(contents.replace(/(["'])release\1/g, RELEASE_STRING_MARKER)));
67092
+ const [releaseBlock] = releaseBlocks;
67093
+ if (releaseBlock === void 0 || releaseBlocks.length !== 1) return null;
67094
+ const hasDisabledMinification = getFinalStaticBoolean(releaseBlock, MINIFY_ASSIGNMENT_PATTERN) === false;
67095
+ const hasDisabledResourceShrinking = getFinalStaticBoolean(releaseBlock, SHRINK_RESOURCES_ASSIGNMENT_PATTERN) === false;
67096
+ if (!hasDisabledMinification && !hasDisabledResourceShrinking) return null;
67097
+ return {
67098
+ filePath,
67099
+ hasDisabledMinification,
67100
+ hasDisabledResourceShrinking
67101
+ };
67102
+ }
67103
+ return null;
67104
+ };
67105
+ const checkReactNativeAndroidReleaseShrinking = (rootDirectory) => {
67106
+ const releaseShrinking = readAndroidReleaseShrinking(rootDirectory);
67107
+ if (releaseShrinking === null) return [];
67108
+ const disabledFeatures = [releaseShrinking.hasDisabledMinification ? "code minification" : null, releaseShrinking.hasDisabledResourceShrinking ? "resource shrinking" : null].filter((feature) => feature !== null);
67109
+ return [buildReactNativeDiagnostic({
67110
+ filePath: releaseShrinking.filePath,
67111
+ rule: "rn-android-release-shrinking-disabled",
67112
+ category: "Performance",
67113
+ message: `The Android release build explicitly disables ${disabledFeatures.join(" and ")}, leaving unreachable code or resources in the shipped app.`,
67114
+ help: "Enable release code minification and resource shrinking, then test the optimized build and add keep rules for libraries that require them."
67115
+ })];
67116
+ };
67117
+ const getTypescriptScriptKind = (fileName) => {
67118
+ if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
67119
+ if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
67120
+ if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
67121
+ return ts.ScriptKind.JS;
67122
+ };
67123
+ const unwrapTypescriptExpression = (expression) => {
67124
+ let currentExpression = expression;
67125
+ while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
67126
+ return currentExpression;
67127
+ };
67128
+ const BABEL_CONFIG_FILE_NAMES$1 = [
67129
+ "babel.config.js",
67130
+ "babel.config.cjs",
67131
+ "babel.config.mjs",
67132
+ "babel.config.ts",
67133
+ "babel.config.cts",
67134
+ "babel.config.json",
67135
+ ".babelrc",
67136
+ ".babelrc.js",
67137
+ ".babelrc.cjs",
67138
+ ".babelrc.mjs",
67139
+ ".babelrc.cts",
67140
+ ".babelrc.json"
67141
+ ];
67142
+ const readUnknownPluginNames = (value) => {
67143
+ if (!Array.isArray(value)) return null;
67144
+ const pluginNames = [];
67145
+ for (const entry of value) {
67146
+ if (typeof entry === "string") {
67147
+ pluginNames.push(entry);
67148
+ continue;
67149
+ }
67150
+ if (Array.isArray(entry) && typeof entry[0] === "string") {
67151
+ pluginNames.push(entry[0]);
67152
+ continue;
67153
+ }
67154
+ return null;
67155
+ }
67156
+ return pluginNames;
67157
+ };
67158
+ const getStaticPropertyName$1 = (propertyName) => {
67159
+ if (ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName)) return propertyName.text;
67160
+ return null;
67161
+ };
67162
+ const getStaticPluginName = (expression) => {
67163
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
67164
+ if (ts.isStringLiteralLike(unwrappedExpression)) return unwrappedExpression.text;
67165
+ if (!ts.isArrayLiteralExpression(unwrappedExpression)) return null;
67166
+ const pluginExpression = unwrappedExpression.elements[0];
67167
+ return pluginExpression && ts.isStringLiteralLike(pluginExpression) ? pluginExpression.text : null;
67168
+ };
67169
+ const getPluginNamesFromObject = (objectExpression) => {
67170
+ if (objectExpression.properties.some((property) => ts.isSpreadAssignment(property))) return null;
67171
+ const pluginProperties = objectExpression.properties.filter((property) => ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "plugins");
67172
+ if (pluginProperties.length !== 1) return pluginProperties.length === 0 ? [] : null;
67173
+ const pluginListExpression = unwrapTypescriptExpression(pluginProperties[0].initializer);
67174
+ if (!ts.isArrayLiteralExpression(pluginListExpression)) return null;
67175
+ const pluginNames = [];
67176
+ for (const element of pluginListExpression.elements) {
67177
+ if (ts.isSpreadElement(element)) return null;
67178
+ const pluginName = getStaticPluginName(element);
67179
+ if (pluginName === null) return null;
67180
+ pluginNames.push(pluginName);
67181
+ }
67182
+ return pluginNames;
67183
+ };
67184
+ 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";
67185
+ const getDirectExportedObject = (sourceFile) => {
67186
+ const exportedExpressions = [];
67187
+ for (const statement of sourceFile.statements) {
67188
+ if (ts.isExportAssignment(statement)) {
67189
+ exportedExpressions.push(statement.expression);
67190
+ continue;
67191
+ }
67192
+ if (ts.isExpressionStatement(statement) && ts.isBinaryExpression(statement.expression) && statement.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken && isModuleExports(statement.expression.left)) exportedExpressions.push(statement.expression.right);
67193
+ }
67194
+ if (exportedExpressions.length !== 1) return null;
67195
+ const exportedExpression = unwrapTypescriptExpression(exportedExpressions[0]);
67196
+ return ts.isObjectLiteralExpression(exportedExpression) ? exportedExpression : null;
67197
+ };
67198
+ const parseConfigPluginNames = (filePath, contents) => {
67199
+ try {
67200
+ const parsedJson = JSON.parse(contents);
67201
+ return isPlainObject(parsedJson) ? readUnknownPluginNames(parsedJson.plugins) : null;
67202
+ } catch {
67203
+ if (ts.transpileModule(contents, {
67204
+ fileName: filePath,
67205
+ reportDiagnostics: true,
67206
+ compilerOptions: { allowJs: true }
67207
+ }).diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) return null;
67208
+ const exportedObject = getDirectExportedObject(ts.createSourceFile(filePath, contents, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filePath)));
67209
+ return exportedObject === null ? null : getPluginNamesFromObject(exportedObject);
67210
+ }
67211
+ };
67212
+ const readStaticBabelPluginNames = (rootDirectory) => {
67213
+ for (const fileName of BABEL_CONFIG_FILE_NAMES$1) {
67214
+ const absoluteFilePath = Path.join(rootDirectory, fileName);
67215
+ if (!isFile(absoluteFilePath)) continue;
67216
+ try {
67217
+ return {
67218
+ filePath: fileName,
67219
+ pluginNames: parseConfigPluginNames(fileName, NFS.readFileSync(absoluteFilePath, "utf-8"))
67220
+ };
67221
+ } catch {
67222
+ return {
67223
+ filePath: fileName,
67224
+ pluginNames: null
67225
+ };
67226
+ }
67227
+ }
67228
+ const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
67229
+ if (!isPlainObject(packageJson.babel)) return null;
67230
+ return {
67231
+ filePath: "package.json",
67232
+ pluginNames: readUnknownPluginNames(packageJson.babel.plugins)
67233
+ };
67234
+ };
67235
+ const REACT_COMPILER_PLUGIN_NAMES = new Set(["babel-plugin-react-compiler", "react-compiler"]);
67236
+ const REANIMATED_PLUGIN_NAME = "react-native-reanimated/plugin";
67237
+ const WORKLETS_PLUGIN_NAME = "react-native-worklets/plugin";
67238
+ const checkReactCompilerPluginOrder = (filePath, pluginNames) => {
67239
+ if (pluginNames.findIndex((pluginName) => REACT_COMPILER_PLUGIN_NAMES.has(pluginName)) <= 0) return [];
67240
+ return [buildReactNativeDiagnostic({
67241
+ filePath,
67242
+ rule: "rn-react-compiler-plugin-first",
67243
+ category: "Performance",
67244
+ message: "The React Compiler Babel plugin is not first, so an earlier transform can change component or hook code before the compiler analyzes it.",
67245
+ help: "Move `babel-plugin-react-compiler` to the first entry in the Babel `plugins` array."
67246
+ })];
67247
+ };
67248
+ const checkReanimatedPluginOrder = (project, filePath, pluginNames) => {
67249
+ const reanimatedMajorVersion = project.reanimatedVersion === null || project.reanimatedVersion === void 0 ? null : getLowestDependencyMajor(project.reanimatedVersion);
67250
+ if (reanimatedMajorVersion === null || reanimatedMajorVersion < 4) return [];
67251
+ const diagnostics = [];
67252
+ if (pluginNames.includes(REANIMATED_PLUGIN_NAME)) diagnostics.push(buildReactNativeDiagnostic({
67253
+ filePath,
67254
+ rule: "rn-reanimated-worklets-plugin-last",
67255
+ category: "Performance",
67256
+ message: "This Reanimated 4 project still uses `react-native-reanimated/plugin`, which was renamed to the Worklets Babel plugin.",
67257
+ help: "Replace it with `react-native-worklets/plugin` and keep that plugin last in the Babel `plugins` array."
67258
+ }));
67259
+ const workletsPluginIndex = pluginNames.lastIndexOf(WORKLETS_PLUGIN_NAME);
67260
+ if (workletsPluginIndex !== -1 && workletsPluginIndex !== pluginNames.length - 1) diagnostics.push(buildReactNativeDiagnostic({
67261
+ filePath,
67262
+ rule: "rn-reanimated-worklets-plugin-last",
67263
+ category: "Performance",
67264
+ message: "`react-native-worklets/plugin` is not last in the Babel `plugins` array, so later transforms can prevent worklets from being compiled correctly.",
67265
+ help: "Move `react-native-worklets/plugin` to the final entry in the Babel `plugins` array."
67266
+ }));
67267
+ return diagnostics;
67268
+ };
67269
+ const checkReactNativeBabelPluginOrder = (rootDirectory, project) => {
67270
+ const babelPlugins = readStaticBabelPluginNames(rootDirectory);
67271
+ if (babelPlugins === null || babelPlugins.pluginNames === null) return [];
67272
+ return [...checkReactCompilerPluginOrder(babelPlugins.filePath, babelPlugins.pluginNames), ...checkReanimatedPluginOrder(project, babelPlugins.filePath, babelPlugins.pluginNames)];
67273
+ };
66959
67274
  const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
66960
67275
  const isBuilderBobLibrary = (packageJson) => {
66961
67276
  const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
@@ -67032,7 +67347,12 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
67032
67347
  const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
67033
67348
  const checkReactNativeProject = (rootDirectory, project) => {
67034
67349
  if (!isReactNativeProject(project)) return [];
67035
- return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
67350
+ return [
67351
+ ...checkReactNativeMetroBabelPreset(rootDirectory),
67352
+ ...checkReactNativeBabelPluginOrder(rootDirectory, project),
67353
+ ...checkReactNativeAndroidReleaseShrinking(rootDirectory),
67354
+ ...checkReactNativeLibraryDependencies(rootDirectory)
67355
+ ];
67036
67356
  };
67037
67357
  const RULE_KEY = "no-vulnerable-react-server-components";
67038
67358
  const REACT_RSC_ADVISORIES_BY_MINOR = {
@@ -67183,17 +67503,6 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
67183
67503
  }
67184
67504
  return diagnostics;
67185
67505
  };
67186
- const getTypescriptScriptKind = (fileName) => {
67187
- if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
67188
- if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
67189
- if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
67190
- return ts.ScriptKind.JS;
67191
- };
67192
- const unwrapTypescriptExpression = (expression) => {
67193
- let currentExpression = expression;
67194
- while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
67195
- return currentExpression;
67196
- };
67197
67506
  const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
67198
67507
  isAnimationFunction: false,
67199
67508
  isMotionComponent: false,
@@ -67749,7 +68058,11 @@ const buildCapabilities = (project) => {
67749
68058
  capabilities.add(project.framework);
67750
68059
  if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
67751
68060
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
67752
- if (project.expoVersion !== null) capabilities.add("expo");
68061
+ if (project.expoVersion !== null) {
68062
+ capabilities.add("expo");
68063
+ const expoSdkMajorVersion = getLowestDependencyMajor(project.expoVersion);
68064
+ if (expoSdkMajorVersion !== null && expoSdkMajorVersion >= 54) capabilities.add("expo:54");
68065
+ }
67753
68066
  if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
67754
68067
  if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
67755
68068
  if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
@@ -67813,6 +68126,11 @@ const buildCapabilities = (project) => {
67813
68126
  if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addVersionCapabilityLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
67814
68127
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
67815
68128
  if (project.hasReactCompiler) capabilities.add("react-compiler");
68129
+ if (project.reanimatedVersion !== void 0 && project.reanimatedVersion !== null) {
68130
+ capabilities.add("reanimated");
68131
+ const reanimatedMajorVersion = getLowestDependencyMajor(project.reanimatedVersion);
68132
+ if (reanimatedMajorVersion !== null && reanimatedMajorVersion >= 4) capabilities.add("reanimated:4");
68133
+ }
67816
68134
  if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
67817
68135
  if (project.styledComponentsVersion) {
67818
68136
  capabilities.add("styled-components");
@@ -75505,5 +75823,5 @@ const startLanguageServer = () => {
75505
75823
  };
75506
75824
  //#endregion
75507
75825
  export { startLanguageServer };
75508
- !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]="aeb8bc8c-7094-5b26-bb18-e269e2d4318a")}catch(e){}}();
75509
- //# debugId=aeb8bc8c-7094-5b26-bb18-e269e2d4318a
75826
+ !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]="3e0d8192-41dd-5226-8de2-b76eaf077e6c")}catch(e){}}();
75827
+ //# debugId=3e0d8192-41dd-5226-8de2-b76eaf077e6c
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.9.1-dev.54ba416",
3
+ "version": "0.9.1-dev.5d2f66d",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -69,8 +69,8 @@
69
69
  "vscode-languageserver-textdocument": "^1.0.12",
70
70
  "vscode-uri": "^3.1.0",
71
71
  "yaml": "^2.9.0",
72
- "oxlint-plugin-react-doctor": "0.9.1-dev.54ba416",
73
- "deslop-js": "0.9.1"
72
+ "deslop-js": "0.9.1",
73
+ "oxlint-plugin-react-doctor": "0.9.1-dev.5d2f66d"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/babel__code-frame": "^7.27.0",
@@ -80,9 +80,9 @@
80
80
  "commander": "^14.0.3",
81
81
  "ink-testing-library": "^4.0.0",
82
82
  "ora": "^9.4.0",
83
- "@react-doctor/language-server": "0.9.1",
83
+ "@react-doctor/api": "0.9.1",
84
84
  "@react-doctor/core": "0.9.1",
85
- "@react-doctor/api": "0.9.1"
85
+ "@react-doctor/language-server": "0.9.1"
86
86
  },
87
87
  "engines": {
88
88
  "node": "^20.19.0 || >=22.13.0"