react-doctor 0.9.1-dev.5d2f66d → 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/lsp.js CHANGED
@@ -61478,7 +61478,8 @@ var AmbiguousProjectError = class extends Error {
61478
61478
  }
61479
61479
  };
61480
61480
  const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
61481
- const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
61481
+ const HTML_FILE_PATTERN = /\.html$/i;
61482
+ const SOURCE_FILE_PATTERN = /\.(?:tsx?|jsx?|mts|mjs|[hH][tT][mM][lL])$/;
61482
61483
  const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
61483
61484
  const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
61484
61485
  const MINIFIED_SNIFF_BYTES = 65536;
@@ -70573,6 +70574,12 @@ const appendReanimatedSharedValueHint = (help, rule, project) => {
70573
70574
  if (!help) return REANIMATED_SHARED_VALUE_HINT;
70574
70575
  return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
70575
70576
  };
70577
+ const columnOfUtf8Offset = (sourceBuffer, utf8Offset) => {
70578
+ const boundedOffset = Math.min(Math.max(utf8Offset, 0), sourceBuffer.length);
70579
+ let lineStartOffset = boundedOffset;
70580
+ while (lineStartOffset > 0 && sourceBuffer[lineStartOffset - 1] !== 10) lineStartOffset--;
70581
+ return sourceBuffer.subarray(lineStartOffset, boundedOffset).toString("utf8").length + 1;
70582
+ };
70576
70583
  const REDACTED_PLACEHOLDER = "<redacted>";
70577
70584
  const KEEP_PREFIX = `$1${REDACTED_PLACEHOLDER}`;
