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/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="082c13c1-ba8f-56d5-9944-c005ab0edc0e")}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]="ea274003-c9c6-5b5c-b839-b1545ccc48fa")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -65482,7 +65482,7 @@ const resolveImportedConfigFile = (fromFilePath, moduleSpecifier) => {
65482
65482
  ]).find(isFile) ?? null;
65483
65483
  };
65484
65484
  const parseConfigSourceFile = (filePath, content) => filePath.endsWith(".json") || Path.basename(filePath) === ".babelrc" ? ts.parseJsonText(filePath, content) : ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
65485
- const getStaticPropertyName$1 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
65485
+ const getStaticPropertyName$2 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
65486
65486
  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");
65487
65487
  const hasExportModifier = (node) => ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
65488
65488
  const getRequireModuleSpecifier = (expression) => {
@@ -65546,7 +65546,7 @@ const getImportBinding = (sourceFile, bindingName) => {
65546
65546
  const moduleSpecifier = getRequireModuleSpecifier(declaration.initializer);
65547
65547
  if (bindingElement && moduleSpecifier) return {
65548
65548
  moduleSpecifier,
65549
- exportName: bindingElement.propertyName ? getStaticPropertyName$1(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
65549
+ exportName: bindingElement.propertyName ? getStaticPropertyName$2(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
65550
65550
  isNamespace: false
65551
65551
  };
65552
65552
  }
@@ -65707,7 +65707,7 @@ const getReactCompilerFlagState = (expression, analysis) => {
65707
65707
  }
65708
65708
  if (!ts.isObjectLiteralExpression(unwrappedExpression)) return null;
65709
65709
  for (const property of [...unwrappedExpression.properties].reverse()) {
65710
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
65710
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
65711
65711
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.name, analysis) };
65712
65712
  if (ts.isSpreadAssignment(property)) {
65713
65713
  const spreadState = getReactCompilerFlagState(property.expression, analysis);
@@ -65733,8 +65733,8 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
65733
65733
  }
65734
65734
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
65735
65735
  for (const property of [...resolvedExpression.properties].reverse()) {
65736
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === propertyName) return property.initializer;
65737
- if (ts.isMethodDeclaration(property) && getStaticPropertyName$1(property.name) === propertyName) return property;
65736
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === propertyName) return property.initializer;
65737
+ if (ts.isMethodDeclaration(property) && getStaticPropertyName$2(property.name) === propertyName) return property;
65738
65738
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
65739
65739
  if (ts.isSpreadAssignment(property)) {
65740
65740
  const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
@@ -65765,7 +65765,7 @@ const configExpressionMayDefineProperty = (expression, propertyName, analysis) =
65765
65765
  return resolvedExpression.properties.some((property) => {
65766
65766
  if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
65767
65767
  if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
65768
- const staticPropertyName = getStaticPropertyName$1(property.name);
65768
+ const staticPropertyName = getStaticPropertyName$2(property.name);
65769
65769
  return staticPropertyName === null || staticPropertyName === propertyName;
65770
65770
  }
65771
65771
  return false;
@@ -65784,7 +65784,7 @@ const getExportedConfigNodes = (sourceFile, exportName) => {
65784
65784
  continue;
65785
65785
  }
65786
65786
  if (ts.isExpressionStatement(statement) && isCommonJsConfigExportAssignment(statement.expression, sourceFile) && exportName !== "default" && ts.isObjectLiteralExpression(statement.expression.right)) for (const property of [...statement.expression.right.properties].reverse()) {
65787
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === exportName) {
65787
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === exportName) {
65788
65788
  exportedNodes.push(property.initializer);
65789
65789
  break;
65790
65790
  }
@@ -65943,10 +65943,10 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
65943
65943
  const reactCompilerFlagState = excludedPropertyNames?.has("reactCompiler") ? null : getReactCompilerFlagState(node, analysis);
65944
65944
  if (reactCompilerFlagState?.isEnabled) return true;
65945
65945
  for (const [propertyIndex, property] of node.properties.entries()) if (ts.isPropertyAssignment(property)) {
65946
- const propertyName = getStaticPropertyName$1(property.name);
65946
+ const propertyName = getStaticPropertyName$2(property.name);
65947
65947
  if (propertyName && excludedPropertyNames?.has(propertyName)) continue;
65948
65948
  if (propertyName === "reactCompiler") continue;
65949
- 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;
65949
+ 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;
65950
65950
  const propertyAllowsCompilerTransform = propertyName === "plugins" || propertyName === "presets";
65951
65951
  if (propertyName === "extends" && ts.isStringLiteralLike(property.initializer) && property.initializer.text.startsWith(".")) {
65952
65952
  const extendedFilePath = resolveImportedConfigFile(analysis.filePath, property.initializer.text);
@@ -65956,12 +65956,12 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
65956
65956
  } else if (ts.isShorthandPropertyAssignment(property)) {
65957
65957
  if (excludedPropertyNames?.has(property.name.text)) continue;
65958
65958
  if (property.name.text === "reactCompiler") continue;
65959
- 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;
65959
+ 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;
65960
65960
  const propertyAllowsCompilerTransform = property.name.text === "plugins" || property.name.text === "presets";
65961
65961
  if (analyzeConfigNode(property.name, analysis, propertyAllowsCompilerTransform, propertyAllowsCompilerTransform)) return true;
65962
65962
  } else if (ts.isSpreadAssignment(property)) {
65963
65963
  if (reactCompilerFlagState && getReactCompilerFlagState(property.expression, analysis) !== null) continue;
65964
- 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;
65964
+ 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;
65965
65965
  if (analyzeConfigNode(property.expression, analysis, allowCompilerTransform, false, excludedPropertyNames)) return true;
65966
65966
  }
65967
65967
  return false;
@@ -70202,6 +70202,321 @@ const buildReactNativeDiagnostic = (input) => ({
70202
70202
  column: input.column ?? 0,
70203
70203
  category: input.category ?? "Correctness"
70204
70204
  });
70205
+ const ANDROID_APP_BUILD_FILE_NAMES = ["build.gradle", "build.gradle.kts"];
70206
+ const BUILD_TYPES_BLOCK_PATTERN = /\bbuildTypes\s*\{/g;
70207
+ const RELEASE_STRING_MARKER = "__RD_RELEASE__";
70208
+ const RELEASE_BLOCK_PATTERNS = [/\brelease\s*\{/g, /\b(?:getByName|named)\s*\(\s*(?:["']release["']|__RD_RELEASE__)\s*\)\s*\{/g];
70209
+ const MINIFY_ASSIGNMENT_PATTERN = /\b(?:isMinifyEnabled|minifyEnabled)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
70210
+ const SHRINK_RESOURCES_ASSIGNMENT_PATTERN = /\b(?:isShrinkResources|shrinkResources)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
70211
+ const stripGradleComments = (contents) => {
70212
+ let result = "";
70213
+ let index = 0;
70214
+ let quote = null;
70215
+ while (index < contents.length) {
70216
+ const character = contents[index];
70217
+ const nextCharacter = contents[index + 1];
70218
+ if (quote !== null) {
70219
+ result += character;
70220
+ if (character === "\\") {
70221
+ result += nextCharacter ?? "";
70222
+ index += 2;
70223
+ continue;
70224
+ }
70225
+ if (character === quote) quote = null;
70226
+ index += 1;
70227
+ continue;
70228
+ }
70229
+ if (character === "\"" || character === "'") {
70230
+ quote = character;
70231
+ result += character;
70232
+ index += 1;
70233
+ continue;
70234
+ }
70235
+ if (character === "/" && nextCharacter === "/") {
70236
+ while (index < contents.length && contents[index] !== "\n") {
70237
+ result += " ";
70238
+ index += 1;
70239
+ }
70240
+ continue;
70241
+ }
70242
+ if (character === "/" && nextCharacter === "*") {
70243
+ result += " ";
70244
+ index += 2;
70245
+ while (index < contents.length && !(contents[index] === "*" && contents[index + 1] === "/")) {
70246
+ result += contents[index] === "\n" ? "\n" : " ";
70247
+ index += 1;
70248
+ }
70249
+ if (index < contents.length) {
70250
+ result += " ";
70251
+ index += 2;
70252
+ }
70253
+ continue;
70254
+ }
70255
+ result += character;
70256
+ index += 1;
70257
+ }
70258
+ return result;
70259
+ };
70260
+ const maskGradleStrings = (contents) => {
70261
+ let result = "";
70262
+ let index = 0;
70263
+ let quote = null;
70264
+ while (index < contents.length) {
70265
+ const character = contents[index];
70266
+ if (quote === null) {
70267
+ if (character === "\"" || character === "'") {
70268
+ quote = character;
70269
+ result += " ";
70270
+ } else result += character;
70271
+ index += 1;
70272
+ continue;
70273
+ }
70274
+ if (character === "\\") {
70275
+ result += " ";
70276
+ index += 2;
70277
+ continue;
70278
+ }
70279
+ if (character === quote) quote = null;
70280
+ result += character === "\n" ? "\n" : " ";
70281
+ index += 1;
70282
+ }
70283
+ return result;
70284
+ };
70285
+ const findBlockContents = (contents, pattern) => {
70286
+ const blocks = [];
70287
+ pattern.lastIndex = 0;
70288
+ let match = pattern.exec(contents);
70289
+ while (match !== null) {
70290
+ const openingBraceIndex = pattern.lastIndex - 1;
70291
+ let depth = 1;
70292
+ let index = openingBraceIndex + 1;
70293
+ let quote = null;
70294
+ while (index < contents.length && depth > 0) {
70295
+ const character = contents[index];
70296
+ if (quote !== null) {
70297
+ if (character === "\\") {
70298
+ index += 2;
70299
+ continue;
70300
+ }
70301
+ if (character === quote) quote = null;
70302
+ } else if (character === "\"" || character === "'") quote = character;
70303
+ else if (character === "{") depth += 1;
70304
+ else if (character === "}") depth -= 1;
70305
+ index += 1;
70306
+ }
70307
+ if (depth === 0) blocks.push(contents.slice(openingBraceIndex + 1, index - 1));
70308
+ match = pattern.exec(contents);
70309
+ }
70310
+ return blocks;
70311
+ };
70312
+ const getFinalStaticBoolean = (contents, pattern) => {
70313
+ pattern.lastIndex = 0;
70314
+ let finalValue = null;
70315
+ let match = pattern.exec(contents);
70316
+ while (match !== null) {
70317
+ const staticValue = match[1]?.trim().replace(/\)$/, "").trim();
70318
+ if (staticValue === "true") finalValue = true;
70319
+ else if (staticValue === "false") finalValue = false;
70320
+ else finalValue = null;
70321
+ match = pattern.exec(contents);
70322
+ }
70323
+ return finalValue;
70324
+ };
70325
+ const getReleaseBlocks = (contents) => findBlockContents(contents, BUILD_TYPES_BLOCK_PATTERN).flatMap((buildTypesBlock) => RELEASE_BLOCK_PATTERNS.flatMap((pattern) => findBlockContents(buildTypesBlock, pattern)));
70326
+ const readAndroidReleaseShrinking = (rootDirectory) => {
70327
+ for (const buildFileName of ANDROID_APP_BUILD_FILE_NAMES) {
70328
+ const filePath = Path.posix.join("android", "app", buildFileName);
70329
+ const absoluteFilePath = Path.join(rootDirectory, filePath);
70330
+ if (!isFile(absoluteFilePath)) continue;
70331
+ let contents;
70332
+ try {
70333
+ contents = stripGradleComments(NFS.readFileSync(absoluteFilePath, "utf-8"));
70334
+ } catch {
70335
+ return null;
70336
+ }
70337
+ const releaseBlocks = getReleaseBlocks(maskGradleStrings(contents.replace(/(["'])release\1/g, RELEASE_STRING_MARKER)));
70338
+ const [releaseBlock] = releaseBlocks;
70339
+ if (releaseBlock === void 0 || releaseBlocks.length !== 1) return null;
70340
+ const hasDisabledMinification = getFinalStaticBoolean(releaseBlock, MINIFY_ASSIGNMENT_PATTERN) === false;
70341
+ const hasDisabledResourceShrinking = getFinalStaticBoolean(releaseBlock, SHRINK_RESOURCES_ASSIGNMENT_PATTERN) === false;
70342
+ if (!hasDisabledMinification && !hasDisabledResourceShrinking) return null;
70343
+ return {
70344
+ filePath,
70345
+ hasDisabledMinification,
70346
+ hasDisabledResourceShrinking
70347
+ };
70348
+ }
70349
+ return null;
70350
+ };
70351
+ const checkReactNativeAndroidReleaseShrinking = (rootDirectory) => {
70352
+ const releaseShrinking = readAndroidReleaseShrinking(rootDirectory);
70353
+ if (releaseShrinking === null) return [];
70354
+ const disabledFeatures = [releaseShrinking.hasDisabledMinification ? "code minification" : null, releaseShrinking.hasDisabledResourceShrinking ? "resource shrinking" : null].filter((feature) => feature !== null);
70355
+ return [buildReactNativeDiagnostic({
70356
+ filePath: releaseShrinking.filePath,
70357
+ rule: "rn-android-release-shrinking-disabled",
70358
+ category: "Performance",
70359
+ message: `The Android release build explicitly disables ${disabledFeatures.join(" and ")}, leaving unreachable code or resources in the shipped app.`,
70360
+ help: "Enable release code minification and resource shrinking, then test the optimized build and add keep rules for libraries that require them."
70361
+ })];
70362
+ };
70363
+ const getTypescriptScriptKind = (fileName) => {
70364
+ if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
70365
+ if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
70366
+ if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
70367
+ return ts.ScriptKind.JS;
70368
+ };
70369
+ const unwrapTypescriptExpression = (expression) => {
70370
+ let currentExpression = expression;
70371
+ while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
70372
+ return currentExpression;
70373
+ };
70374
+ const BABEL_CONFIG_FILE_NAMES$1 = [
70375
+ "babel.config.js",
70376
+ "babel.config.cjs",
70377
+ "babel.config.mjs",
70378
+ "babel.config.ts",
70379
+ "babel.config.cts",
70380
+ "babel.config.json",
70381
+ ".babelrc",
70382
+ ".babelrc.js",
70383
+ ".babelrc.cjs",
70384
+ ".babelrc.mjs",
70385
+ ".babelrc.cts",
70386
+ ".babelrc.json"
70387
+ ];
70388
+ const readUnknownPluginNames = (value) => {
70389
+ if (!Array.isArray(value)) return null;
70390
+ const pluginNames = [];
70391
+ for (const entry of value) {
70392
+ if (typeof entry === "string") {
70393
+ pluginNames.push(entry);
70394
+ continue;
70395
+ }
70396
+ if (Array.isArray(entry) && typeof entry[0] === "string") {
70397
+ pluginNames.push(entry[0]);
70398
+ continue;
70399
+ }
70400
+ return null;
70401
+ }
70402
+ return pluginNames;
70403
+ };
70404
+ const getStaticPropertyName$1 = (propertyName) => {
70405
+ if (ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName)) return propertyName.text;
70406
+ return null;
70407
+ };
70408
+ const getStaticPluginName = (expression) => {
70409
+ const unwrappedExpression = unwrapTypescriptExpression(expression);
70410
+ if (ts.isStringLiteralLike(unwrappedExpression)) return unwrappedExpression.text;
70411
+ if (!ts.isArrayLiteralExpression(unwrappedExpression)) return null;
70412
+ const pluginExpression = unwrappedExpression.elements[0];
70413
+ return pluginExpression && ts.isStringLiteralLike(pluginExpression) ? pluginExpression.text : null;
70414
+ };
70415
+ const getPluginNamesFromObject = (objectExpression) => {
70416
+ if (objectExpression.properties.some((property) => ts.isSpreadAssignment(property))) return null;
70417
+ const pluginProperties = objectExpression.properties.filter((property) => ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "plugins");
70418
+ if (pluginProperties.length !== 1) return pluginProperties.length === 0 ? [] : null;
70419
+ const pluginListExpression = unwrapTypescriptExpression(pluginProperties[0].initializer);
70420
+ if (!ts.isArrayLiteralExpression(pluginListExpression)) return null;
70421
+ const pluginNames = [];
70422
+ for (const element of pluginListExpression.elements) {
70423
+ if (ts.isSpreadElement(element)) return null;
70424
+ const pluginName = getStaticPluginName(element);
70425
+ if (pluginName === null) return null;
70426
+ pluginNames.push(pluginName);
70427
+ }
70428
+ return pluginNames;
70429
+ };
70430
+ 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";
70431
+ const getDirectExportedObject = (sourceFile) => {
70432
+ const exportedExpressions = [];
70433
+ for (const statement of sourceFile.statements) {
70434
+ if (ts.isExportAssignment(statement)) {
70435
+ exportedExpressions.push(statement.expression);
70436
+ continue;
70437
+ }
70438
+ if (ts.isExpressionStatement(statement) && ts.isBinaryExpression(statement.expression) && statement.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken && isModuleExports(statement.expression.left)) exportedExpressions.push(statement.expression.right);
70439
+ }
70440
+ if (exportedExpressions.length !== 1) return null;
70441
+ const exportedExpression = unwrapTypescriptExpression(exportedExpressions[0]);
70442
+ return ts.isObjectLiteralExpression(exportedExpression) ? exportedExpression : null;
70443
+ };
70444
+ const parseConfigPluginNames = (filePath, contents) => {
70445
+ try {
70446
+ const parsedJson = JSON.parse(contents);
70447
+ return isPlainObject(parsedJson) ? readUnknownPluginNames(parsedJson.plugins) : null;
70448
+ } catch {
70449
+ if (ts.transpileModule(contents, {
70450
+ fileName: filePath,
70451
+ reportDiagnostics: true,
70452
+ compilerOptions: { allowJs: true }
70453
+ }).diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) return null;
70454
+ const exportedObject = getDirectExportedObject(ts.createSourceFile(filePath, contents, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filePath)));
70455
+ return exportedObject === null ? null : getPluginNamesFromObject(exportedObject);
70456
+ }
70457
+ };
70458
+ const readStaticBabelPluginNames = (rootDirectory) => {
70459
+ for (const fileName of BABEL_CONFIG_FILE_NAMES$1) {
70460
+ const absoluteFilePath = Path.join(rootDirectory, fileName);
70461
+ if (!isFile(absoluteFilePath)) continue;
70462
+ try {
70463
+ return {
70464
+ filePath: fileName,
70465
+ pluginNames: parseConfigPluginNames(fileName, NFS.readFileSync(absoluteFilePath, "utf-8"))
70466
+ };
70467
+ } catch {
70468
+ return {
70469
+ filePath: fileName,
70470
+ pluginNames: null
70471
+ };
70472
+ }
70473
+ }
70474
+ const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
70475
+ if (!isPlainObject(packageJson.babel)) return null;
70476
+ return {
70477
+ filePath: "package.json",
70478
+ pluginNames: readUnknownPluginNames(packageJson.babel.plugins)
70479
+ };
70480
+ };
70481
+ const REACT_COMPILER_PLUGIN_NAMES = new Set(["babel-plugin-react-compiler", "react-compiler"]);
70482
+ const REANIMATED_PLUGIN_NAME = "react-native-reanimated/plugin";
70483
+ const WORKLETS_PLUGIN_NAME = "react-native-worklets/plugin";
70484
+ const checkReactCompilerPluginOrder = (filePath, pluginNames) => {
70485
+ if (pluginNames.findIndex((pluginName) => REACT_COMPILER_PLUGIN_NAMES.has(pluginName)) <= 0) return [];
70486
+ return [buildReactNativeDiagnostic({
70487
+ filePath,
70488
+ rule: "rn-react-compiler-plugin-first",
70489
+ category: "Performance",
70490
+ message: "The React Compiler Babel plugin is not first, so an earlier transform can change component or hook code before the compiler analyzes it.",
70491
+ help: "Move `babel-plugin-react-compiler` to the first entry in the Babel `plugins` array."
70492
+ })];
70493
+ };
70494
+ const checkReanimatedPluginOrder = (project, filePath, pluginNames) => {
70495
+ const reanimatedMajorVersion = project.reanimatedVersion === null || project.reanimatedVersion === void 0 ? null : getLowestDependencyMajor(project.reanimatedVersion);
70496
+ if (reanimatedMajorVersion === null || reanimatedMajorVersion < 4) return [];
70497
+ const diagnostics = [];
70498
+ if (pluginNames.includes(REANIMATED_PLUGIN_NAME)) diagnostics.push(buildReactNativeDiagnostic({
70499
+ filePath,
70500
+ rule: "rn-reanimated-worklets-plugin-last",
70501
+ category: "Performance",
70502
+ message: "This Reanimated 4 project still uses `react-native-reanimated/plugin`, which was renamed to the Worklets Babel plugin.",
70503
+ help: "Replace it with `react-native-worklets/plugin` and keep that plugin last in the Babel `plugins` array."
70504
+ }));
70505
+ const workletsPluginIndex = pluginNames.lastIndexOf(WORKLETS_PLUGIN_NAME);
70506
+ if (workletsPluginIndex !== -1 && workletsPluginIndex !== pluginNames.length - 1) diagnostics.push(buildReactNativeDiagnostic({
70507
+ filePath,
70508
+ rule: "rn-reanimated-worklets-plugin-last",
70509
+ category: "Performance",
70510
+ message: "`react-native-worklets/plugin` is not last in the Babel `plugins` array, so later transforms can prevent worklets from being compiled correctly.",
70511
+ help: "Move `react-native-worklets/plugin` to the final entry in the Babel `plugins` array."
70512
+ }));
70513
+ return diagnostics;
70514
+ };
70515
+ const checkReactNativeBabelPluginOrder = (rootDirectory, project) => {
70516
+ const babelPlugins = readStaticBabelPluginNames(rootDirectory);
70517
+ if (babelPlugins === null || babelPlugins.pluginNames === null) return [];
70518
+ return [...checkReactCompilerPluginOrder(babelPlugins.filePath, babelPlugins.pluginNames), ...checkReanimatedPluginOrder(project, babelPlugins.filePath, babelPlugins.pluginNames)];
70519
+ };
70205
70520
  const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
70206
70521
  const isBuilderBobLibrary = (packageJson) => {
70207
70522
  const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
@@ -70278,7 +70593,12 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
70278
70593
  const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
70279
70594
  const checkReactNativeProject = (rootDirectory, project) => {
70280
70595
  if (!isReactNativeProject(project)) return [];
70281
- return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
70596
+ return [
70597
+ ...checkReactNativeMetroBabelPreset(rootDirectory),
70598
+ ...checkReactNativeBabelPluginOrder(rootDirectory, project),
70599
+ ...checkReactNativeAndroidReleaseShrinking(rootDirectory),
70600
+ ...checkReactNativeLibraryDependencies(rootDirectory)
70601
+ ];
70282
70602
  };
70283
70603
  const RULE_KEY = "no-vulnerable-react-server-components";
70284
70604
  const REACT_RSC_ADVISORIES_BY_MINOR = {
@@ -70429,17 +70749,6 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
70429
70749
  }
70430
70750
  return diagnostics;
70431
70751
  };
70432
- const getTypescriptScriptKind = (fileName) => {
70433
- if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
70434
- if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
70435
- if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
70436
- return ts.ScriptKind.JS;
70437
- };
70438
- const unwrapTypescriptExpression = (expression) => {
70439
- let currentExpression = expression;
70440
- while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
70441
- return currentExpression;
70442
- };
70443
70752
  const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
70444
70753
  isAnimationFunction: false,
70445
70754
  isMotionComponent: false,
@@ -70995,7 +71304,11 @@ const buildCapabilities = (project) => {
70995
71304
  capabilities.add(project.framework);
70996
71305
  if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
70997
71306
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
70998
- if (project.expoVersion !== null) capabilities.add("expo");
71307
+ if (project.expoVersion !== null) {
71308
+ capabilities.add("expo");
71309
+ const expoSdkMajorVersion = getLowestDependencyMajor(project.expoVersion);
71310
+ if (expoSdkMajorVersion !== null && expoSdkMajorVersion >= 54) capabilities.add("expo:54");
71311
+ }
70999
71312
  if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
71000
71313
  if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
71001
71314
  if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
@@ -71059,6 +71372,11 @@ const buildCapabilities = (project) => {
71059
71372
  if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addVersionCapabilityLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
71060
71373
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
71061
71374
  if (project.hasReactCompiler) capabilities.add("react-compiler");
71375
+ if (project.reanimatedVersion !== void 0 && project.reanimatedVersion !== null) {
71376
+ capabilities.add("reanimated");
71377
+ const reanimatedMajorVersion = getLowestDependencyMajor(project.reanimatedVersion);
71378
+ if (reanimatedMajorVersion !== null && reanimatedMajorVersion >= 4) capabilities.add("reanimated:4");
71379
+ }
71062
71380
  if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
71063
71381
  if (project.styledComponentsVersion) {
71064
71382
  capabilities.add("styled-components");
@@ -77410,7 +77728,7 @@ const makeNoopConsole = () => ({
77410
77728
  });
77411
77729
  //#endregion
77412
77730
  //#region src/cli/utils/version.ts
77413
- const VERSION = "0.9.1-dev.54ba416";
77731
+ const VERSION = "0.9.1-dev.5d2f66d";
77414
77732
  //#endregion
77415
77733
  //#region src/cli/utils/json-mode.ts
77416
77734
  let context = null;
@@ -77768,13 +78086,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
77768
78086
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
77769
78087
  * standard `SENTRY_RELEASE` override.
77770
78088
  */
77771
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.1-dev.54ba416`;
78089
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.1-dev.5d2f66d`;
77772
78090
  /**
77773
78091
  * Deployment environment shown in Sentry's environment filter. Defaults to
77774
78092
  * `production` for tagged releases and `development` for dev/unbuilt versions,
77775
78093
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
77776
78094
  */
77777
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.1-dev.54ba416") ? "development" : "production");
78095
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.1-dev.5d2f66d") ? "development" : "production");
77778
78096
  /**
77779
78097
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
77780
78098
  * (set to `0` to disable tracing) and falls back to
@@ -96572,4 +96890,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
96572
96890
  export { toForwardSlashes as A, highlighter as B, buildRulePriorityMap as C, resolveBlockingLevel as D, formatLearnMoreLine as E, isCiEnvironment as F, sync as G, mergeReactDoctorConfigs as H, DIAGNOSTIC_CATEGORY_BUCKETS as I, void_ as K, Progress as L, isUnicodeSupported as M, METRIC as N, isReactDoctorWorkflowInstalled as O, TUI_REPORT_DETAIL_WIDTH_FRACTION as P, Reporter as R, hasLintHardFailure as S, formatFixRecipeLine as T, resolveScanTarget as U, mapWithConcurrency as V, runPromise as W, canAnimateOnboarding as _, resolveCliInspectOptions as a, buildCodeFrame as b, detectLaunchableAgents as c, launchCliAgent as d, isShareOptedOut as f, doctorFace as g, easeOutCubic as h, selectProjects as i, findNearestPackageDirectory as j, recordCount as k, CLI_AGENT_BINARIES as l, printFooter as m, shouldFailScanGate as n, countUniqueScannedFiles as o, inspect as p, succeed$3 as q, discoverWorkspacePackages as r, setUpGitHubActions as s, exitGracefully as t, copyToClipboard as u, computeProjectedScore as v, buildSortedRuleGroups as w, buildNoScoreMessage as x, formatElapsedTime as y, getCategoryImpact as z };
96573
96891
 
96574
96892
  //# sourceMappingURL=cli.js.map
96575
- //# debugId=082c13c1-ba8f-56d5-9944-c005ab0edc0e
96893
+ //# debugId=ea274003-c9c6-5b5c-b839-b1545ccc48fa