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.
- package/dist/cli.js +351 -32
- package/dist/index.js +347 -28
- package/dist/lsp.js +348 -29
- package/package.json +6 -6
package/dist/lsp.js
CHANGED
|
@@ -61844,7 +61844,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
61844
61844
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
61845
61845
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
61846
61846
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
61847
|
-
const CORE_PACKAGE_VERSION = "0.9.
|
|
61847
|
+
const CORE_PACKAGE_VERSION = "0.9.1";
|
|
61848
61848
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
61849
61849
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
61850
61850
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -62235,6 +62235,7 @@ const frameworkMergeRank = (framework) => {
|
|
|
62235
62235
|
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
62236
62236
|
};
|
|
62237
62237
|
const REACT_COMPILER_LINT_PACKAGES = new Set(["eslint-plugin-react-compiler"]);
|
|
62238
|
+
const REACT_COMPILER_RUNTIME_PACKAGES = new Set(["react-compiler-runtime"]);
|
|
62238
62239
|
const NEXT_CONFIG_FILENAMES = [
|
|
62239
62240
|
"next.config.js",
|
|
62240
62241
|
"next.config.mjs",
|
|
@@ -62337,7 +62338,7 @@ const resolveImportedConfigFile = (fromFilePath, moduleSpecifier) => {
|
|
|
62337
62338
|
]).find(isFile) ?? null;
|
|
62338
62339
|
};
|
|
62339
62340
|
const parseConfigSourceFile = (filePath, content) => filePath.endsWith(".json") || Path.basename(filePath) === ".babelrc" ? ts.parseJsonText(filePath, content) : ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
62340
|
-
const getStaticPropertyName$
|
|
62341
|
+
const getStaticPropertyName$2 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
|
|
62341
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");
|
|
62342
62343
|
const hasExportModifier = (node) => ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
|
|
62343
62344
|
const getRequireModuleSpecifier = (expression) => {
|
|
@@ -62401,7 +62402,7 @@ const getImportBinding = (sourceFile, bindingName) => {
|
|
|
62401
62402
|
const moduleSpecifier = getRequireModuleSpecifier(declaration.initializer);
|
|
62402
62403
|
if (bindingElement && moduleSpecifier) return {
|
|
62403
62404
|
moduleSpecifier,
|
|
62404
|
-
exportName: bindingElement.propertyName ? getStaticPropertyName$
|
|
62405
|
+
exportName: bindingElement.propertyName ? getStaticPropertyName$2(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
|
|
62405
62406
|
isNamespace: false
|
|
62406
62407
|
};
|
|
62407
62408
|
}
|
|
@@ -62562,7 +62563,7 @@ const getReactCompilerFlagState = (expression, analysis) => {
|
|
|
62562
62563
|
}
|
|
62563
62564
|
if (!ts.isObjectLiteralExpression(unwrappedExpression)) return null;
|
|
62564
62565
|
for (const property of [...unwrappedExpression.properties].reverse()) {
|
|
62565
|
-
if (ts.isPropertyAssignment(property) && getStaticPropertyName$
|
|
62566
|
+
if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
|
|
62566
62567
|
if (ts.isShorthandPropertyAssignment(property) && property.name.text === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.name, analysis) };
|
|
62567
62568
|
if (ts.isSpreadAssignment(property)) {
|
|
62568
62569
|
const spreadState = getReactCompilerFlagState(property.expression, analysis);
|
|
@@ -62588,8 +62589,8 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
|
|
|
62588
62589
|
}
|
|
62589
62590
|
if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
|
|
62590
62591
|
for (const property of [...resolvedExpression.properties].reverse()) {
|
|
62591
|
-
if (ts.isPropertyAssignment(property) && getStaticPropertyName$
|
|
62592
|
-
if (ts.isMethodDeclaration(property) && getStaticPropertyName$
|
|
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;
|
|
62593
62594
|
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
|
|
62594
62595
|
if (ts.isSpreadAssignment(property)) {
|
|
62595
62596
|
const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
|
|
@@ -62620,7 +62621,7 @@ const configExpressionMayDefineProperty = (expression, propertyName, analysis) =
|
|
|
62620
62621
|
return resolvedExpression.properties.some((property) => {
|
|
62621
62622
|
if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
|
|
62622
62623
|
if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
|
|
62623
|
-
const staticPropertyName = getStaticPropertyName$
|
|
62624
|
+
const staticPropertyName = getStaticPropertyName$2(property.name);
|
|
62624
62625
|
return staticPropertyName === null || staticPropertyName === propertyName;
|
|
62625
62626
|
}
|
|
62626
62627
|
return false;
|
|
@@ -62639,7 +62640,7 @@ const getExportedConfigNodes = (sourceFile, exportName) => {
|
|
|
62639
62640
|
continue;
|
|
62640
62641
|
}
|
|
62641
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()) {
|
|
62642
|
-
if (ts.isPropertyAssignment(property) && getStaticPropertyName$
|
|
62643
|
+
if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === exportName) {
|
|
62643
62644
|
exportedNodes.push(property.initializer);
|
|
62644
62645
|
break;
|
|
62645
62646
|
}
|
|
@@ -62798,10 +62799,10 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
|
|
|
62798
62799
|
const reactCompilerFlagState = excludedPropertyNames?.has("reactCompiler") ? null : getReactCompilerFlagState(node, analysis);
|
|
62799
62800
|
if (reactCompilerFlagState?.isEnabled) return true;
|
|
62800
62801
|
for (const [propertyIndex, property] of node.properties.entries()) if (ts.isPropertyAssignment(property)) {
|
|
62801
|
-
const propertyName = getStaticPropertyName$
|
|
62802
|
+
const propertyName = getStaticPropertyName$2(property.name);
|
|
62802
62803
|
if (propertyName && excludedPropertyNames?.has(propertyName)) continue;
|
|
62803
62804
|
if (propertyName === "reactCompiler") continue;
|
|
62804
|
-
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$
|
|
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;
|
|
62805
62806
|
const propertyAllowsCompilerTransform = propertyName === "plugins" || propertyName === "presets";
|
|
62806
62807
|
if (propertyName === "extends" && ts.isStringLiteralLike(property.initializer) && property.initializer.text.startsWith(".")) {
|
|
62807
62808
|
const extendedFilePath = resolveImportedConfigFile(analysis.filePath, property.initializer.text);
|
|
@@ -62811,12 +62812,12 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
|
|
|
62811
62812
|
} else if (ts.isShorthandPropertyAssignment(property)) {
|
|
62812
62813
|
if (excludedPropertyNames?.has(property.name.text)) continue;
|
|
62813
62814
|
if (property.name.text === "reactCompiler") continue;
|
|
62814
|
-
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$
|
|
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;
|
|
62815
62816
|
const propertyAllowsCompilerTransform = property.name.text === "plugins" || property.name.text === "presets";
|
|
62816
62817
|
if (analyzeConfigNode(property.name, analysis, propertyAllowsCompilerTransform, propertyAllowsCompilerTransform)) return true;
|
|
62817
62818
|
} else if (ts.isSpreadAssignment(property)) {
|
|
62818
62819
|
if (reactCompilerFlagState && getReactCompilerFlagState(property.expression, analysis) !== null) continue;
|
|
62819
|
-
if (node.properties.slice(propertyIndex + 1).some((laterProperty) => (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && (getStaticPropertyName$
|
|
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;
|
|
62820
62821
|
if (analyzeConfigNode(property.expression, analysis, allowCompilerTransform, false, excludedPropertyNames)) return true;
|
|
62821
62822
|
}
|
|
62822
62823
|
return false;
|
|
@@ -62941,7 +62942,7 @@ const hasCompilerConfigurationInAncestors = (directory) => {
|
|
|
62941
62942
|
}
|
|
62942
62943
|
return false;
|
|
62943
62944
|
};
|
|
62944
|
-
const detectReactCompiler = (directory, packageJson) => hasCompilerConfiguration(directory, packageJson) || hasCompilerConfigurationInAncestors(directory);
|
|
62945
|
+
const detectReactCompiler = (directory, packageJson) => hasCompilerPackage(packageJson, REACT_COMPILER_RUNTIME_PACKAGES) || hasCompilerPackageInAncestors(directory, REACT_COMPILER_RUNTIME_PACKAGES) || hasCompilerConfiguration(directory, packageJson) || hasCompilerConfigurationInAncestors(directory);
|
|
62945
62946
|
const detectReactCompilerLintPlugin = (directory, packageJson) => hasCompilerPackage(packageJson, REACT_COMPILER_LINT_PACKAGES) || hasCompilerPackageInAncestors(directory, REACT_COMPILER_LINT_PACKAGES);
|
|
62946
62947
|
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
62947
62948
|
const filePath = Path.join(directory, filename);
|
|
@@ -66955,6 +66956,321 @@ const buildReactNativeDiagnostic = (input) => ({
|
|
|
66955
66956
|
column: input.column ?? 0,
|
|
66956
66957
|
category: input.category ?? "Correctness"
|
|
66957
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
|
+
};
|
|
66958
67274
|
const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
|
|
66959
67275
|
const isBuilderBobLibrary = (packageJson) => {
|
|
66960
67276
|
const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
|
|
@@ -67031,7 +67347,12 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
|
67031
67347
|
const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
|
|
67032
67348
|
const checkReactNativeProject = (rootDirectory, project) => {
|
|
67033
67349
|
if (!isReactNativeProject(project)) return [];
|
|
67034
|
-
return [
|
|
67350
|
+
return [
|
|
67351
|
+
...checkReactNativeMetroBabelPreset(rootDirectory),
|
|
67352
|
+
...checkReactNativeBabelPluginOrder(rootDirectory, project),
|
|
67353
|
+
...checkReactNativeAndroidReleaseShrinking(rootDirectory),
|
|
67354
|
+
...checkReactNativeLibraryDependencies(rootDirectory)
|
|
67355
|
+
];
|
|
67035
67356
|
};
|
|
67036
67357
|
const RULE_KEY = "no-vulnerable-react-server-components";
|
|
67037
67358
|
const REACT_RSC_ADVISORIES_BY_MINOR = {
|
|
@@ -67182,17 +67503,6 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
67182
67503
|
}
|
|
67183
67504
|
return diagnostics;
|
|
67184
67505
|
};
|
|
67185
|
-
const getTypescriptScriptKind = (fileName) => {
|
|
67186
|
-
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
67187
|
-
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
67188
|
-
if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
67189
|
-
return ts.ScriptKind.JS;
|
|
67190
|
-
};
|
|
67191
|
-
const unwrapTypescriptExpression = (expression) => {
|
|
67192
|
-
let currentExpression = expression;
|
|
67193
|
-
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
67194
|
-
return currentExpression;
|
|
67195
|
-
};
|
|
67196
67506
|
const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
|
|
67197
67507
|
isAnimationFunction: false,
|
|
67198
67508
|
isMotionComponent: false,
|
|
@@ -67748,7 +68058,11 @@ const buildCapabilities = (project) => {
|
|
|
67748
68058
|
capabilities.add(project.framework);
|
|
67749
68059
|
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
67750
68060
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
67751
|
-
if (project.expoVersion !== null)
|
|
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
|
+
}
|
|
67752
68066
|
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
67753
68067
|
if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
|
|
67754
68068
|
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
@@ -67812,6 +68126,11 @@ const buildCapabilities = (project) => {
|
|
|
67812
68126
|
if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addVersionCapabilityLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
|
|
67813
68127
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
67814
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
|
+
}
|
|
67815
68134
|
if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
|
|
67816
68135
|
if (project.styledComponentsVersion) {
|
|
67817
68136
|
capabilities.add("styled-components");
|
|
@@ -73474,7 +73793,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
73474
73793
|
/** Display name used in client-facing messages and progress titles. */
|
|
73475
73794
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
73476
73795
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
73477
|
-
const SERVER_VERSION = "0.9.
|
|
73796
|
+
const SERVER_VERSION = "0.9.1";
|
|
73478
73797
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
73479
73798
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
73480
73799
|
/**
|
|
@@ -75504,5 +75823,5 @@ const startLanguageServer = () => {
|
|
|
75504
75823
|
};
|
|
75505
75824
|
//#endregion
|
|
75506
75825
|
export { startLanguageServer };
|
|
75507
|
-
!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]="
|
|
75508
|
-
//# debugId=
|
|
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.
|
|
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
|
-
"deslop-js": "0.9.
|
|
73
|
-
"oxlint-plugin-react-doctor": "0.9.
|
|
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/api": "0.9.
|
|
84
|
-
"@react-doctor/core": "0.9.
|
|
85
|
-
"@react-doctor/language-server": "0.9.
|
|
83
|
+
"@react-doctor/api": "0.9.1",
|
|
84
|
+
"@react-doctor/core": "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"
|