react-doctor 0.5.6 → 0.5.7-dev.350a6ed
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 +774 -412
- package/dist/index.d.ts +23 -3
- package/dist/index.js +324 -196
- package/dist/lsp.js +343 -220
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0a49b285-88be-5b0e-803b-22151c5001c2")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -17,6 +17,7 @@ import * as NodeUrl from "node:url";
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
20
21
|
import { gzipSync } from "node:zlib";
|
|
21
22
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
22
23
|
/**
|
|
@@ -19256,7 +19257,8 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
19256
19257
|
category: String$1,
|
|
19257
19258
|
fileContext: optional(Literals(["test", "story"])),
|
|
19258
19259
|
suppressionHint: optional(String$1),
|
|
19259
|
-
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation))
|
|
19260
|
+
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
19261
|
+
fixGroupId: optional(String$1)
|
|
19260
19262
|
}) {};
|
|
19261
19263
|
const JsonReportMode = Literals([
|
|
19262
19264
|
"full",
|
|
@@ -19298,6 +19300,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
|
|
|
19298
19300
|
score: Unknown,
|
|
19299
19301
|
skippedChecks: ArraySchema(String$1),
|
|
19300
19302
|
skippedCheckReasons: optional(Record$1(String$1, String$1)),
|
|
19303
|
+
scannedFileCount: optional(Number$1),
|
|
19301
19304
|
elapsedMilliseconds: Number$1
|
|
19302
19305
|
}) {};
|
|
19303
19306
|
/**
|
|
@@ -32724,6 +32727,7 @@ const isLargeMinifiedFile = (absolutePath) => {
|
|
|
32724
32727
|
if (sizeBytes < 2e4) return false;
|
|
32725
32728
|
return isMinifiedSource(absolutePath);
|
|
32726
32729
|
};
|
|
32730
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32727
32731
|
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32728
32732
|
"EACCES",
|
|
32729
32733
|
"EPERM",
|
|
@@ -32733,11 +32737,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
|
32733
32737
|
"ELOOP",
|
|
32734
32738
|
"ENAMETOOLONG"
|
|
32735
32739
|
]);
|
|
32736
|
-
const isIgnorableReaddirError = (error) =>
|
|
32737
|
-
if (typeof error !== "object" || error === null) return false;
|
|
32738
|
-
const errorCode = error.code;
|
|
32739
|
-
return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
|
|
32740
|
-
};
|
|
32740
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
32741
32741
|
const readDirectoryEntries = (directoryPath) => {
|
|
32742
32742
|
try {
|
|
32743
32743
|
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
@@ -32787,7 +32787,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
|
|
|
32787
32787
|
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
32788
32788
|
} catch (error) {
|
|
32789
32789
|
if (error instanceof SyntaxError) return {};
|
|
32790
|
-
if (error
|
|
32790
|
+
if (isErrnoException(error)) {
|
|
32791
32791
|
const { code } = error;
|
|
32792
32792
|
if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
|
|
32793
32793
|
}
|
|
@@ -33512,17 +33512,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
33512
33512
|
return false;
|
|
33513
33513
|
};
|
|
33514
33514
|
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
33515
|
-
const
|
|
33516
|
-
const spec = packageJson.dependencies?.
|
|
33515
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
33516
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33517
33517
|
return typeof spec === "string" ? spec : null;
|
|
33518
33518
|
};
|
|
33519
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson,
|
|
33519
|
+
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33520
33520
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33521
|
-
const
|
|
33522
|
-
const spec = packageJson.dependencies?.["@shopify/flash-list"] ?? packageJson.devDependencies?.["@shopify/flash-list"] ?? packageJson.peerDependencies?.["@shopify/flash-list"] ?? packageJson.optionalDependencies?.["@shopify/flash-list"];
|
|
33523
|
-
return typeof spec === "string" ? spec : null;
|
|
33524
|
-
};
|
|
33525
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getShopifyFlashListDependencySpec);
|
|
33521
|
+
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33526
33522
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33527
33523
|
if (version === null || !isCatalogReference(version)) return version;
|
|
33528
33524
|
const catalogName = extractCatalogName(version);
|
|
@@ -33534,11 +33530,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
|
|
|
33534
33530
|
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33535
33531
|
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33536
33532
|
};
|
|
33537
|
-
const
|
|
33538
|
-
const spec = packageJson.dependencies?.next ?? packageJson.devDependencies?.next ?? packageJson.peerDependencies?.next ?? packageJson.optionalDependencies?.next;
|
|
33539
|
-
return typeof spec === "string" ? spec : null;
|
|
33540
|
-
};
|
|
33541
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getNextjsDependencySpec);
|
|
33533
|
+
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33542
33534
|
const getPreactVersion = (packageJson) => {
|
|
33543
33535
|
return {
|
|
33544
33536
|
...packageJson.peerDependencies,
|
|
@@ -33620,6 +33612,11 @@ const ES_TARGET_YEAR_BY_NAME = {
|
|
|
33620
33612
|
esnext: 9999
|
|
33621
33613
|
};
|
|
33622
33614
|
/**
|
|
33615
|
+
* tsconfig filenames probed when resolving a project's TypeScript
|
|
33616
|
+
* compiler options — the root config first, then a monorepo base config.
|
|
33617
|
+
*/
|
|
33618
|
+
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
33619
|
+
/**
|
|
33623
33620
|
* Project-config files that `StagedFiles.materialize` copies into
|
|
33624
33621
|
* the temp directory alongside staged sources so oxlint resolves
|
|
33625
33622
|
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
@@ -33666,6 +33663,13 @@ const APP_ONLY_RULE_KEYS = new Set([
|
|
|
33666
33663
|
]);
|
|
33667
33664
|
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33668
33665
|
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33666
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33667
|
+
"react-doctor/no-derived-state",
|
|
33668
|
+
"react-doctor/no-derived-state-effect",
|
|
33669
|
+
"react-doctor/no-derived-useState",
|
|
33670
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
33671
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33672
|
+
]);
|
|
33669
33673
|
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33670
33674
|
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33671
33675
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
@@ -34118,6 +34122,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
34118
34122
|
if (detected.major !== required.major) return detected.major > required.major;
|
|
34119
34123
|
return detected.minor >= required.minor;
|
|
34120
34124
|
};
|
|
34125
|
+
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34121
34126
|
var InvalidGlobPatternError = class extends Error {
|
|
34122
34127
|
pattern;
|
|
34123
34128
|
reason;
|
|
@@ -34146,7 +34151,7 @@ const compileGlobPattern = (rawPattern) => {
|
|
|
34146
34151
|
try {
|
|
34147
34152
|
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
34148
34153
|
} catch (caughtError) {
|
|
34149
|
-
throw new InvalidGlobPatternError(rawPattern,
|
|
34154
|
+
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
34150
34155
|
}
|
|
34151
34156
|
};
|
|
34152
34157
|
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
@@ -34242,115 +34247,6 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34242
34247
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34243
34248
|
};
|
|
34244
34249
|
};
|
|
34245
|
-
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34246
|
-
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34247
|
-
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34248
|
-
let stringDelimiter = null;
|
|
34249
|
-
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34250
|
-
const character = line[charIndex];
|
|
34251
|
-
if (stringDelimiter !== null) {
|
|
34252
|
-
if (character === "\\") {
|
|
34253
|
-
charIndex++;
|
|
34254
|
-
continue;
|
|
34255
|
-
}
|
|
34256
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34257
|
-
continue;
|
|
34258
|
-
}
|
|
34259
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34260
|
-
stringDelimiter = character;
|
|
34261
|
-
continue;
|
|
34262
|
-
}
|
|
34263
|
-
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34264
|
-
}
|
|
34265
|
-
return false;
|
|
34266
|
-
};
|
|
34267
|
-
const findOpenerTagOnLine = (line) => {
|
|
34268
|
-
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34269
|
-
if (match.index === void 0) continue;
|
|
34270
|
-
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34271
|
-
}
|
|
34272
|
-
return null;
|
|
34273
|
-
};
|
|
34274
|
-
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34275
|
-
const openerLine = lines[openerLineIndex];
|
|
34276
|
-
if (openerLine === void 0) return null;
|
|
34277
|
-
const opener = findOpenerTagOnLine(openerLine);
|
|
34278
|
-
if (!opener) return null;
|
|
34279
|
-
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34280
|
-
let braceDepth = 0;
|
|
34281
|
-
let innerAngleDepth = 0;
|
|
34282
|
-
let stringDelimiter = null;
|
|
34283
|
-
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34284
|
-
const currentLine = lines[lineIndex];
|
|
34285
|
-
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34286
|
-
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34287
|
-
const character = currentLine[charIndex];
|
|
34288
|
-
if (stringDelimiter !== null) {
|
|
34289
|
-
if (character === "\\") {
|
|
34290
|
-
charIndex++;
|
|
34291
|
-
continue;
|
|
34292
|
-
}
|
|
34293
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34294
|
-
continue;
|
|
34295
|
-
}
|
|
34296
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34297
|
-
stringDelimiter = character;
|
|
34298
|
-
continue;
|
|
34299
|
-
}
|
|
34300
|
-
if (character === "{") {
|
|
34301
|
-
braceDepth++;
|
|
34302
|
-
continue;
|
|
34303
|
-
}
|
|
34304
|
-
if (character === "}") {
|
|
34305
|
-
braceDepth--;
|
|
34306
|
-
continue;
|
|
34307
|
-
}
|
|
34308
|
-
if (braceDepth !== 0) continue;
|
|
34309
|
-
if (character === "<") {
|
|
34310
|
-
const followCharacter = currentLine[charIndex + 1];
|
|
34311
|
-
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34312
|
-
continue;
|
|
34313
|
-
}
|
|
34314
|
-
if (character !== ">") continue;
|
|
34315
|
-
const previousCharacter = currentLine[charIndex - 1];
|
|
34316
|
-
const nextCharacter = currentLine[charIndex + 1];
|
|
34317
|
-
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34318
|
-
if (innerAngleDepth > 0) {
|
|
34319
|
-
innerAngleDepth--;
|
|
34320
|
-
continue;
|
|
34321
|
-
}
|
|
34322
|
-
return lineIndex;
|
|
34323
|
-
}
|
|
34324
|
-
}
|
|
34325
|
-
return null;
|
|
34326
|
-
};
|
|
34327
|
-
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34328
|
-
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34329
|
-
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34330
|
-
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34331
|
-
}
|
|
34332
|
-
return null;
|
|
34333
|
-
};
|
|
34334
|
-
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34335
|
-
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34336
|
-
const collected = [];
|
|
34337
|
-
let isStillInChain = true;
|
|
34338
|
-
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34339
|
-
const candidateLine = lines[candidateIndex];
|
|
34340
|
-
if (candidateLine === void 0) break;
|
|
34341
|
-
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34342
|
-
if (match) {
|
|
34343
|
-
collected.push({
|
|
34344
|
-
commentLineIndex: candidateIndex,
|
|
34345
|
-
ruleList: match[1],
|
|
34346
|
-
isInChain: isStillInChain
|
|
34347
|
-
});
|
|
34348
|
-
continue;
|
|
34349
|
-
}
|
|
34350
|
-
isStillInChain = false;
|
|
34351
|
-
}
|
|
34352
|
-
return collected;
|
|
34353
|
-
};
|
|
34354
34250
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34355
34251
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34356
34252
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34475,7 +34371,13 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34475
34371
|
}
|
|
34476
34372
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34477
34373
|
const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
|
|
34478
|
-
const
|
|
34374
|
+
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34375
|
+
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34376
|
+
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
34377
|
+
const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
|
|
34378
|
+
if (canonicalCandidate === canonicalTarget) return true;
|
|
34379
|
+
return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
|
|
34380
|
+
};
|
|
34479
34381
|
const getEquivalentRuleKeys = (ruleKey) => {
|
|
34480
34382
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
34481
34383
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
@@ -34485,12 +34387,182 @@ const stripDescriptionTail = (ruleList) => {
|
|
|
34485
34387
|
if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
|
|
34486
34388
|
return ruleList.slice(0, descriptionMatch.index);
|
|
34487
34389
|
};
|
|
34488
|
-
const
|
|
34390
|
+
const tokenizeRuleList = (ruleList) => {
|
|
34489
34391
|
const trimmed = ruleList?.trim();
|
|
34490
|
-
if (!trimmed) return
|
|
34392
|
+
if (!trimmed) return [];
|
|
34491
34393
|
const ruleSection = stripDescriptionTail(trimmed).trim();
|
|
34492
|
-
if (!ruleSection) return
|
|
34493
|
-
return ruleSection.split(/[,\s]+/).
|
|
34394
|
+
if (!ruleSection) return [];
|
|
34395
|
+
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34396
|
+
};
|
|
34397
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34398
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34399
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34400
|
+
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — change it to \`${ruleId}\`.`;
|
|
34401
|
+
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34402
|
+
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34403
|
+
const candidates = [{
|
|
34404
|
+
line: lines[diagnosticLineIndex],
|
|
34405
|
+
requiredScope: "line"
|
|
34406
|
+
}, {
|
|
34407
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34408
|
+
requiredScope: "next-line"
|
|
34409
|
+
}];
|
|
34410
|
+
for (const { line, requiredScope } of candidates) {
|
|
34411
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34412
|
+
if (!match) continue;
|
|
34413
|
+
const [, tool, scope, ruleList] = match;
|
|
34414
|
+
if (scope !== requiredScope) continue;
|
|
34415
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34416
|
+
if (tokens.includes(ruleId)) continue;
|
|
34417
|
+
for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
|
|
34418
|
+
}
|
|
34419
|
+
return null;
|
|
34420
|
+
};
|
|
34421
|
+
const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34422
|
+
let openMisname = null;
|
|
34423
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34424
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34425
|
+
const line = lines[lineIndex];
|
|
34426
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34427
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34428
|
+
if (disableMatch) {
|
|
34429
|
+
const [, tool, ruleList] = disableMatch;
|
|
34430
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34431
|
+
if (tokens.includes(ruleId)) openMisname = null;
|
|
34432
|
+
else {
|
|
34433
|
+
const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
|
|
34434
|
+
if (misnamed) openMisname = {
|
|
34435
|
+
tool,
|
|
34436
|
+
token: misnamed
|
|
34437
|
+
};
|
|
34438
|
+
}
|
|
34439
|
+
continue;
|
|
34440
|
+
}
|
|
34441
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34442
|
+
if (enableMatch) {
|
|
34443
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34444
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
|
|
34445
|
+
}
|
|
34446
|
+
}
|
|
34447
|
+
return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
|
|
34448
|
+
};
|
|
34449
|
+
const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34450
|
+
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34451
|
+
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34452
|
+
};
|
|
34453
|
+
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34454
|
+
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34455
|
+
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34456
|
+
let stringDelimiter = null;
|
|
34457
|
+
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34458
|
+
const character = line[charIndex];
|
|
34459
|
+
if (stringDelimiter !== null) {
|
|
34460
|
+
if (character === "\\") {
|
|
34461
|
+
charIndex++;
|
|
34462
|
+
continue;
|
|
34463
|
+
}
|
|
34464
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34465
|
+
continue;
|
|
34466
|
+
}
|
|
34467
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34468
|
+
stringDelimiter = character;
|
|
34469
|
+
continue;
|
|
34470
|
+
}
|
|
34471
|
+
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34472
|
+
}
|
|
34473
|
+
return false;
|
|
34474
|
+
};
|
|
34475
|
+
const findOpenerTagOnLine = (line) => {
|
|
34476
|
+
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34477
|
+
if (match.index === void 0) continue;
|
|
34478
|
+
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34479
|
+
}
|
|
34480
|
+
return null;
|
|
34481
|
+
};
|
|
34482
|
+
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34483
|
+
const openerLine = lines[openerLineIndex];
|
|
34484
|
+
if (openerLine === void 0) return null;
|
|
34485
|
+
const opener = findOpenerTagOnLine(openerLine);
|
|
34486
|
+
if (!opener) return null;
|
|
34487
|
+
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34488
|
+
let braceDepth = 0;
|
|
34489
|
+
let innerAngleDepth = 0;
|
|
34490
|
+
let stringDelimiter = null;
|
|
34491
|
+
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34492
|
+
const currentLine = lines[lineIndex];
|
|
34493
|
+
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34494
|
+
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34495
|
+
const character = currentLine[charIndex];
|
|
34496
|
+
if (stringDelimiter !== null) {
|
|
34497
|
+
if (character === "\\") {
|
|
34498
|
+
charIndex++;
|
|
34499
|
+
continue;
|
|
34500
|
+
}
|
|
34501
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34502
|
+
continue;
|
|
34503
|
+
}
|
|
34504
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34505
|
+
stringDelimiter = character;
|
|
34506
|
+
continue;
|
|
34507
|
+
}
|
|
34508
|
+
if (character === "{") {
|
|
34509
|
+
braceDepth++;
|
|
34510
|
+
continue;
|
|
34511
|
+
}
|
|
34512
|
+
if (character === "}") {
|
|
34513
|
+
braceDepth--;
|
|
34514
|
+
continue;
|
|
34515
|
+
}
|
|
34516
|
+
if (braceDepth !== 0) continue;
|
|
34517
|
+
if (character === "<") {
|
|
34518
|
+
const followCharacter = currentLine[charIndex + 1];
|
|
34519
|
+
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34520
|
+
continue;
|
|
34521
|
+
}
|
|
34522
|
+
if (character !== ">") continue;
|
|
34523
|
+
const previousCharacter = currentLine[charIndex - 1];
|
|
34524
|
+
const nextCharacter = currentLine[charIndex + 1];
|
|
34525
|
+
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34526
|
+
if (innerAngleDepth > 0) {
|
|
34527
|
+
innerAngleDepth--;
|
|
34528
|
+
continue;
|
|
34529
|
+
}
|
|
34530
|
+
return lineIndex;
|
|
34531
|
+
}
|
|
34532
|
+
}
|
|
34533
|
+
return null;
|
|
34534
|
+
};
|
|
34535
|
+
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34536
|
+
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34537
|
+
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34538
|
+
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34539
|
+
}
|
|
34540
|
+
return null;
|
|
34541
|
+
};
|
|
34542
|
+
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34543
|
+
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34544
|
+
const collected = [];
|
|
34545
|
+
let isStillInChain = true;
|
|
34546
|
+
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34547
|
+
const candidateLine = lines[candidateIndex];
|
|
34548
|
+
if (candidateLine === void 0) break;
|
|
34549
|
+
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34550
|
+
if (match) {
|
|
34551
|
+
collected.push({
|
|
34552
|
+
commentLineIndex: candidateIndex,
|
|
34553
|
+
ruleList: match[1],
|
|
34554
|
+
isInChain: isStillInChain
|
|
34555
|
+
});
|
|
34556
|
+
continue;
|
|
34557
|
+
}
|
|
34558
|
+
isStillInChain = false;
|
|
34559
|
+
}
|
|
34560
|
+
return collected;
|
|
34561
|
+
};
|
|
34562
|
+
const isRuleListedInComment = (ruleList, ruleId) => {
|
|
34563
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34564
|
+
if (tokens.length === 0) return true;
|
|
34565
|
+
return tokens.some((token) => isSameRuleKey(token, ruleId));
|
|
34494
34566
|
};
|
|
34495
34567
|
const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34496
34568
|
const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
|
|
@@ -34534,7 +34606,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34534
34606
|
};
|
|
34535
34607
|
return {
|
|
34536
34608
|
isSuppressed: false,
|
|
34537
|
-
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
|
|
34609
|
+
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
|
|
34538
34610
|
};
|
|
34539
34611
|
};
|
|
34540
34612
|
/**
|
|
@@ -35328,7 +35400,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
|
|
|
35328
35400
|
const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
|
|
35329
35401
|
const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
|
|
35330
35402
|
const jiti = createJiti(import.meta.url);
|
|
35331
|
-
const formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
35332
35403
|
const importDefaultExport = async (jitiInstance, filePath) => {
|
|
35333
35404
|
const imported = await jitiInstance.import(filePath);
|
|
35334
35405
|
return imported?.default ?? imported;
|
|
@@ -35360,7 +35431,7 @@ const loadModuleConfig = async (filePath) => {
|
|
|
35360
35431
|
try {
|
|
35361
35432
|
return await importDefaultExport(aliasJiti, filePath);
|
|
35362
35433
|
} catch (retryError) {
|
|
35363
|
-
throw new Error(`${
|
|
35434
|
+
throw new Error(`${messageFromUnknown(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${messageFromUnknown(retryError)})`, { cause: retryError });
|
|
35364
35435
|
}
|
|
35365
35436
|
}
|
|
35366
35437
|
};
|
|
@@ -35409,7 +35480,7 @@ const loadLegacyConfig = (directory) => {
|
|
|
35409
35480
|
}
|
|
35410
35481
|
warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
|
|
35411
35482
|
} catch (error) {
|
|
35412
|
-
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${
|
|
35483
|
+
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
|
|
35413
35484
|
}
|
|
35414
35485
|
return {
|
|
35415
35486
|
status: "invalid",
|
|
@@ -35436,7 +35507,7 @@ const loadConfigFromDirectory = async (directory) => {
|
|
|
35436
35507
|
warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
|
|
35437
35508
|
sawBrokenConfigFile = true;
|
|
35438
35509
|
} catch (error) {
|
|
35439
|
-
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${
|
|
35510
|
+
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
|
|
35440
35511
|
sawBrokenConfigFile = true;
|
|
35441
35512
|
}
|
|
35442
35513
|
}
|
|
@@ -35538,6 +35609,29 @@ const resolveScanTarget = async (requestedDirectory, options = {}) => {
|
|
|
35538
35609
|
didRedirectViaRootDir: redirectedDirectory !== null
|
|
35539
35610
|
};
|
|
35540
35611
|
};
|
|
35612
|
+
const buildFixGroupId = (diagnostic) => createHash("sha1").update(JSON.stringify([
|
|
35613
|
+
diagnostic.filePath,
|
|
35614
|
+
`${diagnostic.plugin}/${diagnostic.rule}`,
|
|
35615
|
+
diagnostic.message
|
|
35616
|
+
])).digest("hex").slice(0, 16);
|
|
35617
|
+
const isGroupableRule = (diagnostic) => ROOT_CAUSE_GROUPABLE_RULE_KEYS.has(`${diagnostic.plugin}/${diagnostic.rule}`);
|
|
35618
|
+
const assignFixGroups = (diagnostics) => {
|
|
35619
|
+
const siteCountByGroupId = /* @__PURE__ */ new Map();
|
|
35620
|
+
for (const diagnostic of diagnostics) {
|
|
35621
|
+
if (!isGroupableRule(diagnostic)) continue;
|
|
35622
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35623
|
+
siteCountByGroupId.set(groupId, (siteCountByGroupId.get(groupId) ?? 0) + 1);
|
|
35624
|
+
}
|
|
35625
|
+
return diagnostics.map((diagnostic) => {
|
|
35626
|
+
if (!isGroupableRule(diagnostic)) return diagnostic;
|
|
35627
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35628
|
+
if ((siteCountByGroupId.get(groupId) ?? 0) < 2) return diagnostic;
|
|
35629
|
+
return {
|
|
35630
|
+
...diagnostic,
|
|
35631
|
+
fixGroupId: groupId
|
|
35632
|
+
};
|
|
35633
|
+
});
|
|
35634
|
+
};
|
|
35541
35635
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
35542
35636
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
35543
35637
|
const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
|
|
@@ -36044,10 +36138,15 @@ const buildHardeningDiagnostic = (input) => ({
|
|
|
36044
36138
|
column: input.column ?? 0,
|
|
36045
36139
|
category: "Security"
|
|
36046
36140
|
});
|
|
36047
|
-
const checkPnpmHardening = (
|
|
36048
|
-
if (!isPnpmManagedProject(
|
|
36049
|
-
const workspacePath = Path.join(
|
|
36050
|
-
const
|
|
36141
|
+
const checkPnpmHardening = (scanDirectory) => {
|
|
36142
|
+
if (!isPnpmManagedProject(scanDirectory)) return [];
|
|
36143
|
+
const workspacePath = Path.join(scanDirectory, PNPM_WORKSPACE_FILE);
|
|
36144
|
+
const hasWorkspaceFile = isFile(workspacePath);
|
|
36145
|
+
if (!hasWorkspaceFile) {
|
|
36146
|
+
const monorepoRoot = findMonorepoRoot(scanDirectory);
|
|
36147
|
+
if (monorepoRoot !== null && isFile(Path.join(monorepoRoot, PNPM_WORKSPACE_FILE))) return [];
|
|
36148
|
+
}
|
|
36149
|
+
const settings = parseHardeningSettings(hasWorkspaceFile ? NFS.readFileSync(workspacePath, "utf-8") : "");
|
|
36051
36150
|
const diagnostics = [];
|
|
36052
36151
|
if (settings.minimumReleaseAge === null) diagnostics.push(buildHardeningDiagnostic({
|
|
36053
36152
|
message: "pnpm-workspace.yaml is missing `minimumReleaseAge` — newly published versions can ship malware that gets caught and unpublished within hours",
|
|
@@ -36664,7 +36763,7 @@ const readIgnoreFile = (filePath) => {
|
|
|
36664
36763
|
try {
|
|
36665
36764
|
content = NFS.readFileSync(filePath, "utf-8");
|
|
36666
36765
|
} catch (error) {
|
|
36667
|
-
const errnoCode = error
|
|
36766
|
+
const errnoCode = isErrnoException(error) ? error.code : void 0;
|
|
36668
36767
|
if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
|
|
36669
36768
|
return [];
|
|
36670
36769
|
}
|
|
@@ -36705,8 +36804,8 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
36705
36804
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
36706
36805
|
return patterns;
|
|
36707
36806
|
};
|
|
36807
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36708
36808
|
const KNIP_JSON_FILENAME = "knip.json";
|
|
36709
|
-
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36710
36809
|
const readJsonFileSafe = (filePath) => {
|
|
36711
36810
|
let rawContents;
|
|
36712
36811
|
try {
|
|
@@ -36722,10 +36821,10 @@ const readJsonFileSafe = (filePath) => {
|
|
|
36722
36821
|
};
|
|
36723
36822
|
const readKnipConfig = (rootDirectory) => {
|
|
36724
36823
|
const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
|
|
36725
|
-
if (isRecord
|
|
36824
|
+
if (isRecord(knipJson)) return knipJson;
|
|
36726
36825
|
const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
|
|
36727
|
-
const packageKnipConfig = isRecord
|
|
36728
|
-
return isRecord
|
|
36826
|
+
const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
|
|
36827
|
+
return isRecord(packageKnipConfig) ? packageKnipConfig : null;
|
|
36729
36828
|
};
|
|
36730
36829
|
const normalizePatternList = (value) => {
|
|
36731
36830
|
if (typeof value === "string" && value.length > 0) return [value];
|
|
@@ -36737,10 +36836,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
|
|
|
36737
36836
|
return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
|
|
36738
36837
|
};
|
|
36739
36838
|
const collectKnipWorkspacePatterns = (workspaces, settingName) => {
|
|
36740
|
-
if (!isRecord
|
|
36839
|
+
if (!isRecord(workspaces)) return [];
|
|
36741
36840
|
const patterns = [];
|
|
36742
36841
|
for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
|
|
36743
|
-
if (!isRecord
|
|
36842
|
+
if (!isRecord(workspaceConfig)) continue;
|
|
36744
36843
|
patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
|
|
36745
36844
|
}
|
|
36746
36845
|
return patterns;
|
|
@@ -36750,12 +36849,11 @@ const collectKnipPatterns = (rootDirectory, settingName) => {
|
|
|
36750
36849
|
if (!config) return [];
|
|
36751
36850
|
return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
|
|
36752
36851
|
};
|
|
36753
|
-
const collectDeadCodeIgnorePatterns = (rootDirectory
|
|
36852
|
+
const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
36754
36853
|
const seen = /* @__PURE__ */ new Set();
|
|
36755
36854
|
const sources = [
|
|
36756
36855
|
readIgnoreFile(path.join(rootDirectory, ".gitignore")),
|
|
36757
36856
|
collectIgnorePatterns(rootDirectory),
|
|
36758
|
-
userConfig?.ignore?.files ?? [],
|
|
36759
36857
|
collectKnipPatterns(rootDirectory, "ignore")
|
|
36760
36858
|
];
|
|
36761
36859
|
for (const source of sources) for (const pattern of source) seen.add(pattern);
|
|
@@ -36786,8 +36884,6 @@ const toCanonicalPath = (filePath) => {
|
|
|
36786
36884
|
};
|
|
36787
36885
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
36788
36886
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
36789
|
-
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
36790
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36791
36887
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
36792
36888
|
const inputChunks = [];
|
|
36793
36889
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -36845,7 +36941,7 @@ process.stdin.on("end", () => {
|
|
|
36845
36941
|
});
|
|
36846
36942
|
`;
|
|
36847
36943
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
36848
|
-
for (const filename of TSCONFIG_FILENAMES
|
|
36944
|
+
for (const filename of TSCONFIG_FILENAMES) {
|
|
36849
36945
|
const candidate = Path.join(rootDirectory, filename);
|
|
36850
36946
|
if (NFS.existsSync(candidate)) return candidate;
|
|
36851
36947
|
}
|
|
@@ -37033,11 +37129,10 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
|
|
|
37033
37129
|
});
|
|
37034
37130
|
});
|
|
37035
37131
|
const checkDeadCode = async (options) => {
|
|
37036
|
-
const { userConfig } = options;
|
|
37037
37132
|
const rootDirectory = toCanonicalPath(options.rootDirectory);
|
|
37038
37133
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37039
37134
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37040
|
-
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory
|
|
37135
|
+
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37041
37136
|
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37042
37137
|
rootDirectory,
|
|
37043
37138
|
entryPatterns,
|
|
@@ -37227,15 +37322,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37227
37322
|
})()) }));
|
|
37228
37323
|
static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
|
|
37229
37324
|
};
|
|
37230
|
-
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
37231
|
-
|
|
37232
|
-
|
|
37233
|
-
|
|
37234
|
-
|
|
37235
|
-
|
|
37236
|
-
|
|
37237
|
-
}
|
|
37238
|
-
};
|
|
37325
|
+
const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
|
|
37326
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
|
|
37327
|
+
try {
|
|
37328
|
+
return NFS.readFileSync(absolutePath, "utf-8").split("\n");
|
|
37329
|
+
} catch {
|
|
37330
|
+
return null;
|
|
37331
|
+
}
|
|
37239
37332
|
};
|
|
37240
37333
|
var Files = class Files extends Service()("react-doctor/Files") {
|
|
37241
37334
|
static layerNode = succeed$3(Files, Files.of({
|
|
@@ -37446,7 +37539,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37446
37539
|
directory: input.directory,
|
|
37447
37540
|
cause
|
|
37448
37541
|
}) });
|
|
37449
|
-
})
|
|
37542
|
+
}), withSpan("git.exec", { attributes: {
|
|
37543
|
+
"git.command": input.command,
|
|
37544
|
+
"git.subcommand": input.args[0] ?? ""
|
|
37545
|
+
} }));
|
|
37450
37546
|
const runGit = (directory, args) => runCommand({
|
|
37451
37547
|
command: "git",
|
|
37452
37548
|
args,
|
|
@@ -37474,7 +37570,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37474
37570
|
]);
|
|
37475
37571
|
if (candidates.status !== 0) return null;
|
|
37476
37572
|
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
|
|
37477
|
-
});
|
|
37573
|
+
}).pipe(withSpan("Git.defaultBranch"));
|
|
37478
37574
|
const branchExists = (directory, branch) => runGit(directory, [
|
|
37479
37575
|
"rev-parse",
|
|
37480
37576
|
"--verify",
|
|
@@ -37521,7 +37617,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37521
37617
|
const result = resultOption.value;
|
|
37522
37618
|
if (result.status !== 0) return null;
|
|
37523
37619
|
return parseGithubViewerPermission(result.stdout);
|
|
37524
|
-
}).pipe(catch_$1(() => succeed$2(null)));
|
|
37620
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
|
|
37525
37621
|
/**
|
|
37526
37622
|
* Resolves a `--diff A..B` / `A...B` commit range into a changed-file
|
|
37527
37623
|
* selection. Each endpoint is validated with `isSafeGitRevision`
|
|
@@ -37635,7 +37731,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37635
37731
|
changedFiles: splitNullSeparated(diff.stdout),
|
|
37636
37732
|
isCurrentChanges: false
|
|
37637
37733
|
};
|
|
37638
|
-
}),
|
|
37734
|
+
}).pipe(withSpan("Git.diffSelection")),
|
|
37639
37735
|
stagedFilePaths: (directory) => runGit(directory, [
|
|
37640
37736
|
"diff",
|
|
37641
37737
|
"--cached",
|
|
@@ -37677,7 +37773,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37677
37773
|
status: result.status,
|
|
37678
37774
|
stdout: result.stdout
|
|
37679
37775
|
};
|
|
37680
|
-
}),
|
|
37776
|
+
}).pipe(withSpan("Git.grep")),
|
|
37681
37777
|
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
37682
37778
|
if (files.length === 0) return [];
|
|
37683
37779
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
@@ -37693,7 +37789,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37693
37789
|
]);
|
|
37694
37790
|
if (result.status !== 0) return null;
|
|
37695
37791
|
return parseChangedLineRanges(result.stdout);
|
|
37696
|
-
})
|
|
37792
|
+
}).pipe(withSpan("Git.changedLineRanges"))
|
|
37697
37793
|
});
|
|
37698
37794
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
37699
37795
|
/**
|
|
@@ -37908,7 +38004,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
37908
38004
|
for (const [absolutePath, originalContent] of originalContents) try {
|
|
37909
38005
|
NFS.writeFileSync(absolutePath, originalContent);
|
|
37910
38006
|
} catch (error) {
|
|
37911
|
-
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${
|
|
38007
|
+
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
|
|
37912
38008
|
}
|
|
37913
38009
|
};
|
|
37914
38010
|
const onExit = () => restore();
|
|
@@ -38014,7 +38110,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
|
|
|
38014
38110
|
try {
|
|
38015
38111
|
resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
|
|
38016
38112
|
} catch (error) {
|
|
38017
|
-
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${
|
|
38113
|
+
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
|
|
38018
38114
|
return null;
|
|
38019
38115
|
}
|
|
38020
38116
|
const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
|
|
@@ -38086,8 +38182,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38086
38182
|
}
|
|
38087
38183
|
return enabled;
|
|
38088
38184
|
};
|
|
38089
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
|
|
38090
|
-
const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38185
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
|
|
38186
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38091
38187
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38092
38188
|
const jsPlugins = [];
|
|
38093
38189
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -38147,7 +38243,6 @@ const resolveOxlintBinary = () => {
|
|
|
38147
38243
|
return Path.join(oxlintPackageDirectory, "bin", "oxlint");
|
|
38148
38244
|
};
|
|
38149
38245
|
const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
|
|
38150
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
38151
38246
|
const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
38152
38247
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
38153
38248
|
return null;
|
|
@@ -38519,7 +38614,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
|
|
|
38519
38614
|
const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
38520
38615
|
let currentNode = identifier.parent;
|
|
38521
38616
|
while (currentNode) {
|
|
38522
|
-
if (
|
|
38617
|
+
if (isScopeBoundary(currentNode)) {
|
|
38523
38618
|
if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
|
|
38524
38619
|
}
|
|
38525
38620
|
if (currentNode === sourceFile) return false;
|
|
@@ -38610,11 +38705,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
|
|
|
38610
38705
|
});
|
|
38611
38706
|
return resolution;
|
|
38612
38707
|
};
|
|
38613
|
-
const isScopeNode = isScopeBoundary;
|
|
38614
38708
|
const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
|
|
38615
38709
|
let currentNode = identifier.parent;
|
|
38616
38710
|
while (currentNode) {
|
|
38617
|
-
if (
|
|
38711
|
+
if (isScopeBoundary(currentNode)) {
|
|
38618
38712
|
const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
|
|
38619
38713
|
if (resolution) return resolution;
|
|
38620
38714
|
}
|
|
@@ -38784,9 +38878,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
38784
38878
|
try {
|
|
38785
38879
|
parsed = JSON.parse(sanitizedStdout);
|
|
38786
38880
|
} catch {
|
|
38787
|
-
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
38881
|
+
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
38788
38882
|
}
|
|
38789
|
-
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
38883
|
+
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
38790
38884
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
38791
38885
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
38792
38886
|
const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
@@ -38862,7 +38956,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38862
38956
|
child.kill("SIGKILL");
|
|
38863
38957
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38864
38958
|
kind: "timeout",
|
|
38865
|
-
detail: `${spawnTimeoutMs /
|
|
38959
|
+
detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
|
|
38866
38960
|
}) }));
|
|
38867
38961
|
}, spawnTimeoutMs);
|
|
38868
38962
|
timeoutHandle.unref?.();
|
|
@@ -39077,6 +39171,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
39077
39171
|
NFS.closeSync(fileHandle);
|
|
39078
39172
|
}
|
|
39079
39173
|
};
|
|
39174
|
+
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
39175
|
+
/**
|
|
39176
|
+
* Detects an oxlint config-load crash caused by the optional
|
|
39177
|
+
* `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
|
|
39178
|
+
* builds the partial-failure note for it; returns `null` when the failure
|
|
39179
|
+
* was anything else.
|
|
39180
|
+
*
|
|
39181
|
+
* oxlint prints a framed error to stdout (not stderr) and exits non-zero
|
|
39182
|
+
* when a `jsPlugins` entry can't be imported; that non-JSON stdout
|
|
39183
|
+
* surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
|
|
39184
|
+
* config load on it, leaving the plugin in would drop every curated
|
|
39185
|
+
* react-doctor diagnostic too — so the caller retries with the plugin
|
|
39186
|
+
* stripped (issue #833). Both markers sit at the start of oxlint's
|
|
39187
|
+
* message, so they survive the `preview` slice even for deep pnpm paths.
|
|
39188
|
+
*/
|
|
39189
|
+
const reactHooksJsPluginDropNote = (error) => {
|
|
39190
|
+
if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
|
|
39191
|
+
const { preview } = error.reason;
|
|
39192
|
+
if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
|
|
39193
|
+
const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
|
|
39194
|
+
return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
|
|
39195
|
+
};
|
|
39080
39196
|
/**
|
|
39081
39197
|
* The oxlint runner. Composed of three pieces in `runners/oxlint/`:
|
|
39082
39198
|
*
|
|
@@ -39104,15 +39220,16 @@ const runOxlint = async (options) => {
|
|
|
39104
39220
|
const pluginPath = resolvePluginPath();
|
|
39105
39221
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
39106
39222
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
39107
|
-
const buildConfig = (
|
|
39223
|
+
const buildConfig = (overrides) => createOxlintConfig({
|
|
39108
39224
|
pluginPath,
|
|
39109
39225
|
project,
|
|
39110
39226
|
customRulesOnly,
|
|
39111
|
-
extendsPaths:
|
|
39227
|
+
extendsPaths: overrides.extendsPaths,
|
|
39112
39228
|
ignoredTags,
|
|
39113
39229
|
serverAuthFunctionNames,
|
|
39114
39230
|
severityControls,
|
|
39115
|
-
userPlugins
|
|
39231
|
+
userPlugins,
|
|
39232
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
39116
39233
|
});
|
|
39117
39234
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
39118
39235
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -39148,12 +39265,22 @@ const runOxlint = async (options) => {
|
|
|
39148
39265
|
outputMaxBytes,
|
|
39149
39266
|
concurrency: options.concurrency
|
|
39150
39267
|
});
|
|
39151
|
-
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
39268
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
39152
39269
|
try {
|
|
39153
39270
|
return await runBatches();
|
|
39154
39271
|
} catch (error) {
|
|
39272
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
39273
|
+
if (reactHooksJsDropNote !== null) {
|
|
39274
|
+
writeOxlintConfig(configPath, buildConfig({
|
|
39275
|
+
extendsPaths,
|
|
39276
|
+
disableReactHooksJsPlugin: true
|
|
39277
|
+
}));
|
|
39278
|
+
const diagnostics = await runBatches();
|
|
39279
|
+
onPartialFailure?.(reactHooksJsDropNote);
|
|
39280
|
+
return diagnostics;
|
|
39281
|
+
}
|
|
39155
39282
|
if (extendsPaths.length === 0) throw error;
|
|
39156
|
-
writeOxlintConfig(configPath, buildConfig([]));
|
|
39283
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
|
|
39157
39284
|
return await runBatches();
|
|
39158
39285
|
}
|
|
39159
39286
|
} finally {
|
|
@@ -39951,17 +40078,17 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39951
40078
|
}))))))));
|
|
39952
40079
|
const deadCodeFailureState = yield* get$2(deadCodeFailure);
|
|
39953
40080
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
39954
|
-
const scanElapsedSeconds = (scanElapsedMilliseconds /
|
|
40081
|
+
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
39955
40082
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
39956
40083
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
39957
40084
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
39958
40085
|
yield* reporterService.finalize;
|
|
39959
|
-
const finalDiagnostics = [
|
|
40086
|
+
const finalDiagnostics = assignFixGroups([
|
|
39960
40087
|
...envCollected,
|
|
39961
40088
|
...supplyChainCollected,
|
|
39962
40089
|
...lintCollected,
|
|
39963
40090
|
...deadCodeCollected
|
|
39964
|
-
];
|
|
40091
|
+
]);
|
|
39965
40092
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
39966
40093
|
const scoreMetadata = {
|
|
39967
40094
|
...repo !== null ? { repo } : {},
|
|
@@ -40165,7 +40292,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
40165
40292
|
static layerNode = effect(StagedFiles, gen(function* () {
|
|
40166
40293
|
const git = yield* Git;
|
|
40167
40294
|
return StagedFiles.of({
|
|
40168
|
-
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
|
|
40295
|
+
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
|
|
40169
40296
|
materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
|
|
40170
40297
|
directory,
|
|
40171
40298
|
files: stagedFiles,
|
|
@@ -40175,7 +40302,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
40175
40302
|
tempDirectory: tree.tempDirectory,
|
|
40176
40303
|
stagedFiles: tree.materializedFiles,
|
|
40177
40304
|
cleanup: tree.cleanup
|
|
40178
|
-
})))
|
|
40305
|
+
})), withSpan("StagedFiles.materialize"))
|
|
40179
40306
|
});
|
|
40180
40307
|
}));
|
|
40181
40308
|
/**
|
|
@@ -40307,6 +40434,7 @@ const buildJsonReport = (input) => {
|
|
|
40307
40434
|
score: result.score,
|
|
40308
40435
|
skippedChecks: result.skippedChecks,
|
|
40309
40436
|
...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
|
|
40437
|
+
...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
|
|
40310
40438
|
elapsedMilliseconds: result.elapsedMilliseconds
|
|
40311
40439
|
}));
|
|
40312
40440
|
const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
|
|
@@ -40573,4 +40701,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
40573
40701
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
40574
40702
|
|
|
40575
40703
|
//# sourceMappingURL=index.js.map
|
|
40576
|
-
//# debugId=
|
|
40704
|
+
//# debugId=0a49b285-88be-5b0e-803b-22151c5001c2
|