react-doctor 0.9.1-dev.54ba416 → 0.9.1-dev.8534d86
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/README.md +2 -0
- package/dist/cli.js +676 -69
- package/dist/index.d.ts +2 -2
- package/dist/index.js +671 -65
- package/dist/lsp.js +673 -66
- package/package.json +4 -4
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]="
|
|
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]="68199c0f-9834-55ef-9753-efce9ed982ad")}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";
|
|
@@ -64616,7 +64616,8 @@ var AmbiguousProjectError = class extends Error {
|
|
|
64616
64616
|
}
|
|
64617
64617
|
};
|
|
64618
64618
|
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
|
|
64619
|
-
const
|
|
64619
|
+
const HTML_FILE_PATTERN = /\.html$/i;
|
|
64620
|
+
const SOURCE_FILE_PATTERN = /\.(?:tsx?|jsx?|mts|mjs|[hH][tT][mM][lL])$/;
|
|
64620
64621
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
64621
64622
|
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
64622
64623
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
@@ -65482,7 +65483,7 @@ const resolveImportedConfigFile = (fromFilePath, moduleSpecifier) => {
|
|
|
65482
65483
|
]).find(isFile) ?? null;
|
|
65483
65484
|
};
|
|
65484
65485
|
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$
|
|
65486
|
+
const getStaticPropertyName$2 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
|
|
65486
65487
|
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
65488
|
const hasExportModifier = (node) => ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
|
|
65488
65489
|
const getRequireModuleSpecifier = (expression) => {
|
|
@@ -65546,7 +65547,7 @@ const getImportBinding = (sourceFile, bindingName) => {
|
|
|
65546
65547
|
const moduleSpecifier = getRequireModuleSpecifier(declaration.initializer);
|
|
65547
65548
|
if (bindingElement && moduleSpecifier) return {
|
|
65548
65549
|
moduleSpecifier,
|
|
65549
|
-
exportName: bindingElement.propertyName ? getStaticPropertyName$
|
|
65550
|
+
exportName: bindingElement.propertyName ? getStaticPropertyName$2(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
|
|
65550
65551
|
isNamespace: false
|
|
65551
65552
|
};
|
|
65552
65553
|
}
|
|
@@ -65707,7 +65708,7 @@ const getReactCompilerFlagState = (expression, analysis) => {
|
|
|
65707
65708
|
}
|
|
65708
65709
|
if (!ts.isObjectLiteralExpression(unwrappedExpression)) return null;
|
|
65709
65710
|
for (const property of [...unwrappedExpression.properties].reverse()) {
|
|
65710
|
-
if (ts.isPropertyAssignment(property) && getStaticPropertyName$
|
|
65711
|
+
if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
|
|
65711
65712
|
if (ts.isShorthandPropertyAssignment(property) && property.name.text === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.name, analysis) };
|
|
65712
65713
|
if (ts.isSpreadAssignment(property)) {
|
|
65713
65714
|
const spreadState = getReactCompilerFlagState(property.expression, analysis);
|
|
@@ -65733,8 +65734,8 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
|
|
|
65733
65734
|
}
|
|
65734
65735
|
if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
|
|
65735
65736
|
for (const property of [...resolvedExpression.properties].reverse()) {
|
|
65736
|
-
if (ts.isPropertyAssignment(property) && getStaticPropertyName$
|
|
65737
|
-
if (ts.isMethodDeclaration(property) && getStaticPropertyName$
|
|
65737
|
+
if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === propertyName) return property.initializer;
|
|
65738
|
+
if (ts.isMethodDeclaration(property) && getStaticPropertyName$2(property.name) === propertyName) return property;
|
|
65738
65739
|
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
|
|
65739
65740
|
if (ts.isSpreadAssignment(property)) {
|
|
65740
65741
|
const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
|
|
@@ -65765,7 +65766,7 @@ const configExpressionMayDefineProperty = (expression, propertyName, analysis) =
|
|
|
65765
65766
|
return resolvedExpression.properties.some((property) => {
|
|
65766
65767
|
if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
|
|
65767
65768
|
if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
|
|
65768
|
-
const staticPropertyName = getStaticPropertyName$
|
|
65769
|
+
const staticPropertyName = getStaticPropertyName$2(property.name);
|
|
65769
65770
|
return staticPropertyName === null || staticPropertyName === propertyName;
|
|
65770
65771
|
}
|
|
65771
65772
|
return false;
|
|
@@ -65784,7 +65785,7 @@ const getExportedConfigNodes = (sourceFile, exportName) => {
|
|
|
65784
65785
|
continue;
|
|
65785
65786
|
}
|
|
65786
65787
|
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$
|
|
65788
|
+
if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === exportName) {
|
|
65788
65789
|
exportedNodes.push(property.initializer);
|
|
65789
65790
|
break;
|
|
65790
65791
|
}
|
|
@@ -65943,10 +65944,10 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
|
|
|
65943
65944
|
const reactCompilerFlagState = excludedPropertyNames?.has("reactCompiler") ? null : getReactCompilerFlagState(node, analysis);
|
|
65944
65945
|
if (reactCompilerFlagState?.isEnabled) return true;
|
|
65945
65946
|
for (const [propertyIndex, property] of node.properties.entries()) if (ts.isPropertyAssignment(property)) {
|
|
65946
|
-
const propertyName = getStaticPropertyName$
|
|
65947
|
+
const propertyName = getStaticPropertyName$2(property.name);
|
|
65947
65948
|
if (propertyName && excludedPropertyNames?.has(propertyName)) continue;
|
|
65948
65949
|
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$
|
|
65950
|
+
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
65951
|
const propertyAllowsCompilerTransform = propertyName === "plugins" || propertyName === "presets";
|
|
65951
65952
|
if (propertyName === "extends" && ts.isStringLiteralLike(property.initializer) && property.initializer.text.startsWith(".")) {
|
|
65952
65953
|
const extendedFilePath = resolveImportedConfigFile(analysis.filePath, property.initializer.text);
|
|
@@ -65956,12 +65957,12 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
|
|
|
65956
65957
|
} else if (ts.isShorthandPropertyAssignment(property)) {
|
|
65957
65958
|
if (excludedPropertyNames?.has(property.name.text)) continue;
|
|
65958
65959
|
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$
|
|
65960
|
+
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
65961
|
const propertyAllowsCompilerTransform = property.name.text === "plugins" || property.name.text === "presets";
|
|
65961
65962
|
if (analyzeConfigNode(property.name, analysis, propertyAllowsCompilerTransform, propertyAllowsCompilerTransform)) return true;
|
|
65962
65963
|
} else if (ts.isSpreadAssignment(property)) {
|
|
65963
65964
|
if (reactCompilerFlagState && getReactCompilerFlagState(property.expression, analysis) !== null) continue;
|
|
65964
|
-
if (node.properties.slice(propertyIndex + 1).some((laterProperty) => (ts.isPropertyAssignment(laterProperty) || ts.isShorthandPropertyAssignment(laterProperty)) && (getStaticPropertyName$
|
|
65965
|
+
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
65966
|
if (analyzeConfigNode(property.expression, analysis, allowCompilerTransform, false, excludedPropertyNames)) return true;
|
|
65966
65967
|
}
|
|
65967
65968
|
return false;
|
|
@@ -70202,6 +70203,321 @@ const buildReactNativeDiagnostic = (input) => ({
|
|
|
70202
70203
|
column: input.column ?? 0,
|
|
70203
70204
|
category: input.category ?? "Correctness"
|
|
70204
70205
|
});
|
|
70206
|
+
const ANDROID_APP_BUILD_FILE_NAMES = ["build.gradle", "build.gradle.kts"];
|
|
70207
|
+
const BUILD_TYPES_BLOCK_PATTERN = /\bbuildTypes\s*\{/g;
|
|
70208
|
+
const RELEASE_STRING_MARKER = "__RD_RELEASE__";
|
|
70209
|
+
const RELEASE_BLOCK_PATTERNS = [/\brelease\s*\{/g, /\b(?:getByName|named)\s*\(\s*(?:["']release["']|__RD_RELEASE__)\s*\)\s*\{/g];
|
|
70210
|
+
const MINIFY_ASSIGNMENT_PATTERN = /\b(?:isMinifyEnabled|minifyEnabled)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
|
|
70211
|
+
const SHRINK_RESOURCES_ASSIGNMENT_PATTERN = /\b(?:isShrinkResources|shrinkResources)\b\s*(?:\(\s*|=\s*|\s+)([^;}\r\n]+)/g;
|
|
70212
|
+
const stripGradleComments = (contents) => {
|
|
70213
|
+
let result = "";
|
|
70214
|
+
let index = 0;
|
|
70215
|
+
let quote = null;
|
|
70216
|
+
while (index < contents.length) {
|
|
70217
|
+
const character = contents[index];
|
|
70218
|
+
const nextCharacter = contents[index + 1];
|
|
70219
|
+
if (quote !== null) {
|
|
70220
|
+
result += character;
|
|
70221
|
+
if (character === "\\") {
|
|
70222
|
+
result += nextCharacter ?? "";
|
|
70223
|
+
index += 2;
|
|
70224
|
+
continue;
|
|
70225
|
+
}
|
|
70226
|
+
if (character === quote) quote = null;
|
|
70227
|
+
index += 1;
|
|
70228
|
+
continue;
|
|
70229
|
+
}
|
|
70230
|
+
if (character === "\"" || character === "'") {
|
|
70231
|
+
quote = character;
|
|
70232
|
+
result += character;
|
|
70233
|
+
index += 1;
|
|
70234
|
+
continue;
|
|
70235
|
+
}
|
|
70236
|
+
if (character === "/" && nextCharacter === "/") {
|
|
70237
|
+
while (index < contents.length && contents[index] !== "\n") {
|
|
70238
|
+
result += " ";
|
|
70239
|
+
index += 1;
|
|
70240
|
+
}
|
|
70241
|
+
continue;
|
|
70242
|
+
}
|
|
70243
|
+
if (character === "/" && nextCharacter === "*") {
|
|
70244
|
+
result += " ";
|
|
70245
|
+
index += 2;
|
|
70246
|
+
while (index < contents.length && !(contents[index] === "*" && contents[index + 1] === "/")) {
|
|
70247
|
+
result += contents[index] === "\n" ? "\n" : " ";
|
|
70248
|
+
index += 1;
|
|
70249
|
+
}
|
|
70250
|
+
if (index < contents.length) {
|
|
70251
|
+
result += " ";
|
|
70252
|
+
index += 2;
|
|
70253
|
+
}
|
|
70254
|
+
continue;
|
|
70255
|
+
}
|
|
70256
|
+
result += character;
|
|
70257
|
+
index += 1;
|
|
70258
|
+
}
|
|
70259
|
+
return result;
|
|
70260
|
+
};
|
|
70261
|
+
const maskGradleStrings = (contents) => {
|
|
70262
|
+
let result = "";
|
|
70263
|
+
let index = 0;
|
|
70264
|
+
let quote = null;
|
|
70265
|
+
while (index < contents.length) {
|
|
70266
|
+
const character = contents[index];
|
|
70267
|
+
if (quote === null) {
|
|
70268
|
+
if (character === "\"" || character === "'") {
|
|
70269
|
+
quote = character;
|
|
70270
|
+
result += " ";
|
|
70271
|
+
} else result += character;
|
|
70272
|
+
index += 1;
|
|
70273
|
+
continue;
|
|
70274
|
+
}
|
|
70275
|
+
if (character === "\\") {
|
|
70276
|
+
result += " ";
|
|
70277
|
+
index += 2;
|
|
70278
|
+
continue;
|
|
70279
|
+
}
|
|
70280
|
+
if (character === quote) quote = null;
|
|
70281
|
+
result += character === "\n" ? "\n" : " ";
|
|
70282
|
+
index += 1;
|
|
70283
|
+
}
|
|
70284
|
+
return result;
|
|
70285
|
+
};
|
|
70286
|
+
const findBlockContents = (contents, pattern) => {
|
|
70287
|
+
const blocks = [];
|
|
70288
|
+
pattern.lastIndex = 0;
|
|
70289
|
+
let match = pattern.exec(contents);
|
|
70290
|
+
while (match !== null) {
|
|
70291
|
+
const openingBraceIndex = pattern.lastIndex - 1;
|
|
70292
|
+
let depth = 1;
|
|
70293
|
+
let index = openingBraceIndex + 1;
|
|
70294
|
+
let quote = null;
|
|
70295
|
+
while (index < contents.length && depth > 0) {
|
|
70296
|
+
const character = contents[index];
|
|
70297
|
+
if (quote !== null) {
|
|
70298
|
+
if (character === "\\") {
|
|
70299
|
+
index += 2;
|
|
70300
|
+
continue;
|
|
70301
|
+
}
|
|
70302
|
+
if (character === quote) quote = null;
|
|
70303
|
+
} else if (character === "\"" || character === "'") quote = character;
|
|
70304
|
+
else if (character === "{") depth += 1;
|
|
70305
|
+
else if (character === "}") depth -= 1;
|
|
70306
|
+
index += 1;
|
|
70307
|
+
}
|
|
70308
|
+
if (depth === 0) blocks.push(contents.slice(openingBraceIndex + 1, index - 1));
|
|
70309
|
+
match = pattern.exec(contents);
|
|
70310
|
+
}
|
|
70311
|
+
return blocks;
|
|
70312
|
+
};
|
|
70313
|
+
const getFinalStaticBoolean = (contents, pattern) => {
|
|
70314
|
+
pattern.lastIndex = 0;
|
|
70315
|
+
let finalValue = null;
|
|
70316
|
+
let match = pattern.exec(contents);
|
|
70317
|
+
while (match !== null) {
|
|
70318
|
+
const staticValue = match[1]?.trim().replace(/\)$/, "").trim();
|
|
70319
|
+
if (staticValue === "true") finalValue = true;
|
|
70320
|
+
else if (staticValue === "false") finalValue = false;
|
|
70321
|
+
else finalValue = null;
|
|
70322
|
+
match = pattern.exec(contents);
|
|
70323
|
+
}
|
|
70324
|
+
return finalValue;
|
|
70325
|
+
};
|
|
70326
|
+
const getReleaseBlocks = (contents) => findBlockContents(contents, BUILD_TYPES_BLOCK_PATTERN).flatMap((buildTypesBlock) => RELEASE_BLOCK_PATTERNS.flatMap((pattern) => findBlockContents(buildTypesBlock, pattern)));
|
|
70327
|
+
const readAndroidReleaseShrinking = (rootDirectory) => {
|
|
70328
|
+
for (const buildFileName of ANDROID_APP_BUILD_FILE_NAMES) {
|
|
70329
|
+
const filePath = Path.posix.join("android", "app", buildFileName);
|
|
70330
|
+
const absoluteFilePath = Path.join(rootDirectory, filePath);
|
|
70331
|
+
if (!isFile(absoluteFilePath)) continue;
|
|
70332
|
+
let contents;
|
|
70333
|
+
try {
|
|
70334
|
+
contents = stripGradleComments(NFS.readFileSync(absoluteFilePath, "utf-8"));
|
|
70335
|
+
} catch {
|
|
70336
|
+
return null;
|
|
70337
|
+
}
|
|
70338
|
+
const releaseBlocks = getReleaseBlocks(maskGradleStrings(contents.replace(/(["'])release\1/g, RELEASE_STRING_MARKER)));
|
|
70339
|
+
const [releaseBlock] = releaseBlocks;
|
|
70340
|
+
if (releaseBlock === void 0 || releaseBlocks.length !== 1) return null;
|
|
70341
|
+
const hasDisabledMinification = getFinalStaticBoolean(releaseBlock, MINIFY_ASSIGNMENT_PATTERN) === false;
|
|
70342
|
+
const hasDisabledResourceShrinking = getFinalStaticBoolean(releaseBlock, SHRINK_RESOURCES_ASSIGNMENT_PATTERN) === false;
|
|
70343
|
+
if (!hasDisabledMinification && !hasDisabledResourceShrinking) return null;
|
|
70344
|
+
return {
|
|
70345
|
+
filePath,
|
|
70346
|
+
hasDisabledMinification,
|
|
70347
|
+
hasDisabledResourceShrinking
|
|
70348
|
+
};
|
|
70349
|
+
}
|
|
70350
|
+
return null;
|
|
70351
|
+
};
|
|
70352
|
+
const checkReactNativeAndroidReleaseShrinking = (rootDirectory) => {
|
|
70353
|
+
const releaseShrinking = readAndroidReleaseShrinking(rootDirectory);
|
|
70354
|
+
if (releaseShrinking === null) return [];
|
|
70355
|
+
const disabledFeatures = [releaseShrinking.hasDisabledMinification ? "code minification" : null, releaseShrinking.hasDisabledResourceShrinking ? "resource shrinking" : null].filter((feature) => feature !== null);
|
|
70356
|
+
return [buildReactNativeDiagnostic({
|
|
70357
|
+
filePath: releaseShrinking.filePath,
|
|
70358
|
+
rule: "rn-android-release-shrinking-disabled",
|
|
70359
|
+
category: "Performance",
|
|
70360
|
+
message: `The Android release build explicitly disables ${disabledFeatures.join(" and ")}, leaving unreachable code or resources in the shipped app.`,
|
|
70361
|
+
help: "Enable release code minification and resource shrinking, then test the optimized build and add keep rules for libraries that require them."
|
|
70362
|
+
})];
|
|
70363
|
+
};
|
|
70364
|
+
const getTypescriptScriptKind = (fileName) => {
|
|
70365
|
+
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
70366
|
+
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
70367
|
+
if (fileName.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
70368
|
+
return ts.ScriptKind.JS;
|
|
70369
|
+
};
|
|
70370
|
+
const unwrapTypescriptExpression = (expression) => {
|
|
70371
|
+
let currentExpression = expression;
|
|
70372
|
+
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
70373
|
+
return currentExpression;
|
|
70374
|
+
};
|
|
70375
|
+
const BABEL_CONFIG_FILE_NAMES$1 = [
|
|
70376
|
+
"babel.config.js",
|
|
70377
|
+
"babel.config.cjs",
|
|
70378
|
+
"babel.config.mjs",
|
|
70379
|
+
"babel.config.ts",
|
|
70380
|
+
"babel.config.cts",
|
|
70381
|
+
"babel.config.json",
|
|
70382
|
+
".babelrc",
|
|
70383
|
+
".babelrc.js",
|
|
70384
|
+
".babelrc.cjs",
|
|
70385
|
+
".babelrc.mjs",
|
|
70386
|
+
".babelrc.cts",
|
|
70387
|
+
".babelrc.json"
|
|
70388
|
+
];
|
|
70389
|
+
const readUnknownPluginNames = (value) => {
|
|
70390
|
+
if (!Array.isArray(value)) return null;
|
|
70391
|
+
const pluginNames = [];
|
|
70392
|
+
for (const entry of value) {
|
|
70393
|
+
if (typeof entry === "string") {
|
|
70394
|
+
pluginNames.push(entry);
|
|
70395
|
+
continue;
|
|
70396
|
+
}
|
|
70397
|
+
if (Array.isArray(entry) && typeof entry[0] === "string") {
|
|
70398
|
+
pluginNames.push(entry[0]);
|
|
70399
|
+
continue;
|
|
70400
|
+
}
|
|
70401
|
+
return null;
|
|
70402
|
+
}
|
|
70403
|
+
return pluginNames;
|
|
70404
|
+
};
|
|
70405
|
+
const getStaticPropertyName$1 = (propertyName) => {
|
|
70406
|
+
if (ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName)) return propertyName.text;
|
|
70407
|
+
return null;
|
|
70408
|
+
};
|
|
70409
|
+
const getStaticPluginName = (expression) => {
|
|
70410
|
+
const unwrappedExpression = unwrapTypescriptExpression(expression);
|
|
70411
|
+
if (ts.isStringLiteralLike(unwrappedExpression)) return unwrappedExpression.text;
|
|
70412
|
+
if (!ts.isArrayLiteralExpression(unwrappedExpression)) return null;
|
|
70413
|
+
const pluginExpression = unwrappedExpression.elements[0];
|
|
70414
|
+
return pluginExpression && ts.isStringLiteralLike(pluginExpression) ? pluginExpression.text : null;
|
|
70415
|
+
};
|
|
70416
|
+
const getPluginNamesFromObject = (objectExpression) => {
|
|
70417
|
+
if (objectExpression.properties.some((property) => ts.isSpreadAssignment(property))) return null;
|
|
70418
|
+
const pluginProperties = objectExpression.properties.filter((property) => ts.isPropertyAssignment(property) && getStaticPropertyName$1(property.name) === "plugins");
|
|
70419
|
+
if (pluginProperties.length !== 1) return pluginProperties.length === 0 ? [] : null;
|
|
70420
|
+
const pluginListExpression = unwrapTypescriptExpression(pluginProperties[0].initializer);
|
|
70421
|
+
if (!ts.isArrayLiteralExpression(pluginListExpression)) return null;
|
|
70422
|
+
const pluginNames = [];
|
|
70423
|
+
for (const element of pluginListExpression.elements) {
|
|
70424
|
+
if (ts.isSpreadElement(element)) return null;
|
|
70425
|
+
const pluginName = getStaticPluginName(element);
|
|
70426
|
+
if (pluginName === null) return null;
|
|
70427
|
+
pluginNames.push(pluginName);
|
|
70428
|
+
}
|
|
70429
|
+
return pluginNames;
|
|
70430
|
+
};
|
|
70431
|
+
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";
|
|
70432
|
+
const getDirectExportedObject = (sourceFile) => {
|
|
70433
|
+
const exportedExpressions = [];
|
|
70434
|
+
for (const statement of sourceFile.statements) {
|
|
70435
|
+
if (ts.isExportAssignment(statement)) {
|
|
70436
|
+
exportedExpressions.push(statement.expression);
|
|
70437
|
+
continue;
|
|
70438
|
+
}
|
|
70439
|
+
if (ts.isExpressionStatement(statement) && ts.isBinaryExpression(statement.expression) && statement.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken && isModuleExports(statement.expression.left)) exportedExpressions.push(statement.expression.right);
|
|
70440
|
+
}
|
|
70441
|
+
if (exportedExpressions.length !== 1) return null;
|
|
70442
|
+
const exportedExpression = unwrapTypescriptExpression(exportedExpressions[0]);
|
|
70443
|
+
return ts.isObjectLiteralExpression(exportedExpression) ? exportedExpression : null;
|
|
70444
|
+
};
|
|
70445
|
+
const parseConfigPluginNames = (filePath, contents) => {
|
|
70446
|
+
try {
|
|
70447
|
+
const parsedJson = JSON.parse(contents);
|
|
70448
|
+
return isPlainObject(parsedJson) ? readUnknownPluginNames(parsedJson.plugins) : null;
|
|
70449
|
+
} catch {
|
|
70450
|
+
if (ts.transpileModule(contents, {
|
|
70451
|
+
fileName: filePath,
|
|
70452
|
+
reportDiagnostics: true,
|
|
70453
|
+
compilerOptions: { allowJs: true }
|
|
70454
|
+
}).diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) return null;
|
|
70455
|
+
const exportedObject = getDirectExportedObject(ts.createSourceFile(filePath, contents, ts.ScriptTarget.Latest, true, getTypescriptScriptKind(filePath)));
|
|
70456
|
+
return exportedObject === null ? null : getPluginNamesFromObject(exportedObject);
|
|
70457
|
+
}
|
|
70458
|
+
};
|
|
70459
|
+
const readStaticBabelPluginNames = (rootDirectory) => {
|
|
70460
|
+
for (const fileName of BABEL_CONFIG_FILE_NAMES$1) {
|
|
70461
|
+
const absoluteFilePath = Path.join(rootDirectory, fileName);
|
|
70462
|
+
if (!isFile(absoluteFilePath)) continue;
|
|
70463
|
+
try {
|
|
70464
|
+
return {
|
|
70465
|
+
filePath: fileName,
|
|
70466
|
+
pluginNames: parseConfigPluginNames(fileName, NFS.readFileSync(absoluteFilePath, "utf-8"))
|
|
70467
|
+
};
|
|
70468
|
+
} catch {
|
|
70469
|
+
return {
|
|
70470
|
+
filePath: fileName,
|
|
70471
|
+
pluginNames: null
|
|
70472
|
+
};
|
|
70473
|
+
}
|
|
70474
|
+
}
|
|
70475
|
+
const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
|
|
70476
|
+
if (!isPlainObject(packageJson.babel)) return null;
|
|
70477
|
+
return {
|
|
70478
|
+
filePath: "package.json",
|
|
70479
|
+
pluginNames: readUnknownPluginNames(packageJson.babel.plugins)
|
|
70480
|
+
};
|
|
70481
|
+
};
|
|
70482
|
+
const REACT_COMPILER_PLUGIN_NAMES = new Set(["babel-plugin-react-compiler", "react-compiler"]);
|
|
70483
|
+
const REANIMATED_PLUGIN_NAME = "react-native-reanimated/plugin";
|
|
70484
|
+
const WORKLETS_PLUGIN_NAME = "react-native-worklets/plugin";
|
|
70485
|
+
const checkReactCompilerPluginOrder = (filePath, pluginNames) => {
|
|
70486
|
+
if (pluginNames.findIndex((pluginName) => REACT_COMPILER_PLUGIN_NAMES.has(pluginName)) <= 0) return [];
|
|
70487
|
+
return [buildReactNativeDiagnostic({
|
|
70488
|
+
filePath,
|
|
70489
|
+
rule: "rn-react-compiler-plugin-first",
|
|
70490
|
+
category: "Performance",
|
|
70491
|
+
message: "The React Compiler Babel plugin is not first, so an earlier transform can change component or hook code before the compiler analyzes it.",
|
|
70492
|
+
help: "Move `babel-plugin-react-compiler` to the first entry in the Babel `plugins` array."
|
|
70493
|
+
})];
|
|
70494
|
+
};
|
|
70495
|
+
const checkReanimatedPluginOrder = (project, filePath, pluginNames) => {
|
|
70496
|
+
const reanimatedMajorVersion = project.reanimatedVersion === null || project.reanimatedVersion === void 0 ? null : getLowestDependencyMajor(project.reanimatedVersion);
|
|
70497
|
+
if (reanimatedMajorVersion === null || reanimatedMajorVersion < 4) return [];
|
|
70498
|
+
const diagnostics = [];
|
|
70499
|
+
if (pluginNames.includes(REANIMATED_PLUGIN_NAME)) diagnostics.push(buildReactNativeDiagnostic({
|
|
70500
|
+
filePath,
|
|
70501
|
+
rule: "rn-reanimated-worklets-plugin-last",
|
|
70502
|
+
category: "Performance",
|
|
70503
|
+
message: "This Reanimated 4 project still uses `react-native-reanimated/plugin`, which was renamed to the Worklets Babel plugin.",
|
|
70504
|
+
help: "Replace it with `react-native-worklets/plugin` and keep that plugin last in the Babel `plugins` array."
|
|
70505
|
+
}));
|
|
70506
|
+
const workletsPluginIndex = pluginNames.lastIndexOf(WORKLETS_PLUGIN_NAME);
|
|
70507
|
+
if (workletsPluginIndex !== -1 && workletsPluginIndex !== pluginNames.length - 1) diagnostics.push(buildReactNativeDiagnostic({
|
|
70508
|
+
filePath,
|
|
70509
|
+
rule: "rn-reanimated-worklets-plugin-last",
|
|
70510
|
+
category: "Performance",
|
|
70511
|
+
message: "`react-native-worklets/plugin` is not last in the Babel `plugins` array, so later transforms can prevent worklets from being compiled correctly.",
|
|
70512
|
+
help: "Move `react-native-worklets/plugin` to the final entry in the Babel `plugins` array."
|
|
70513
|
+
}));
|
|
70514
|
+
return diagnostics;
|
|
70515
|
+
};
|
|
70516
|
+
const checkReactNativeBabelPluginOrder = (rootDirectory, project) => {
|
|
70517
|
+
const babelPlugins = readStaticBabelPluginNames(rootDirectory);
|
|
70518
|
+
if (babelPlugins === null || babelPlugins.pluginNames === null) return [];
|
|
70519
|
+
return [...checkReactCompilerPluginOrder(babelPlugins.filePath, babelPlugins.pluginNames), ...checkReanimatedPluginOrder(project, babelPlugins.filePath, babelPlugins.pluginNames)];
|
|
70520
|
+
};
|
|
70205
70521
|
const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
|
|
70206
70522
|
const isBuilderBobLibrary = (packageJson) => {
|
|
70207
70523
|
const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
|
|
@@ -70278,7 +70594,12 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
|
70278
70594
|
const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
|
|
70279
70595
|
const checkReactNativeProject = (rootDirectory, project) => {
|
|
70280
70596
|
if (!isReactNativeProject(project)) return [];
|
|
70281
|
-
return [
|
|
70597
|
+
return [
|
|
70598
|
+
...checkReactNativeMetroBabelPreset(rootDirectory),
|
|
70599
|
+
...checkReactNativeBabelPluginOrder(rootDirectory, project),
|
|
70600
|
+
...checkReactNativeAndroidReleaseShrinking(rootDirectory),
|
|
70601
|
+
...checkReactNativeLibraryDependencies(rootDirectory)
|
|
70602
|
+
];
|
|
70282
70603
|
};
|
|
70283
70604
|
const RULE_KEY = "no-vulnerable-react-server-components";
|
|
70284
70605
|
const REACT_RSC_ADVISORIES_BY_MINOR = {
|
|
@@ -70429,17 +70750,6 @@ const checkReactServerComponentsAdvisory = (scanDirectory, project) => {
|
|
|
70429
70750
|
}
|
|
70430
70751
|
return diagnostics;
|
|
70431
70752
|
};
|
|
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
70753
|
const EMPTY_MOTION_EXPRESSION_EVIDENCE = {
|
|
70444
70754
|
isAnimationFunction: false,
|
|
70445
70755
|
isMotionComponent: false,
|
|
@@ -70995,7 +71305,11 @@ const buildCapabilities = (project) => {
|
|
|
70995
71305
|
capabilities.add(project.framework);
|
|
70996
71306
|
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
70997
71307
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
70998
|
-
if (project.expoVersion !== null)
|
|
71308
|
+
if (project.expoVersion !== null) {
|
|
71309
|
+
capabilities.add("expo");
|
|
71310
|
+
const expoSdkMajorVersion = getLowestDependencyMajor(project.expoVersion);
|
|
71311
|
+
if (expoSdkMajorVersion !== null && expoSdkMajorVersion >= 54) capabilities.add("expo:54");
|
|
71312
|
+
}
|
|
70999
71313
|
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
71000
71314
|
if (SSR_FRAMEWORKS.has(project.framework) || project.hasSsrDependency) capabilities.add("ssr");
|
|
71001
71315
|
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
@@ -71059,6 +71373,11 @@ const buildCapabilities = (project) => {
|
|
|
71059
71373
|
if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addVersionCapabilityLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
|
|
71060
71374
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
71061
71375
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
71376
|
+
if (project.reanimatedVersion !== void 0 && project.reanimatedVersion !== null) {
|
|
71377
|
+
capabilities.add("reanimated");
|
|
71378
|
+
const reanimatedMajorVersion = getLowestDependencyMajor(project.reanimatedVersion);
|
|
71379
|
+
if (reanimatedMajorVersion !== null && reanimatedMajorVersion >= 4) capabilities.add("reanimated:4");
|
|
71380
|
+
}
|
|
71062
71381
|
if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
|
|
71063
71382
|
if (project.styledComponentsVersion) {
|
|
71064
71383
|
capabilities.add("styled-components");
|
|
@@ -73517,6 +73836,12 @@ const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
|
73517
73836
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
73518
73837
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
73519
73838
|
};
|
|
73839
|
+
const columnOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
73840
|
+
const boundedOffset = Math.min(Math.max(utf8Offset, 0), sourceBuffer.length);
|
|
73841
|
+
let lineStartOffset = boundedOffset;
|
|
73842
|
+
while (lineStartOffset > 0 && sourceBuffer[lineStartOffset - 1] !== 10) lineStartOffset--;
|
|
73843
|
+
return sourceBuffer.subarray(lineStartOffset, boundedOffset).toString("utf8").length + 1;
|
|
73844
|
+
};
|
|
73520
73845
|
const REDACTED_PLACEHOLDER = "<redacted>";
|
|
73521
73846
|
const KEEP_PREFIX = `$1${REDACTED_PLACEHOLDER}`;
|
|
73522
73847
|
const KNOWN_SECRET_RULES = [
|
|
@@ -74232,7 +74557,7 @@ const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORI
|
|
|
74232
74557
|
* with empty text still carry a useful jump target, so they're kept
|
|
74233
74558
|
* with a neutral message.
|
|
74234
74559
|
*/
|
|
74235
|
-
const buildRelatedLocations = (labels, filePath) => {
|
|
74560
|
+
const buildRelatedLocations = (labels, filePath, sourceBuffer) => {
|
|
74236
74561
|
if (labels.length <= 1) return [];
|
|
74237
74562
|
const related = [];
|
|
74238
74563
|
for (let labelIndex = 1; labelIndex < labels.length; labelIndex++) {
|
|
@@ -74240,8 +74565,8 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
74240
74565
|
if (!label?.span) continue;
|
|
74241
74566
|
related.push({
|
|
74242
74567
|
filePath,
|
|
74243
|
-
line: label.span.line ?? 0,
|
|
74244
|
-
column: label.span.column ?? 0,
|
|
74568
|
+
line: sourceBuffer === null ? label.span.line ?? 0 : lineOfUtf8Offset(sourceBuffer, label.span.offset),
|
|
74569
|
+
column: sourceBuffer === null ? label.span.column ?? 0 : columnOfUtf8Offset(sourceBuffer, label.span.offset),
|
|
74245
74570
|
offset: label.span.offset,
|
|
74246
74571
|
length: label.span.length,
|
|
74247
74572
|
message: label.label ?? ""
|
|
@@ -74261,7 +74586,7 @@ const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagn
|
|
|
74261
74586
|
* to the first `{`. Raises `OxlintOutputUnparseable` when the
|
|
74262
74587
|
* stdout isn't valid JSON or doesn't carry a `diagnostics` array.
|
|
74263
74588
|
*/
|
|
74264
|
-
const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
74589
|
+
const parseOxlintOutput = (stdout, project, rootDirectory, sourcePathByLintPath) => {
|
|
74265
74590
|
if (!stdout) return [];
|
|
74266
74591
|
const jsonStart = stdout.indexOf("{");
|
|
74267
74592
|
const sanitizedStdout = jsonStart > 0 ? stdout.slice(jsonStart) : stdout;
|
|
@@ -74278,8 +74603,10 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
74278
74603
|
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
74279
74604
|
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
74280
74605
|
const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
74606
|
+
const resolveMappedSourceFilename = (filename) => sourcePathByLintPath?.get(Path.normalize(resolveAbsolutePath(filename)));
|
|
74607
|
+
const resolveSourceFilename = (filename) => resolveMappedSourceFilename(filename) ?? filename;
|
|
74281
74608
|
const readSourceBuffer = (filename) => {
|
|
74282
|
-
const absolutePath = resolveAbsolutePath(filename);
|
|
74609
|
+
const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
|
|
74283
74610
|
const cached = sourceBufferCache.get(absolutePath);
|
|
74284
74611
|
if (cached !== void 0) return cached;
|
|
74285
74612
|
let sourceBuffer;
|
|
@@ -74292,7 +74619,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
74292
74619
|
return sourceBuffer;
|
|
74293
74620
|
};
|
|
74294
74621
|
const readSourceLines = (filename) => {
|
|
74295
|
-
const absolutePath = resolveAbsolutePath(filename);
|
|
74622
|
+
const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
|
|
74296
74623
|
const cached = sourceLinesCache.get(absolutePath);
|
|
74297
74624
|
if (cached !== void 0) return cached;
|
|
74298
74625
|
const sourceBuffer = readSourceBuffer(filename);
|
|
@@ -74301,27 +74628,30 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
74301
74628
|
return sourceLines;
|
|
74302
74629
|
};
|
|
74303
74630
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
74304
|
-
const absolutePath = resolveAbsolutePath(filename);
|
|
74631
|
+
const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
|
|
74305
74632
|
const cached = minifiedFileCache.get(absolutePath);
|
|
74306
74633
|
if (cached !== void 0) return cached;
|
|
74307
74634
|
const minified = isMinifiedSource(absolutePath);
|
|
74308
74635
|
minifiedFileCache.set(absolutePath, minified);
|
|
74309
74636
|
return minified;
|
|
74310
74637
|
};
|
|
74311
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !(TYPESCRIPT_DECLARATION_FILE_PATTERN.test(diagnostic.filename) && OXLINT_IGNORED_DECLARATION_DIAGNOSTIC_CODES.has(diagnostic.code)) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
74638
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(resolveSourceFilename(diagnostic.filename)) && !(TYPESCRIPT_DECLARATION_FILE_PATTERN.test(diagnostic.filename) && OXLINT_IGNORED_DECLARATION_DIAGNOSTIC_CODES.has(diagnostic.code)) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
74312
74639
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
74313
74640
|
const primaryLabel = diagnostic.labels[0];
|
|
74314
74641
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
74315
|
-
const
|
|
74642
|
+
const mappedSourceFilename = resolveMappedSourceFilename(diagnostic.filename);
|
|
74643
|
+
const normalizedFilePath = (mappedSourceFilename ?? diagnostic.filename).replaceAll("\\", "/");
|
|
74316
74644
|
const primarySpan = primaryLabel?.span;
|
|
74317
74645
|
const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
|
|
74318
|
-
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
74646
|
+
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath, mappedSourceFilename === void 0 ? null : sourceBuffer);
|
|
74319
74647
|
const category = resolveDiagnosticCategory(plugin, rule);
|
|
74320
74648
|
const matchByOccurrence = resolveMatchByOccurrence(rule, category);
|
|
74321
74649
|
const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
|
|
74322
|
-
const
|
|
74650
|
+
const primaryLineNumber = primarySpan && mappedSourceFilename !== void 0 && sourceBuffer !== null ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset) : primarySpan?.line ?? 0;
|
|
74651
|
+
const primaryColumnNumber = primarySpan && mappedSourceFilename !== void 0 && sourceBuffer !== null ? columnOfUtf8Offset(sourceBuffer, primarySpan.offset) : primarySpan?.column ?? 0;
|
|
74652
|
+
const primaryLineIndex = primarySpan ? primaryLineNumber - 1 : -1;
|
|
74323
74653
|
const primaryLine = sourceLines?.[primaryLineIndex];
|
|
74324
|
-
const primaryColumnIndex = primarySpan ?
|
|
74654
|
+
const primaryColumnIndex = primarySpan ? primaryColumnNumber - 1 : -1;
|
|
74325
74655
|
const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
|
|
74326
74656
|
const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
|
|
74327
74657
|
const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
|
|
@@ -74334,8 +74664,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
74334
74664
|
message: cleaned.message,
|
|
74335
74665
|
help: cleaned.help,
|
|
74336
74666
|
url: diagnostic.url,
|
|
74337
|
-
line:
|
|
74338
|
-
column:
|
|
74667
|
+
line: primaryLineNumber,
|
|
74668
|
+
column: primaryColumnNumber,
|
|
74339
74669
|
...primarySpan ? {
|
|
74340
74670
|
offset: primarySpan.offset,
|
|
74341
74671
|
length: primarySpan.length,
|
|
@@ -74523,7 +74853,11 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
74523
74853
|
* loop with a slimmer config in that case.
|
|
74524
74854
|
*/
|
|
74525
74855
|
const spawnLintBatches = async (input) => {
|
|
74526
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
74856
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
74857
|
+
const resolveSourcePath = (filePath) => {
|
|
74858
|
+
const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
|
|
74859
|
+
return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
|
|
74860
|
+
};
|
|
74527
74861
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
74528
74862
|
let rescueDeadlineEpochMs;
|
|
74529
74863
|
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
|
|
@@ -74543,7 +74877,7 @@ const spawnLintBatches = async (input) => {
|
|
|
74543
74877
|
}
|
|
74544
74878
|
const batchArgs = [...baseArgs, ...batch];
|
|
74545
74879
|
try {
|
|
74546
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
74880
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory, sourcePathByLintPath);
|
|
74547
74881
|
} catch (error) {
|
|
74548
74882
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
74549
74883
|
if (isPastDeadline()) {
|
|
@@ -74649,10 +74983,12 @@ const spawnLintBatches = async (input) => {
|
|
|
74649
74983
|
onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
|
|
74650
74984
|
};
|
|
74651
74985
|
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
74652
|
-
|
|
74653
|
-
|
|
74986
|
+
const droppedSourceFiles = [...new Set(droppedFiles.map(resolveSourcePath))];
|
|
74987
|
+
const deadlineSkippedSourceFiles = [...new Set(outcome.deadlineSkippedFiles.map(resolveSourcePath))];
|
|
74988
|
+
reportSkippedFiles(droppedSourceFiles, (fileListPreview) => `${droppedSourceFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
74989
|
+
reportSkippedFiles(deadlineSkippedSourceFiles, (fileListPreview) => `${deadlineSkippedSourceFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
74654
74990
|
const unavailableFiles = new Set([...droppedFiles, ...outcome.deadlineSkippedFiles]);
|
|
74655
|
-
input.onAnalyzedFiles?.([...new Set(fileBatches.flat().filter((filePath) => !unavailableFiles.has(filePath)))].sort());
|
|
74991
|
+
input.onAnalyzedFiles?.([...new Set(fileBatches.flat().filter((filePath) => !unavailableFiles.has(filePath)).map(resolveSourcePath))].sort());
|
|
74656
74992
|
return dedupeDiagnostics(diagnostics);
|
|
74657
74993
|
};
|
|
74658
74994
|
let didValidate = false;
|
|
@@ -74751,6 +75087,266 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
|
74751
75087
|
}
|
|
74752
75088
|
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
74753
75089
|
};
|
|
75090
|
+
const THREE_MODULE_IMPORT_PATTERN = /\b(?:from\s*|import\s*(?:\(\s*)?|require\s*\(\s*)["']three(?:\/[^"']*)?["']/;
|
|
75091
|
+
const containsThreeModuleImport = (sourceText) => THREE_MODULE_IMPORT_PATTERN.test(sourceText);
|
|
75092
|
+
const COMMENT_OPEN = "<!--";
|
|
75093
|
+
const COMMENT_CLOSE = "-->";
|
|
75094
|
+
const FRONTMATTER_FENCE = "---";
|
|
75095
|
+
const SCRIPT_TAG_NAME = "script";
|
|
75096
|
+
const SCRIPT_OPEN = `<${SCRIPT_TAG_NAME}`;
|
|
75097
|
+
const SCRIPT_CLOSE = `</${SCRIPT_TAG_NAME}`;
|
|
75098
|
+
const TEMPLATE_TAG_NAME = "template";
|
|
75099
|
+
const TEMPLATE_CLOSE = `</${TEMPLATE_TAG_NAME}`;
|
|
75100
|
+
const JAVASCRIPT_MIME_TYPES = new Set([
|
|
75101
|
+
"application/ecmascript",
|
|
75102
|
+
"application/javascript",
|
|
75103
|
+
"application/x-ecmascript",
|
|
75104
|
+
"application/x-javascript",
|
|
75105
|
+
"text/ecmascript",
|
|
75106
|
+
"text/javascript",
|
|
75107
|
+
"text/javascript1.0",
|
|
75108
|
+
"text/javascript1.1",
|
|
75109
|
+
"text/javascript1.2",
|
|
75110
|
+
"text/javascript1.3",
|
|
75111
|
+
"text/javascript1.4",
|
|
75112
|
+
"text/javascript1.5",
|
|
75113
|
+
"text/jscript",
|
|
75114
|
+
"text/livescript",
|
|
75115
|
+
"text/x-ecmascript",
|
|
75116
|
+
"text/x-javascript"
|
|
75117
|
+
]);
|
|
75118
|
+
const RAW_TEXT_TAG_NAMES = new Set([
|
|
75119
|
+
"iframe",
|
|
75120
|
+
"noembed",
|
|
75121
|
+
"noframes",
|
|
75122
|
+
"noscript",
|
|
75123
|
+
"style",
|
|
75124
|
+
"textarea",
|
|
75125
|
+
"title",
|
|
75126
|
+
"xmp"
|
|
75127
|
+
]);
|
|
75128
|
+
const PLAINTEXT_TAG_NAME = "plaintext";
|
|
75129
|
+
const TAG_OPEN_UTF8_BYTE = "<".charCodeAt(0);
|
|
75130
|
+
const TAG_CLOSE_UTF8_BYTE = ">".charCodeAt(0);
|
|
75131
|
+
const SINGLE_QUOTE_UTF8_BYTE = "'".charCodeAt(0);
|
|
75132
|
+
const DOUBLE_QUOTE_UTF8_BYTE = "\"".charCodeAt(0);
|
|
75133
|
+
const ATTRIBUTE_ASSIGNMENT_UTF8_BYTE = "=".charCodeAt(0);
|
|
75134
|
+
const TAG_SELF_CLOSE_UTF8_BYTE = "/".charCodeAt(0);
|
|
75135
|
+
const TAB_UTF8_BYTE = " ".charCodeAt(0);
|
|
75136
|
+
const FORM_FEED_UTF8_BYTE = "\f".charCodeAt(0);
|
|
75137
|
+
const isHtmlWhitespace = (byte) => byte === TAB_UTF8_BYTE || byte === 10 || byte === FORM_FEED_UTF8_BYTE || byte === 13 || byte === 32;
|
|
75138
|
+
const matchesAsciiCaseInsensitive = (sourceBuffer, startByte, expected) => {
|
|
75139
|
+
if (startByte + expected.length > sourceBuffer.length) return false;
|
|
75140
|
+
for (let expectedIndex = 0; expectedIndex < expected.length; expectedIndex++) {
|
|
75141
|
+
const sourceByte = sourceBuffer[startByte + expectedIndex];
|
|
75142
|
+
if (sourceByte === void 0 || String.fromCharCode(sourceByte).toLowerCase() !== expected[expectedIndex]) return false;
|
|
75143
|
+
}
|
|
75144
|
+
return true;
|
|
75145
|
+
};
|
|
75146
|
+
const isTagBoundary = (byte) => byte === TAG_CLOSE_UTF8_BYTE || byte === TAG_SELF_CLOSE_UTF8_BYTE || isHtmlWhitespace(byte);
|
|
75147
|
+
const isPotentialTagStart = (byte) => {
|
|
75148
|
+
if (byte === void 0) return false;
|
|
75149
|
+
return /[A-Za-z!/?]/.test(String.fromCharCode(byte));
|
|
75150
|
+
};
|
|
75151
|
+
const findLineEnd = (sourceBuffer, lineStartByte) => {
|
|
75152
|
+
let byteIndex = lineStartByte;
|
|
75153
|
+
while (byteIndex < sourceBuffer.length && sourceBuffer[byteIndex] !== 10 && sourceBuffer[byteIndex] !== 13) byteIndex++;
|
|
75154
|
+
return byteIndex;
|
|
75155
|
+
};
|
|
75156
|
+
const findNextLineStart = (sourceBuffer, lineStartByte) => {
|
|
75157
|
+
const lineEndByte = findLineEnd(sourceBuffer, lineStartByte);
|
|
75158
|
+
if (sourceBuffer[lineEndByte] === 13 && sourceBuffer[lineEndByte + 1] === 10) return lineEndByte + 2;
|
|
75159
|
+
return Math.min(lineEndByte + 1, sourceBuffer.length);
|
|
75160
|
+
};
|
|
75161
|
+
const isFrontmatterFenceLine = (sourceBuffer, lineStartByte) => {
|
|
75162
|
+
if (!matchesAsciiCaseInsensitive(sourceBuffer, lineStartByte, FRONTMATTER_FENCE)) return false;
|
|
75163
|
+
const lineEndByte = findLineEnd(sourceBuffer, lineStartByte);
|
|
75164
|
+
for (let byteIndex = lineStartByte + 3; byteIndex < lineEndByte; byteIndex++) if (!isHtmlWhitespace(sourceBuffer[byteIndex])) return false;
|
|
75165
|
+
return true;
|
|
75166
|
+
};
|
|
75167
|
+
const findFrontmatterEnd = (sourceBuffer) => {
|
|
75168
|
+
if (!isFrontmatterFenceLine(sourceBuffer, 0)) return 0;
|
|
75169
|
+
const openingFenceEndByte = findLineEnd(sourceBuffer, 0);
|
|
75170
|
+
let lineStartByte = findNextLineStart(sourceBuffer, 0);
|
|
75171
|
+
while (lineStartByte < sourceBuffer.length) {
|
|
75172
|
+
if (isFrontmatterFenceLine(sourceBuffer, lineStartByte)) return findLineEnd(sourceBuffer, lineStartByte);
|
|
75173
|
+
lineStartByte = findNextLineStart(sourceBuffer, lineStartByte);
|
|
75174
|
+
}
|
|
75175
|
+
return openingFenceEndByte;
|
|
75176
|
+
};
|
|
75177
|
+
const maskByteRange = (maskedBuffer, startByte, endByte) => {
|
|
75178
|
+
for (let byteIndex = startByte; byteIndex < endByte; byteIndex++) {
|
|
75179
|
+
const byte = maskedBuffer[byteIndex];
|
|
75180
|
+
if (byte !== 10 && byte !== 13) maskedBuffer[byteIndex] = 32;
|
|
75181
|
+
}
|
|
75182
|
+
};
|
|
75183
|
+
const readStartTagName = (sourceBuffer, tagStartByte) => {
|
|
75184
|
+
const firstNameByte = sourceBuffer[tagStartByte + 1];
|
|
75185
|
+
if (firstNameByte === void 0 || !/[A-Za-z]/.test(String.fromCharCode(firstNameByte))) return null;
|
|
75186
|
+
let nameEndByte = tagStartByte + 2;
|
|
75187
|
+
while (nameEndByte < sourceBuffer.length && /[A-Za-z0-9:-]/.test(String.fromCharCode(sourceBuffer[nameEndByte] ?? 32))) nameEndByte++;
|
|
75188
|
+
return sourceBuffer.subarray(tagStartByte + 1, nameEndByte).toString("ascii").toLowerCase();
|
|
75189
|
+
};
|
|
75190
|
+
const findTagEnd = (sourceBuffer, startByte) => {
|
|
75191
|
+
let quoteByte = null;
|
|
75192
|
+
for (let byteIndex = startByte; byteIndex < sourceBuffer.length; byteIndex++) {
|
|
75193
|
+
const byte = sourceBuffer[byteIndex];
|
|
75194
|
+
if (quoteByte !== null) {
|
|
75195
|
+
if (byte === quoteByte) quoteByte = null;
|
|
75196
|
+
continue;
|
|
75197
|
+
}
|
|
75198
|
+
if (byte === SINGLE_QUOTE_UTF8_BYTE || byte === DOUBLE_QUOTE_UTF8_BYTE) {
|
|
75199
|
+
quoteByte = byte;
|
|
75200
|
+
continue;
|
|
75201
|
+
}
|
|
75202
|
+
if (byte === TAG_CLOSE_UTF8_BYTE) return byteIndex;
|
|
75203
|
+
}
|
|
75204
|
+
return sourceBuffer.length;
|
|
75205
|
+
};
|
|
75206
|
+
const parseScriptAttributes = (sourceBuffer, attributesStartByte, tagEndByte) => {
|
|
75207
|
+
let byteIndex = attributesStartByte;
|
|
75208
|
+
let hasSource = false;
|
|
75209
|
+
let language = null;
|
|
75210
|
+
let scriptType = null;
|
|
75211
|
+
while (byteIndex < tagEndByte) {
|
|
75212
|
+
while (byteIndex < tagEndByte && (isHtmlWhitespace(sourceBuffer[byteIndex]) || sourceBuffer[byteIndex] === TAG_SELF_CLOSE_UTF8_BYTE)) byteIndex++;
|
|
75213
|
+
const attributeNameStartByte = byteIndex;
|
|
75214
|
+
while (byteIndex < tagEndByte && !isHtmlWhitespace(sourceBuffer[byteIndex]) && sourceBuffer[byteIndex] !== ATTRIBUTE_ASSIGNMENT_UTF8_BYTE && sourceBuffer[byteIndex] !== TAG_SELF_CLOSE_UTF8_BYTE) byteIndex++;
|
|
75215
|
+
const attributeName = sourceBuffer.subarray(attributeNameStartByte, byteIndex).toString("ascii").toLowerCase();
|
|
75216
|
+
while (byteIndex < tagEndByte && isHtmlWhitespace(sourceBuffer[byteIndex])) byteIndex++;
|
|
75217
|
+
let attributeValue = "";
|
|
75218
|
+
if (sourceBuffer[byteIndex] === ATTRIBUTE_ASSIGNMENT_UTF8_BYTE) {
|
|
75219
|
+
byteIndex++;
|
|
75220
|
+
while (byteIndex < tagEndByte && isHtmlWhitespace(sourceBuffer[byteIndex])) byteIndex++;
|
|
75221
|
+
const quoteByte = sourceBuffer[byteIndex];
|
|
75222
|
+
if (quoteByte === SINGLE_QUOTE_UTF8_BYTE || quoteByte === DOUBLE_QUOTE_UTF8_BYTE) {
|
|
75223
|
+
const valueStartByte = byteIndex + 1;
|
|
75224
|
+
byteIndex = valueStartByte;
|
|
75225
|
+
while (byteIndex < tagEndByte && sourceBuffer[byteIndex] !== quoteByte) byteIndex++;
|
|
75226
|
+
attributeValue = sourceBuffer.subarray(valueStartByte, byteIndex).toString("utf8");
|
|
75227
|
+
if (byteIndex < tagEndByte) byteIndex++;
|
|
75228
|
+
} else {
|
|
75229
|
+
const valueStartByte = byteIndex;
|
|
75230
|
+
while (byteIndex < tagEndByte && !isHtmlWhitespace(sourceBuffer[byteIndex]) && sourceBuffer[byteIndex] !== TAG_CLOSE_UTF8_BYTE) byteIndex++;
|
|
75231
|
+
attributeValue = sourceBuffer.subarray(valueStartByte, byteIndex).toString("utf8");
|
|
75232
|
+
}
|
|
75233
|
+
}
|
|
75234
|
+
if (attributeName === "src") hasSource = true;
|
|
75235
|
+
else if (attributeName === "type" && scriptType === null) scriptType = attributeValue;
|
|
75236
|
+
else if (attributeName === "language" && language === null) language = attributeValue;
|
|
75237
|
+
}
|
|
75238
|
+
return {
|
|
75239
|
+
hasSource,
|
|
75240
|
+
language,
|
|
75241
|
+
type: scriptType
|
|
75242
|
+
};
|
|
75243
|
+
};
|
|
75244
|
+
const isExecutableScript = (attributes) => {
|
|
75245
|
+
if (attributes.hasSource) return false;
|
|
75246
|
+
const type = attributes.type?.trim().toLowerCase() ?? "";
|
|
75247
|
+
if (type !== "") {
|
|
75248
|
+
const mimeType = type.split(";")[0]?.trim() ?? "";
|
|
75249
|
+
return mimeType === "module" || JAVASCRIPT_MIME_TYPES.has(mimeType);
|
|
75250
|
+
}
|
|
75251
|
+
const language = attributes.language?.trim().toLowerCase() ?? "";
|
|
75252
|
+
return language === "" || JAVASCRIPT_MIME_TYPES.has(`text/${language}`);
|
|
75253
|
+
};
|
|
75254
|
+
const findElementClose = (sourceBuffer, startByte, tagName) => {
|
|
75255
|
+
const closeTagStart = `</${tagName}`;
|
|
75256
|
+
for (let byteIndex = startByte; byteIndex < sourceBuffer.length; byteIndex++) if (sourceBuffer[byteIndex] === TAG_OPEN_UTF8_BYTE && matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, closeTagStart) && isTagBoundary(sourceBuffer[byteIndex + closeTagStart.length])) return byteIndex;
|
|
75257
|
+
return sourceBuffer.length;
|
|
75258
|
+
};
|
|
75259
|
+
const prepareHtmlScriptSource = (sourceBuffer) => {
|
|
75260
|
+
const frontmatterEndByte = findFrontmatterEnd(sourceBuffer);
|
|
75261
|
+
let maskedBuffer = frontmatterEndByte > 0 ? Buffer.from(sourceBuffer) : null;
|
|
75262
|
+
if (maskedBuffer !== null) maskByteRange(maskedBuffer, 0, frontmatterEndByte);
|
|
75263
|
+
const executableScriptBodies = [];
|
|
75264
|
+
let byteIndex = frontmatterEndByte;
|
|
75265
|
+
let templateDepth = 0;
|
|
75266
|
+
while (byteIndex < sourceBuffer.length) {
|
|
75267
|
+
if (sourceBuffer[byteIndex] !== TAG_OPEN_UTF8_BYTE) {
|
|
75268
|
+
byteIndex++;
|
|
75269
|
+
continue;
|
|
75270
|
+
}
|
|
75271
|
+
if (matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, COMMENT_OPEN)) {
|
|
75272
|
+
const commentEndByte = sourceBuffer.indexOf(COMMENT_CLOSE, byteIndex + 4);
|
|
75273
|
+
byteIndex = commentEndByte === -1 ? sourceBuffer.length : commentEndByte + 3;
|
|
75274
|
+
continue;
|
|
75275
|
+
}
|
|
75276
|
+
if (matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, TEMPLATE_CLOSE) && isTagBoundary(sourceBuffer[byteIndex + TEMPLATE_CLOSE.length])) {
|
|
75277
|
+
const tagEndByte = findTagEnd(sourceBuffer, byteIndex + TEMPLATE_CLOSE.length);
|
|
75278
|
+
if (templateDepth > 0) templateDepth--;
|
|
75279
|
+
byteIndex = Math.min(tagEndByte + 1, sourceBuffer.length);
|
|
75280
|
+
continue;
|
|
75281
|
+
}
|
|
75282
|
+
if (!matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, SCRIPT_OPEN) || !isTagBoundary(sourceBuffer[byteIndex + SCRIPT_OPEN.length])) {
|
|
75283
|
+
if (!isPotentialTagStart(sourceBuffer[byteIndex + 1])) {
|
|
75284
|
+
byteIndex++;
|
|
75285
|
+
continue;
|
|
75286
|
+
}
|
|
75287
|
+
const tagEndByte = findTagEnd(sourceBuffer, byteIndex + 1);
|
|
75288
|
+
const tagName = readStartTagName(sourceBuffer, byteIndex);
|
|
75289
|
+
if (tagName === PLAINTEXT_TAG_NAME) break;
|
|
75290
|
+
if (tagName === TEMPLATE_TAG_NAME) templateDepth++;
|
|
75291
|
+
if (tagName !== null && RAW_TEXT_TAG_NAMES.has(tagName)) {
|
|
75292
|
+
const closeTagStartByte = findElementClose(sourceBuffer, tagEndByte + 1, tagName);
|
|
75293
|
+
if (closeTagStartByte === sourceBuffer.length) break;
|
|
75294
|
+
const closeTagEndByte = findTagEnd(sourceBuffer, closeTagStartByte + tagName.length + 2);
|
|
75295
|
+
byteIndex = Math.min(closeTagEndByte + 1, sourceBuffer.length);
|
|
75296
|
+
continue;
|
|
75297
|
+
}
|
|
75298
|
+
byteIndex = Math.min(tagEndByte + 1, sourceBuffer.length);
|
|
75299
|
+
continue;
|
|
75300
|
+
}
|
|
75301
|
+
const tagEndByte = findTagEnd(sourceBuffer, byteIndex + SCRIPT_OPEN.length);
|
|
75302
|
+
if (tagEndByte === sourceBuffer.length) break;
|
|
75303
|
+
const bodyStartByte = tagEndByte + 1;
|
|
75304
|
+
const bodyEndByte = findElementClose(sourceBuffer, bodyStartByte, SCRIPT_TAG_NAME);
|
|
75305
|
+
const attributes = parseScriptAttributes(sourceBuffer, byteIndex + SCRIPT_OPEN.length, tagEndByte);
|
|
75306
|
+
if (attributes.hasSource || templateDepth > 0) {
|
|
75307
|
+
maskedBuffer ??= Buffer.from(sourceBuffer);
|
|
75308
|
+
maskByteRange(maskedBuffer, bodyStartByte, bodyEndByte);
|
|
75309
|
+
} else if (isExecutableScript(attributes)) executableScriptBodies.push(sourceBuffer.subarray(bodyStartByte, bodyEndByte));
|
|
75310
|
+
if (bodyEndByte === sourceBuffer.length) break;
|
|
75311
|
+
const closeTagEndByte = findTagEnd(sourceBuffer, bodyEndByte + SCRIPT_CLOSE.length);
|
|
75312
|
+
byteIndex = Math.min(closeTagEndByte + 1, sourceBuffer.length);
|
|
75313
|
+
}
|
|
75314
|
+
return {
|
|
75315
|
+
executableScriptBodies,
|
|
75316
|
+
lintBuffer: maskedBuffer ?? sourceBuffer
|
|
75317
|
+
};
|
|
75318
|
+
};
|
|
75319
|
+
const prepareHtmlLintSources = (rootDirectory, temporaryDirectory, candidateFiles) => {
|
|
75320
|
+
const lintFiles = [];
|
|
75321
|
+
const sourcePathByLintPath = /* @__PURE__ */ new Map();
|
|
75322
|
+
const sizeByLintPath = /* @__PURE__ */ new Map();
|
|
75323
|
+
const htmlSourcesDirectory = Path.join(temporaryDirectory, "html-sources");
|
|
75324
|
+
let htmlFileIndex = 0;
|
|
75325
|
+
let hasThreeModuleImport = false;
|
|
75326
|
+
for (const candidateFile of candidateFiles) {
|
|
75327
|
+
if (!HTML_FILE_PATTERN.test(candidateFile)) {
|
|
75328
|
+
lintFiles.push(candidateFile);
|
|
75329
|
+
continue;
|
|
75330
|
+
}
|
|
75331
|
+
const absoluteSourcePath = Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile);
|
|
75332
|
+
const sourceBuffer = NFS.readFileSync(absoluteSourcePath);
|
|
75333
|
+
const { executableScriptBodies, lintBuffer } = prepareHtmlScriptSource(sourceBuffer);
|
|
75334
|
+
if (htmlFileIndex === 0) NFS.mkdirSync(htmlSourcesDirectory, { recursive: true });
|
|
75335
|
+
const lintPath = Path.join(htmlSourcesDirectory, `${htmlFileIndex}.astro`);
|
|
75336
|
+
NFS.writeFileSync(lintPath, lintBuffer);
|
|
75337
|
+
lintFiles.push(lintPath);
|
|
75338
|
+
sourcePathByLintPath.set(Path.resolve(lintPath), candidateFile);
|
|
75339
|
+
sizeByLintPath.set(lintPath, sourceBuffer.length);
|
|
75340
|
+
hasThreeModuleImport ||= executableScriptBodies.some((scriptBody) => containsThreeModuleImport(scriptBody.toString("utf8")));
|
|
75341
|
+
htmlFileIndex++;
|
|
75342
|
+
}
|
|
75343
|
+
return {
|
|
75344
|
+
lintFiles,
|
|
75345
|
+
sourcePathByLintPath,
|
|
75346
|
+
sizeByLintPath,
|
|
75347
|
+
hasThreeModuleImport
|
|
75348
|
+
};
|
|
75349
|
+
};
|
|
74754
75350
|
/**
|
|
74755
75351
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
74756
75352
|
* the runner: once for the primary scan, once for the
|
|
@@ -74924,21 +75520,6 @@ const runOxlint = async (options) => {
|
|
|
74924
75520
|
const pluginPath = resolvePluginPath();
|
|
74925
75521
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
74926
75522
|
const userPlugins = includedTags.size > 0 ? [] : resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
74927
|
-
const buildConfig = (overrides) => createOxlintConfig({
|
|
74928
|
-
pluginPath,
|
|
74929
|
-
project,
|
|
74930
|
-
customRulesOnly,
|
|
74931
|
-
extendsPaths: overrides.extendsPaths,
|
|
74932
|
-
ignoredTags,
|
|
74933
|
-
includedTags,
|
|
74934
|
-
includeTagDefaults,
|
|
74935
|
-
serverAuthFunctionNames,
|
|
74936
|
-
severityControls,
|
|
74937
|
-
userPlugins,
|
|
74938
|
-
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
74939
|
-
ruleSelection: overrides.ruleSelection,
|
|
74940
|
-
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
74941
|
-
});
|
|
74942
75523
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
74943
75524
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
74944
75525
|
const configPath = Path.join(configDirectory, "oxlintrc.json");
|
|
@@ -74974,7 +75555,30 @@ const runOxlint = async (options) => {
|
|
|
74974
75555
|
await yieldToEventLoop();
|
|
74975
75556
|
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
74976
75557
|
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
75558
|
+
const preparedHtmlSources = prepareHtmlLintSources(rootDirectory, configDirectory, candidateFiles);
|
|
75559
|
+
const lintFiles = preparedHtmlSources.lintFiles;
|
|
75560
|
+
const hasHtmlFiles = preparedHtmlSources.sourcePathByLintPath.size > 0;
|
|
75561
|
+
const lintProject = !project.hasThree && preparedHtmlSources.hasThreeModuleImport ? {
|
|
75562
|
+
...project,
|
|
75563
|
+
hasThree: true
|
|
75564
|
+
} : project;
|
|
75565
|
+
const buildConfig = (overrides) => createOxlintConfig({
|
|
75566
|
+
pluginPath,
|
|
75567
|
+
project: lintProject,
|
|
75568
|
+
customRulesOnly,
|
|
75569
|
+
extendsPaths: overrides.extendsPaths,
|
|
75570
|
+
ignoredTags,
|
|
75571
|
+
includedTags,
|
|
75572
|
+
includeTagDefaults,
|
|
75573
|
+
serverAuthFunctionNames,
|
|
75574
|
+
severityControls,
|
|
75575
|
+
userPlugins,
|
|
75576
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
75577
|
+
ruleSelection: overrides.ruleSelection,
|
|
75578
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
75579
|
+
});
|
|
74977
75580
|
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
75581
|
+
if (sizeByFile !== null) for (const [lintPath, sizeBytes] of preparedHtmlSources.sizeByLintPath) sizeByFile.set(lintPath, sizeBytes);
|
|
74978
75582
|
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
74979
75583
|
baseArgs: passBaseArgs,
|
|
74980
75584
|
files: passFiles,
|
|
@@ -75003,7 +75607,8 @@ const runOxlint = async (options) => {
|
|
|
75003
75607
|
fileBatches: passFileBatches,
|
|
75004
75608
|
rootDirectory,
|
|
75005
75609
|
nodeBinaryPath,
|
|
75006
|
-
project,
|
|
75610
|
+
project: lintProject,
|
|
75611
|
+
sourcePathByLintPath: preparedHtmlSources.sourcePathByLintPath,
|
|
75007
75612
|
onPartialFailure: reportPartialFailure,
|
|
75008
75613
|
onAnalyzedFiles: (filePaths) => {
|
|
75009
75614
|
analyzedFiles = filePaths;
|
|
@@ -75038,7 +75643,7 @@ const runOxlint = async (options) => {
|
|
|
75038
75643
|
};
|
|
75039
75644
|
}
|
|
75040
75645
|
};
|
|
75041
|
-
if (perFileLintCacheEnabled && !project.hasReactCompiler && project.hasReactCompilerLintPlugin !== true && extendsPaths.length === 0 && userPlugins.length === 0) {
|
|
75646
|
+
if (perFileLintCacheEnabled && !project.hasReactCompiler && project.hasReactCompilerLintPlugin !== true && extendsPaths.length === 0 && userPlugins.length === 0 && !hasHtmlFiles) {
|
|
75042
75647
|
const rulesetHash = computeRulesetHash({
|
|
75043
75648
|
config: buildConfig({
|
|
75044
75649
|
extendsPaths: [],
|
|
@@ -75196,14 +75801,15 @@ const runOxlint = async (options) => {
|
|
|
75196
75801
|
]);
|
|
75197
75802
|
}
|
|
75198
75803
|
const baseArgs = makeBaseArgs(configPath);
|
|
75199
|
-
const fileBatches = buildFileBatches(baseArgs,
|
|
75804
|
+
const fileBatches = buildFileBatches(baseArgs, lintFiles);
|
|
75200
75805
|
let analyzedFiles = [];
|
|
75201
75806
|
const runBatches = () => spawnLintBatches({
|
|
75202
75807
|
baseArgs,
|
|
75203
75808
|
fileBatches,
|
|
75204
75809
|
rootDirectory,
|
|
75205
75810
|
nodeBinaryPath,
|
|
75206
|
-
project,
|
|
75811
|
+
project: lintProject,
|
|
75812
|
+
sourcePathByLintPath: preparedHtmlSources.sourcePathByLintPath,
|
|
75207
75813
|
onPartialFailure,
|
|
75208
75814
|
onAnalyzedFiles: (filePaths) => {
|
|
75209
75815
|
analyzedFiles = filePaths;
|
|
@@ -77410,7 +78016,7 @@ const makeNoopConsole = () => ({
|
|
|
77410
78016
|
});
|
|
77411
78017
|
//#endregion
|
|
77412
78018
|
//#region src/cli/utils/version.ts
|
|
77413
|
-
const VERSION = "0.9.1-dev.
|
|
78019
|
+
const VERSION = "0.9.1-dev.8534d86";
|
|
77414
78020
|
//#endregion
|
|
77415
78021
|
//#region src/cli/utils/json-mode.ts
|
|
77416
78022
|
let context = null;
|
|
@@ -77768,13 +78374,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
77768
78374
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
77769
78375
|
* standard `SENTRY_RELEASE` override.
|
|
77770
78376
|
*/
|
|
77771
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.1-dev.
|
|
78377
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.1-dev.8534d86`;
|
|
77772
78378
|
/**
|
|
77773
78379
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
77774
78380
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
77775
78381
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
77776
78382
|
*/
|
|
77777
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.1-dev.
|
|
78383
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.1-dev.8534d86") ? "development" : "production");
|
|
77778
78384
|
/**
|
|
77779
78385
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
77780
78386
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -90439,6 +91045,7 @@ const buildScanAttributes = (input) => {
|
|
|
90439
91045
|
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
90440
91046
|
fileCount: input.result?.scannedFileCount ?? null,
|
|
90441
91047
|
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null,
|
|
91048
|
+
htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length ?? null,
|
|
90442
91049
|
multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length ?? null
|
|
90443
91050
|
});
|
|
90444
91051
|
};
|
|
@@ -96492,7 +97099,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
96492
97099
|
${highlighter.info(CI_URL)}
|
|
96493
97100
|
`;
|
|
96494
97101
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
96495
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
97102
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS and inline HTML script source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
96496
97103
|
program.action(inspectAction);
|
|
96497
97104
|
program.command("design [directory]").description("Run only the focused UI design diagnostics").addHelpText("after", renderDesignHelpEpilog).action((directory, _options, command) => designAction(directory ?? ".", command.optsWithGlobals()));
|
|
96498
97105
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
@@ -96572,4 +97179,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
96572
97179
|
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
97180
|
|
|
96574
97181
|
//# sourceMappingURL=cli.js.map
|
|
96575
|
-
//# debugId=
|
|
97182
|
+
//# debugId=68199c0f-9834-55ef-9753-efce9ed982ad
|