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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="082c13c1-ba8f-56d5-9944-c005ab0edc0e")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="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 SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
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$1 = (propertyName) => ts.isIdentifier(propertyName) || ts.isStringLiteralLike(propertyName) ? propertyName.text : ts.isComputedPropertyName(propertyName) && ts.isStringLiteralLike(propertyName.expression) ? propertyName.expression.text : null;
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$1(bindingElement.propertyName) ?? bindingElement.propertyName.getText(sourceFile) : bindingElement.name.getText(sourceFile),
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$1(property.name) === "reactCompiler") return { isEnabled: !isStaticallyDisabledConfigExpression(property.initializer, analysis) };
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$1(property.name) === propertyName) return property.initializer;
65737
- if (ts.isMethodDeclaration(property) && getStaticPropertyName$1(property.name) === propertyName) return property;
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$1(property.name);
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$1(property.name) === exportName) {
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$1(property.name);
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$1(laterProperty.name) === propertyName)) continue;
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$1(laterProperty.name) === property.name.text)) continue;
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$1(laterProperty.name) === "plugins" || getStaticPropertyName$1(laterProperty.name) === "presets"))) continue;
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 [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
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) capabilities.add("expo");
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 normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
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 primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
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 ? primarySpan.column - 1 : -1;
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: primarySpan?.line ?? 0,
74338
- column: primarySpan?.column ?? 0,
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
- reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
74653
- reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
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, candidateFiles);
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.54ba416";
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.54ba416`;
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.54ba416") ? "development" : "production");
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=082c13c1-ba8f-56d5-9944-c005ab0edc0e
97182
+ //# debugId=68199c0f-9834-55ef-9753-efce9ed982ad