70578
70585
  const KNOWN_SECRET_RULES = [
@@ -71288,7 +71295,7 @@ const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORI
71288
71295
  * with empty text still carry a useful jump target, so they're kept
71289
71296
  * with a neutral message.
71290
71297
  */
71291
- const buildRelatedLocations = (labels, filePath) => {
71298
+ const buildRelatedLocations = (labels, filePath, sourceBuffer) => {
71292
71299
  if (labels.length <= 1) return [];
71293
71300
  const related = [];
71294
71301
  for (let labelIndex = 1; labelIndex < labels.length; labelIndex++) {
@@ -71296,8 +71303,8 @@ const buildRelatedLocations = (labels, filePath) => {
71296
71303
  if (!label?.span) continue;
71297
71304
  related.push({
71298
71305
  filePath,
71299
- line: label.span.line ?? 0,
71300
- column: label.span.column ?? 0,
71306
+ line: sourceBuffer === null ? label.span.line ?? 0 : lineOfUtf8Offset(sourceBuffer, label.span.offset),
71307
+ column: sourceBuffer === null ? label.span.column ?? 0 : columnOfUtf8Offset(sourceBuffer, label.span.offset),
71301
71308
  offset: label.span.offset,
71302
71309
  length: label.span.length,
71303
71310
  message: label.label ?? ""
@@ -71317,7 +71324,7 @@ const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnos
71317
71324
  * to the first `{`. Raises `OxlintOutputUnparseable` when the
71318
71325
  * stdout isn't valid JSON or doesn't carry a `diagnostics` array.
71319
71326
  */
71320
- const parseOxlintOutput = (stdout, project, rootDirectory) => {
71327
+ const parseOxlintOutput = (stdout, project, rootDirectory, sourcePathByLintPath) => {
71321
71328
  if (!stdout) return [];
71322
71329
  const jsonStart = stdout.indexOf("{");
71323
71330
  const sanitizedStdout = jsonStart > 0 ? stdout.slice(jsonStart) : stdout;
@@ -71334,8 +71341,10 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
71334
71341
  const sourceBufferCache = /* @__PURE__ */ new Map();
71335
71342
  const sourceLinesCache = /* @__PURE__ */ new Map();
71336
71343
  const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
71344
+ const resolveMappedSourceFilename = (filename) => sourcePathByLintPath?.get(Path.normalize(resolveAbsolutePath(filename)));
71345
+ const resolveSourceFilename = (filename) => resolveMappedSourceFilename(filename) ?? filename;
71337
71346
  const readSourceBuffer = (filename) => {
71338
- const absolutePath = resolveAbsolutePath(filename);
71347
+ const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
71339
71348
  const cached = sourceBufferCache.get(absolutePath);
71340
71349
  if (cached !== void 0) return cached;
71341
71350
  let sourceBuffer;
@@ -71348,7 +71357,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
71348
71357
  return sourceBuffer;
71349
71358
  };
71350
71359
  const readSourceLines = (filename) => {
71351
- const absolutePath = resolveAbsolutePath(filename);
71360
+ const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
71352
71361
  const cached = sourceLinesCache.get(absolutePath);
71353
71362
  if (cached !== void 0) return cached;
71354
71363
  const sourceBuffer = readSourceBuffer(filename);
@@ -71357,27 +71366,30 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
71357
71366
  return sourceLines;
71358
71367
  };
71359
71368
  const isMinifiedDiagnosticFile = (filename) => {
71360
- const absolutePath = resolveAbsolutePath(filename);
71369
+ const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
71361
71370
  const cached = minifiedFileCache.get(absolutePath);
71362
71371
  if (cached !== void 0) return cached;
71363
71372
  const minified = isMinifiedSource(absolutePath);
71364
71373
  minifiedFileCache.set(absolutePath, minified);
71365
71374
  return minified;
71366
71375
  };
71367
- 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) => {
71376
+ 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) => {
71368
71377
  const { plugin, rule } = parseRuleCode(diagnostic.code);
71369
71378
  const primaryLabel = diagnostic.labels[0];
71370
71379
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
71371
- const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
71380
+ const mappedSourceFilename = resolveMappedSourceFilename(diagnostic.filename);
71381
+ const normalizedFilePath = (mappedSourceFilename ?? diagnostic.filename).replaceAll("\\", "/");
71372
71382
  const primarySpan = primaryLabel?.span;
71373
71383
  const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
71374
- const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
71384
+ const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath, mappedSourceFilename === void 0 ? null : sourceBuffer);
71375
71385
  const category = resolveDiagnosticCategory(plugin, rule);
71376
71386
  const matchByOccurrence = resolveMatchByOccurrence(rule, category);
71377
71387
  const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
71378
- const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
71388
+ const primaryLineNumber = primarySpan && mappedSourceFilename !== void 0 && sourceBuffer !== null ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset) : primarySpan?.line ?? 0;
71389
+ const primaryColumnNumber = primarySpan && mappedSourceFilename !== void 0 && sourceBuffer !== null ? columnOfUtf8Offset(sourceBuffer, primarySpan.offset) : primarySpan?.column ?? 0;
71390
+ const primaryLineIndex = primarySpan ? primaryLineNumber - 1 : -1;
71379
71391
  const primaryLine = sourceLines?.[primaryLineIndex];
71380
- const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
71392
+ const primaryColumnIndex = primarySpan ? primaryColumnNumber - 1 : -1;
71381
71393
  const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
71382
71394
  const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
71383
71395
  const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
@@ -71390,8 +71402,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
71390
71402
  message: cleaned.message,
71391
71403
  help: cleaned.help,
71392
71404
  url: diagnostic.url,
71393
- line: primarySpan?.line ?? 0,
71394
- column: primarySpan?.column ?? 0,
71405
+ line: primaryLineNumber,
71406
+ column: primaryColumnNumber,
71395
71407
  ...primarySpan ? {
71396
71408
  offset: primarySpan.offset,
71397
71409
  length: primarySpan.length,
@@ -71579,7 +71591,11 @@ const isParallelismRelatedSpawnError = (error) => {
71579
71591
  * loop with a slimmer config in that case.
71580
71592
  */
71581
71593
  const spawnLintBatches = async (input) => {
71582
- const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
71594
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
71595
+ const resolveSourcePath = (filePath) => {
71596
+ const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
71597
+ return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
71598
+ };
71583
71599
  const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
71584
71600
  let rescueDeadlineEpochMs;
71585
71601
  const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
@@ -71599,7 +71615,7 @@ const spawnLintBatches = async (input) => {
71599
71615
  }
71600
71616
  const batchArgs = [...baseArgs, ...batch];
71601
71617
  try {
71602
- return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
71618
+ return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory, sourcePathByLintPath);
71603
71619
  } catch (error) {
71604
71620
  if (!isSplittableReactDoctorError(error)) throw error;
71605
71621
  if (isPastDeadline()) {
@@ -71705,10 +71721,12 @@ const spawnLintBatches = async (input) => {
71705
71721
  onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
71706
71722
  };
71707
71723
  const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
71708
- reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
71709
- reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
71724
+ const droppedSourceFiles = [...new Set(droppedFiles.map(resolveSourcePath))];
71725
+ const deadlineSkippedSourceFiles = [...new Set(outcome.deadlineSkippedFiles.map(resolveSourcePath))];
71726
+ reportSkippedFiles(droppedSourceFiles, (fileListPreview) => `${droppedSourceFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
71727
+ reportSkippedFiles(deadlineSkippedSourceFiles, (fileListPreview) => `${deadlineSkippedSourceFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
71710
71728
  const unavailableFiles = new Set([...droppedFiles, ...outcome.deadlineSkippedFiles]);
71711
- input.onAnalyzedFiles?.([...new Set(fileBatches.flat().filter((filePath) => !unavailableFiles.has(filePath)))].sort());
71729
+ input.onAnalyzedFiles?.([...new Set(fileBatches.flat().filter((filePath) => !unavailableFiles.has(filePath)).map(resolveSourcePath))].sort());
71712
71730
  return dedupeDiagnostics(diagnostics);
71713
71731
  };
71714
71732
  let didValidate = false;
@@ -71807,6 +71825,266 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
71807
71825
  }
71808
71826
  return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
71809
71827
  };
71828
+ const THREE_MODULE_IMPORT_PATTERN = /\b(?:from\s*|import\s*(?:\(\s*)?|require\s*\(\s*)["']three(?:\/[^"']*)?["']/;
71829
+ const containsThreeModuleImport = (sourceText) => THREE_MODULE_IMPORT_PATTERN.test(sourceText);
71830
+ const COMMENT_OPEN = "<!--";
71831
+ const COMMENT_CLOSE = "-->";
71832
+ const FRONTMATTER_FENCE = "---";
71833
+ const SCRIPT_TAG_NAME = "script";
71834
+ const SCRIPT_OPEN = `<${SCRIPT_TAG_NAME}`;
71835
+ const SCRIPT_CLOSE = `</${SCRIPT_TAG_NAME}`;
71836
+ const TEMPLATE_TAG_NAME = "template";
71837
+ const TEMPLATE_CLOSE = `</${TEMPLATE_TAG_NAME}`;
71838
+ const JAVASCRIPT_MIME_TYPES = new Set([
71839
+ "application/ecmascript",
71840
+ "application/javascript",
71841
+ "application/x-ecmascript",
71842
+ "application/x-javascript",
71843
+ "text/ecmascript",
71844
+ "text/javascript",
71845
+ "text/javascript1.0",
71846
+ "text/javascript1.1",
71847
+ "text/javascript1.2",
71848
+ "text/javascript1.3",
71849
+ "text/javascript1.4",
71850
+ "text/javascript1.5",
71851
+ "text/jscript",
71852
+ "text/livescript",
71853
+ "text/x-ecmascript",
71854
+ "text/x-javascript"
71855
+ ]);
71856
+ const RAW_TEXT_TAG_NAMES = new Set([
71857
+ "iframe",
71858
+ "noembed",
71859
+ "noframes",
71860
+ "noscript",
71861
+ "style",
71862
+ "textarea",
71863
+ "title",
71864
+ "xmp"
71865
+ ]);
71866
+ const PLAINTEXT_TAG_NAME = "plaintext";
71867
+ const TAG_OPEN_UTF8_BYTE = "<".charCodeAt(0);
71868
+ const TAG_CLOSE_UTF8_BYTE = ">".charCodeAt(0);
71869
+ const SINGLE_QUOTE_UTF8_BYTE = "'".charCodeAt(0);
71870
+ const DOUBLE_QUOTE_UTF8_BYTE = "\"".charCodeAt(0);
71871
+ const ATTRIBUTE_ASSIGNMENT_UTF8_BYTE = "=".charCodeAt(0);
71872
+ const TAG_SELF_CLOSE_UTF8_BYTE = "/".charCodeAt(0);
71873
+ const TAB_UTF8_BYTE = " ".charCodeAt(0);
71874
+ const FORM_FEED_UTF8_BYTE = "\f".charCodeAt(0);
71875
+ const isHtmlWhitespace = (byte) => byte === TAB_UTF8_BYTE || byte === 10 || byte === FORM_FEED_UTF8_BYTE || byte === 13 || byte === 32;
71876
+ const matchesAsciiCaseInsensitive = (sourceBuffer, startByte, expected) => {
71877
+ if (startByte + expected.length > sourceBuffer.length) return false;
71878
+ for (let expectedIndex = 0; expectedIndex < expected.length; expectedIndex++) {
71879
+ const sourceByte = sourceBuffer[startByte + expectedIndex];
71880
+ if (sourceByte === void 0 || String.fromCharCode(sourceByte).toLowerCase() !== expected[expectedIndex]) return false;
71881
+ }
71882
+ return true;
71883
+ };
71884
+ const isTagBoundary = (byte) => byte === TAG_CLOSE_UTF8_BYTE || byte === TAG_SELF_CLOSE_UTF8_BYTE || isHtmlWhitespace(byte);
71885
+ const isPotentialTagStart = (byte) => {
71886
+ if (byte === void 0) return false;
71887
+ return /[A-Za-z!/?]/.test(String.fromCharCode(byte));
71888
+ };
71889
+ const findLineEnd = (sourceBuffer, lineStartByte) => {
71890
+ let byteIndex = lineStartByte;
71891
+ while (byteIndex < sourceBuffer.length && sourceBuffer[byteIndex] !== 10 && sourceBuffer[byteIndex] !== 13) byteIndex++;
71892
+ return byteIndex;
71893
+ };
71894
+ const findNextLineStart = (sourceBuffer, lineStartByte) => {
71895
+ const lineEndByte = findLineEnd(sourceBuffer, lineStartByte);
71896
+ if (sourceBuffer[lineEndByte] === 13 && sourceBuffer[lineEndByte + 1] === 10) return lineEndByte + 2;
71897
+ return Math.min(lineEndByte + 1, sourceBuffer.length);
71898
+ };
71899
+ const isFrontmatterFenceLine = (sourceBuffer, lineStartByte) => {
71900
+ if (!matchesAsciiCaseInsensitive(sourceBuffer, lineStartByte, FRONTMATTER_FENCE)) return false;
71901
+ const lineEndByte = findLineEnd(sourceBuffer, lineStartByte);
71902
+ for (let byteIndex = lineStartByte + 3; byteIndex < lineEndByte; byteIndex++) if (!isHtmlWhitespace(sourceBuffer[byteIndex])) return false;
71903
+ return true;
71904
+ };
71905
+ const findFrontmatterEnd = (sourceBuffer) => {
71906
+ if (!isFrontmatterFenceLine(sourceBuffer, 0)) return 0;
71907
+ const openingFenceEndByte = findLineEnd(sourceBuffer, 0);
71908
+ let lineStartByte = findNextLineStart(sourceBuffer, 0);
71909
+ while (lineStartByte < sourceBuffer.length) {
71910
+ if (isFrontmatterFenceLine(sourceBuffer, lineStartByte)) return findLineEnd(sourceBuffer, lineStartByte);
71911
+ lineStartByte = findNextLineStart(sourceBuffer, lineStartByte);
71912
+ }
71913
+ return openingFenceEndByte;
71914
+ };
71915
+ const maskByteRange = (maskedBuffer, startByte, endByte) => {
71916
+ for (let byteIndex = startByte; byteIndex < endByte; byteIndex++) {
71917
+ const byte = maskedBuffer[byteIndex];
71918
+ if (byte !== 10 && byte !== 13) maskedBuffer[byteIndex] = 32;
71919
+ }
71920
+ };
71921
+ const readStartTagName = (sourceBuffer, tagStartByte) => {
71922
+ const firstNameByte = sourceBuffer[tagStartByte + 1];
71923
+ if (firstNameByte === void 0 || !/[A-Za-z]/.test(String.fromCharCode(firstNameByte))) return null;
71924
+ let nameEndByte = tagStartByte + 2;
71925
+ while (nameEndByte < sourceBuffer.length && /[A-Za-z0-9:-]/.test(String.fromCharCode(sourceBuffer[nameEndByte] ?? 32))) nameEndByte++;
71926
+ return sourceBuffer.subarray(tagStartByte + 1, nameEndByte).toString("ascii").toLowerCase();
71927
+ };
71928
+ const findTagEnd = (sourceBuffer, startByte) => {
71929
+ let quoteByte = null;
71930
+ for (let byteIndex = startByte; byteIndex < sourceBuffer.length; byteIndex++) {
71931
+ const byte = sourceBuffer[byteIndex];
71932
+ if (quoteByte !== null) {
71933
+ if (byte === quoteByte) quoteByte = null;
71934
+ continue;
71935
+ }
71936
+ if (byte === SINGLE_QUOTE_UTF8_BYTE || byte === DOUBLE_QUOTE_UTF8_BYTE) {
71937
+ quoteByte = byte;
71938
+ continue;
71939
+ }
71940
+ if (byte === TAG_CLOSE_UTF8_BYTE) return byteIndex;
71941
+ }
71942
+ return sourceBuffer.length;
71943
+ };
71944
+ const parseScriptAttributes = (sourceBuffer, attributesStartByte, tagEndByte) => {
71945
+ let byteIndex = attributesStartByte;
71946
+ let hasSource = false;
71947
+ let language = null;
71948
+ let scriptType = null;
71949
+ while (byteIndex < tagEndByte) {
71950
+ while (byteIndex < tagEndByte && (isHtmlWhitespace(sourceBuffer[byteIndex]) || sourceBuffer[byteIndex] === TAG_SELF_CLOSE_UTF8_BYTE)) byteIndex++;
71951
+ const attributeNameStartByte = byteIndex;
71952
+ while (byteIndex < tagEndByte && !isHtmlWhitespace(sourceBuffer[byteIndex]) && sourceBuffer[byteIndex] !== ATTRIBUTE_ASSIGNMENT_UTF8_BYTE && sourceBuffer[byteIndex] !== TAG_SELF_CLOSE_UTF8_BYTE) byteIndex++;
71953
+ const attributeName = sourceBuffer.subarray(attributeNameStartByte, byteIndex).toString("ascii").toLowerCase();
71954
+ while (byteIndex < tagEndByte && isHtmlWhitespace(sourceBuffer[byteIndex])) byteIndex++;
71955
+ let attributeValue = "";
71956
+ if (sourceBuffer[byteIndex] === ATTRIBUTE_ASSIGNMENT_UTF8_BYTE) {
71957
+ byteIndex++;
71958
+ while (byteIndex < tagEndByte && isHtmlWhitespace(sourceBuffer[byteIndex])) byteIndex++;
71959
+ const quoteByte = sourceBuffer[byteIndex];
71960
+ if (quoteByte === SINGLE_QUOTE_UTF8_BYTE || quoteByte === DOUBLE_QUOTE_UTF8_BYTE) {
71961
+ const valueStartByte = byteIndex + 1;
71962
+ byteIndex = valueStartByte;
71963
+ while (byteIndex < tagEndByte && sourceBuffer[byteIndex] !== quoteByte) byteIndex++;
71964
+ attributeValue = sourceBuffer.subarray(valueStartByte, byteIndex).toString("utf8");
71965
+ if (byteIndex < tagEndByte) byteIndex++;
71966
+ } else {
71967
+ const valueStartByte = byteIndex;
71968
+ while (byteIndex < tagEndByte && !isHtmlWhitespace(sourceBuffer[byteIndex]) && sourceBuffer[byteIndex] !== TAG_CLOSE_UTF8_BYTE) byteIndex++;
71969
+ attributeValue = sourceBuffer.subarray(valueStartByte, byteIndex).toString("utf8");
71970
+ }
71971
+ }
71972
+ if (attributeName === "src") hasSource = true;
71973
+ else if (attributeName === "type" && scriptType === null) scriptType = attributeValue;
71974
+ else if (attributeName === "language" && language === null) language = attributeValue;
71975
+ }
71976
+ return {
71977
+ hasSource,
71978
+ language,
71979
+ type: scriptType
71980
+ };
71981
+ };
71982
+ const isExecutableScript = (attributes) => {
71983
+ if (attributes.hasSource) return false;
71984
+ const type = attributes.type?.trim().toLowerCase() ?? "";
71985
+ if (type !== "") {
71986
+ const mimeType = type.split(";")[0]?.trim() ?? "";
71987
+ return mimeType === "module" || JAVASCRIPT_MIME_TYPES.has(mimeType);
71988
+ }
71989
+ const language = attributes.language?.trim().toLowerCase() ?? "";
71990
+ return language === "" || JAVASCRIPT_MIME_TYPES.has(`text/${language}`);
71991
+ };
71992
+ const findElementClose = (sourceBuffer, startByte, tagName) => {
71993
+ const closeTagStart = `</${tagName}`;
71994
+ 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;
71995
+ return sourceBuffer.length;
71996
+ };
71997
+ const prepareHtmlScriptSource = (sourceBuffer) => {
71998
+ const frontmatterEndByte = findFrontmatterEnd(sourceBuffer);
71999
+ let maskedBuffer = frontmatterEndByte > 0 ? Buffer.from(sourceBuffer) : null;
72000
+ if (maskedBuffer !== null) maskByteRange(maskedBuffer, 0, frontmatterEndByte);
72001
+ const executableScriptBodies = [];
72002
+ let byteIndex = frontmatterEndByte;
72003
+ let templateDepth = 0;
72004
+ while (byteIndex < sourceBuffer.length) {
72005
+ if (sourceBuffer[byteIndex] !== TAG_OPEN_UTF8_BYTE) {
72006
+ byteIndex++;
72007
+ continue;
72008
+ }
72009
+ if (matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, COMMENT_OPEN)) {
72010
+ const commentEndByte = sourceBuffer.indexOf(COMMENT_CLOSE, byteIndex + 4);
72011
+ byteIndex = commentEndByte === -1 ? sourceBuffer.length : commentEndByte + 3;
72012
+ continue;
72013
+ }
72014
+ if (matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, TEMPLATE_CLOSE) && isTagBoundary(sourceBuffer[byteIndex + TEMPLATE_CLOSE.length])) {
72015
+ const tagEndByte = findTagEnd(sourceBuffer, byteIndex + TEMPLATE_CLOSE.length);
72016
+ if (templateDepth > 0) templateDepth--;
72017
+ byteIndex = Math.min(tagEndByte + 1, sourceBuffer.length);
72018
+ continue;
72019
+ }
72020
+ if (!matchesAsciiCaseInsensitive(sourceBuffer, byteIndex, SCRIPT_OPEN) || !isTagBoundary(sourceBuffer[byteIndex + SCRIPT_OPEN.length])) {
72021
+ if (!isPotentialTagStart(sourceBuffer[byteIndex + 1])) {
72022
+ byteIndex++;
72023
+ continue;
72024
+ }
72025
+ const tagEndByte = findTagEnd(sourceBuffer, byteIndex + 1);
72026
+ const tagName = readStartTagName(sourceBuffer, byteIndex);
72027
+ if (tagName === PLAINTEXT_TAG_NAME) break;
72028
+ if (tagName === TEMPLATE_TAG_NAME) templateDepth++;
72029
+ if (tagName !== null && RAW_TEXT_TAG_NAMES.has(tagName)) {
72030
+ const closeTagStartByte = findElementClose(sourceBuffer, tagEndByte + 1, tagName);
72031
+ if (closeTagStartByte === sourceBuffer.length) break;
72032
+ const closeTagEndByte = findTagEnd(sourceBuffer, closeTagStartByte + tagName.length + 2);
72033
+ byteIndex = Math.min(closeTagEndByte + 1, sourceBuffer.length);
72034
+ continue;
72035
+ }
72036
+ byteIndex = Math.min(tagEndByte + 1, sourceBuffer.length);
72037
+ continue;
72038
+ }
72039
+ const tagEndByte = findTagEnd(sourceBuffer, byteIndex + SCRIPT_OPEN.length);
72040
+ if (tagEndByte === sourceBuffer.length) break;
72041
+ const bodyStartByte = tagEndByte + 1;
72042
+ const bodyEndByte = findElementClose(sourceBuffer, bodyStartByte, SCRIPT_TAG_NAME);
72043
+ const attributes = parseScriptAttributes(sourceBuffer, byteIndex + SCRIPT_OPEN.length, tagEndByte);
72044
+ if (attributes.hasSource || templateDepth > 0) {
72045
+ maskedBuffer ??= Buffer.from(sourceBuffer);
72046
+ maskByteRange(maskedBuffer, bodyStartByte, bodyEndByte);
72047
+ } else if (isExecutableScript(attributes)) executableScriptBodies.push(sourceBuffer.subarray(bodyStartByte, bodyEndByte));
72048
+ if (bodyEndByte === sourceBuffer.length) break;
72049
+ const closeTagEndByte = findTagEnd(sourceBuffer, bodyEndByte + SCRIPT_CLOSE.length);
72050
+ byteIndex = Math.min(closeTagEndByte + 1, sourceBuffer.length);
72051
+ }
72052
+ return {
72053
+ executableScriptBodies,
72054
+ lintBuffer: maskedBuffer ?? sourceBuffer
72055
+ };
72056
+ };
72057
+ const prepareHtmlLintSources = (rootDirectory, temporaryDirectory, candidateFiles) => {
72058
+ const lintFiles = [];
72059
+ const sourcePathByLintPath = /* @__PURE__ */ new Map();
72060
+ const sizeByLintPath = /* @__PURE__ */ new Map();
72061
+ const htmlSourcesDirectory = Path.join(temporaryDirectory, "html-sources");
72062
+ let htmlFileIndex = 0;
72063
+ let hasThreeModuleImport = false;
72064
+ for (const candidateFile of candidateFiles) {
72065
+ if (!HTML_FILE_PATTERN.test(candidateFile)) {
72066
+ lintFiles.push(candidateFile);
72067
+ continue;
72068
+ }
72069
+ const absoluteSourcePath = Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile);
72070
+ const sourceBuffer = NFS.readFileSync(absoluteSourcePath);
72071
+ const { executableScriptBodies, lintBuffer } = prepareHtmlScriptSource(sourceBuffer);
72072
+ if (htmlFileIndex === 0) NFS.mkdirSync(htmlSourcesDirectory, { recursive: true });
72073
+ const lintPath = Path.join(htmlSourcesDirectory, `${htmlFileIndex}.astro`);
72074
+ NFS.writeFileSync(lintPath, lintBuffer);
72075
+ lintFiles.push(lintPath);
72076
+ sourcePathByLintPath.set(Path.resolve(lintPath), candidateFile);
72077
+ sizeByLintPath.set(lintPath, sourceBuffer.length);
72078
+ hasThreeModuleImport ||= executableScriptBodies.some((scriptBody) => containsThreeModuleImport(scriptBody.toString("utf8")));
72079
+ htmlFileIndex++;
72080
+ }
72081
+ return {
72082
+ lintFiles,
72083
+ sourcePathByLintPath,
72084
+ sizeByLintPath,
72085
+ hasThreeModuleImport
72086
+ };
72087
+ };
71810
72088
  /**
71811
72089
  * Atomically (re)writes the generated oxlintrc.json. Used twice in
71812
72090
  * the runner: once for the primary scan, once for the
@@ -71980,21 +72258,6 @@ const runOxlint = async (options) => {
71980
72258
  const pluginPath = resolvePluginPath();
71981
72259
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
71982
72260
  const userPlugins = includedTags.size > 0 ? [] : resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
71983
- const buildConfig = (overrides) => createOxlintConfig({
71984
- pluginPath,
71985
- project,
71986
- customRulesOnly,
71987
- extendsPaths: overrides.extendsPaths,
71988
- ignoredTags,
71989
- includedTags,
71990
- includeTagDefaults,
71991
- serverAuthFunctionNames,
71992
- severityControls,
71993
- userPlugins,
71994
- disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
71995
- ruleSelection: overrides.ruleSelection,
71996
- sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
71997
- });
71998
72261
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
71999
72262
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
72000
72263
  const configPath = Path.join(configDirectory, "oxlintrc.json");
@@ -72030,7 +72293,30 @@ const runOxlint = async (options) => {
72030
72293
  await yieldToEventLoop();
72031
72294
  const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
72032
72295
  const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
72296
+ const preparedHtmlSources = prepareHtmlLintSources(rootDirectory, configDirectory, candidateFiles);
72297
+ const lintFiles = preparedHtmlSources.lintFiles;
72298
+ const hasHtmlFiles = preparedHtmlSources.sourcePathByLintPath.size > 0;
72299
+ const lintProject = !project.hasThree && preparedHtmlSources.hasThreeModuleImport ? {
72300
+ ...project,
72301
+ hasThree: true
72302
+ } : project;
72303
+ const buildConfig = (overrides) => createOxlintConfig({
72304
+ pluginPath,
72305
+ project: lintProject,
72306
+ customRulesOnly,
72307
+ extendsPaths: overrides.extendsPaths,
72308
+ ignoredTags,
72309
+ includedTags,
72310
+ includeTagDefaults,
72311
+ serverAuthFunctionNames,
72312
+ severityControls,
72313
+ userPlugins,
72314
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
72315
+ ruleSelection: overrides.ruleSelection,
72316
+ sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
72317
+ });
72033
72318
  const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
72319
+ if (sizeByFile !== null) for (const [lintPath, sizeBytes] of preparedHtmlSources.sizeByLintPath) sizeByFile.set(lintPath, sizeBytes);
72034
72320
  const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
72035
72321
  baseArgs: passBaseArgs,
72036
72322
  files: passFiles,
@@ -72059,7 +72345,8 @@ const runOxlint = async (options) => {
72059
72345
  fileBatches: passFileBatches,
72060
72346
  rootDirectory,
72061
72347
  nodeBinaryPath,
72062
- project,
72348
+ project: lintProject,
72349
+ sourcePathByLintPath: preparedHtmlSources.sourcePathByLintPath,
72063
72350
  onPartialFailure: reportPartialFailure,
72064
72351
  onAnalyzedFiles: (filePaths) => {
72065
72352
  analyzedFiles = filePaths;
@@ -72094,7 +72381,7 @@ const runOxlint = async (options) => {
72094
72381
  };
72095
72382
  }
72096
72383
  };
72097
- if (perFileLintCacheEnabled && !project.hasReactCompiler && project.hasReactCompilerLintPlugin !== true && extendsPaths.length === 0 && userPlugins.length === 0) {
72384
+ if (perFileLintCacheEnabled && !project.hasReactCompiler && project.hasReactCompilerLintPlugin !== true && extendsPaths.length === 0 && userPlugins.length === 0 && !hasHtmlFiles) {
72098
72385
  const rulesetHash = computeRulesetHash({
72099
72386
  config: buildConfig({
72100
72387
  extendsPaths: [],
@@ -72252,14 +72539,15 @@ const runOxlint = async (options) => {
72252
72539
  ]);
72253
72540
  }
72254
72541
  const baseArgs = makeBaseArgs(configPath);
72255
- const fileBatches = buildFileBatches(baseArgs, candidateFiles);
72542
+ const fileBatches = buildFileBatches(baseArgs, lintFiles);
72256
72543
  let analyzedFiles = [];
72257
72544
  const runBatches = () => spawnLintBatches({
72258
72545
  baseArgs,
72259
72546
  fileBatches,
72260
72547
  rootDirectory,
72261
72548
  nodeBinaryPath,
72262
- project,
72549
+ project: lintProject,
72550
+ sourcePathByLintPath: preparedHtmlSources.sourcePathByLintPath,
72263
72551
  onPartialFailure,
72264
72552
  onAnalyzedFiles: (filePaths) => {
72265
72553
  analyzedFiles = filePaths;
@@ -73835,7 +74123,8 @@ const SCANNABLE_EXTENSIONS = [
73835
74123
  ".js",
73836
74124
  ".jsx",
73837
74125
  ".mts",
73838
- ".mjs"
74126
+ ".mjs",
74127
+ ".html"
73839
74128
  ];
73840
74129
  const SILENT_LOGGER = {
73841
74130
  info: () => {},
@@ -75823,5 +76112,5 @@ const startLanguageServer = () => {
75823
76112
  };
75824
76113
  //#endregion
75825
76114
  export { startLanguageServer };
75826
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3e0d8192-41dd-5226-8de2-b76eaf077e6c")}catch(e){}}();
75827
- //# debugId=3e0d8192-41dd-5226-8de2-b76eaf077e6c
76115
+ !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]="8f4550d9-ab09-5b95-8e16-28b534d81552")}catch(e){}}();
76116
+ //# debugId=8f4550d9-ab09-5b95-8e16-28b534d81552
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.9.1-dev.5d2f66d",
3
+ "version": "0.9.1-dev.8534d86",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -70,7 +70,7 @@
70
70
  "vscode-uri": "^3.1.0",
71
71
  "yaml": "^2.9.0",
72
72
  "deslop-js": "0.9.1",
73
- "oxlint-plugin-react-doctor": "0.9.1-dev.5d2f66d"
73
+ "oxlint-plugin-react-doctor": "0.9.1-dev.8534d86"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/babel__code-frame": "^7.27.0",
@@ -80,9 +80,9 @@
80
80
  "commander": "^14.0.3",
81
81
  "ink-testing-library": "^4.0.0",
82
82
  "ora": "^9.4.0",
83
- "@react-doctor/api": "0.9.1",
84
83
  "@react-doctor/core": "0.9.1",
85
- "@react-doctor/language-server": "0.9.1"
84
+ "@react-doctor/language-server": "0.9.1",
85
+ "@react-doctor/api": "0.9.1"
86
86
  },
87
87
  "engines": {
88
88
  "node": "^20.19.0 || >=22.13.0"