react-doctor 0.9.3-dev.ba04952 → 0.9.3-dev.c825c59
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 +228 -304
- package/dist/index.js +177 -227
- package/dist/lsp.js +181 -204
- package/package.json +4 -4
package/dist/lsp.js
CHANGED
|
@@ -64258,13 +64258,7 @@ const hasReactDependency = (packageJson) => {
|
|
|
64258
64258
|
};
|
|
64259
64259
|
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
64260
64260
|
};
|
|
64261
|
-
const getPreactVersion = (packageJson) =>
|
|
64262
|
-
return {
|
|
64263
|
-
...packageJson.peerDependencies,
|
|
64264
|
-
...packageJson.dependencies,
|
|
64265
|
-
...packageJson.devDependencies
|
|
64266
|
-
}.preact ?? null;
|
|
64267
|
-
};
|
|
64261
|
+
const getPreactVersion = (packageJson) => getDependencySpec(packageJson, "preact");
|
|
64268
64262
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, sourceDirectory = rootDirectory, sourcePackageJson = rootPackageJson, version }) => {
|
|
64269
64263
|
if (version === null || !isCatalogReference(version)) return version;
|
|
64270
64264
|
const catalogName = extractCatalogName(version);
|
|
@@ -65130,7 +65124,7 @@ const clearProjectCache = () => {
|
|
|
65130
65124
|
*/
|
|
65131
65125
|
const discoverProjectWithoutPackageJson = (directory, options) => {
|
|
65132
65126
|
const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
|
|
65133
|
-
const hasOwnTsConfig =
|
|
65127
|
+
const hasOwnTsConfig = isFile(Path.join(directory, "tsconfig.json"));
|
|
65134
65128
|
const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
|
|
65135
65129
|
const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
|
|
65136
65130
|
if (enclosingProject !== null) return {
|
|
@@ -65266,7 +65260,7 @@ const discoverProject = (directory, options = {}) => {
|
|
|
65266
65260
|
const tailwindVersion = tailwindcss.version;
|
|
65267
65261
|
const zodVersion = zod.version;
|
|
65268
65262
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
65269
|
-
const hasTypeScript =
|
|
65263
|
+
const hasTypeScript = isFile(Path.join(directory, "tsconfig.json"));
|
|
65270
65264
|
const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
|
|
65271
65265
|
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
65272
65266
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
@@ -65396,134 +65390,6 @@ const discoverProject = (directory, options = {}) => {
|
|
|
65396
65390
|
* single source file has nothing to analyze.
|
|
65397
65391
|
*/
|
|
65398
65392
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
65399
|
-
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
65400
|
-
var InvalidGlobPatternError = class extends Error {
|
|
65401
|
-
pattern;
|
|
65402
|
-
reason;
|
|
65403
|
-
constructor(pattern, reason) {
|
|
65404
|
-
super(`Invalid glob pattern ${JSON.stringify(pattern)}: ${reason}`);
|
|
65405
|
-
this.name = "InvalidGlobPatternError";
|
|
65406
|
-
this.pattern = pattern;
|
|
65407
|
-
this.reason = reason;
|
|
65408
|
-
}
|
|
65409
|
-
};
|
|
65410
|
-
const assertGlobPattern = (condition, pattern, reason) => {
|
|
65411
|
-
if (!condition) throw new InvalidGlobPatternError(pattern, reason);
|
|
65412
|
-
};
|
|
65413
|
-
const countGlobWildcards = (pattern) => (pattern.match(/[*?]/g) ?? []).length;
|
|
65414
|
-
const normalizeGlobPattern = (pattern) => pattern.replace(/\\/g, "/").replace(/^\//, "");
|
|
65415
|
-
const PICOMATCH_OPTIONS = {
|
|
65416
|
-
dot: true,
|
|
65417
|
-
strictSlashes: false,
|
|
65418
|
-
windows: false
|
|
65419
|
-
};
|
|
65420
|
-
const compileGlobPattern = (rawPattern) => {
|
|
65421
|
-
assertGlobPattern(typeof rawPattern === "string" && rawPattern.length > 0, String(rawPattern), "pattern must be a non-empty string.");
|
|
65422
|
-
assertGlobPattern(rawPattern.length <= MAX_GLOB_PATTERN_LENGTH_CHARS, rawPattern, `pattern length ${rawPattern.length} exceeds the maximum of ${MAX_GLOB_PATTERN_LENGTH_CHARS} characters.`);
|
|
65423
|
-
const wildcardCount = countGlobWildcards(rawPattern);
|
|
65424
|
-
assertGlobPattern(wildcardCount <= 24, rawPattern, `pattern uses ${wildcardCount} wildcards (\`*\` / \`?\`), exceeding the maximum of 24. This guards against catastrophic backtracking from pathological patterns; split the pattern into multiple smaller entries.`);
|
|
65425
|
-
try {
|
|
65426
|
-
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
65427
|
-
} catch (caughtError) {
|
|
65428
|
-
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
65429
|
-
}
|
|
65430
|
-
};
|
|
65431
|
-
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
65432
|
-
const compiled = [];
|
|
65433
|
-
for (const pattern of patterns) try {
|
|
65434
|
-
compiled.push(compileGlobPattern(pattern));
|
|
65435
|
-
} catch (caughtError) {
|
|
65436
|
-
if (!(caughtError instanceof InvalidGlobPatternError)) throw caughtError;
|
|
65437
|
-
onInvalid(caughtError);
|
|
65438
|
-
}
|
|
65439
|
-
return compiled;
|
|
65440
|
-
};
|
|
65441
|
-
const toRelativePath = (filePath, rootDirectory) => {
|
|
65442
|
-
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
65443
|
-
const normalizedRoot = rootDirectory.replace(/\\/g, "/").replace(/\/$/, "") + "/";
|
|
65444
|
-
if (normalizedFilePath.startsWith(normalizedRoot)) return normalizedFilePath.slice(normalizedRoot.length);
|
|
65445
|
-
return normalizedFilePath.replace(/^\.\//, "");
|
|
65446
|
-
};
|
|
65447
|
-
const warnConfigIssue = (message) => {
|
|
65448
|
-
process.stderr.write(`[react-doctor] ${message}\n`);
|
|
65449
|
-
};
|
|
65450
|
-
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
65451
|
-
const collectStringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
65452
|
-
const validateOverrideEntry = (entry, index) => {
|
|
65453
|
-
if (!isPlainObject(entry)) {
|
|
65454
|
-
warnConfigIssue(`ignore.overrides[${index}] must be an object with { files, rules }; ignoring this entry.`);
|
|
65455
|
-
return null;
|
|
65456
|
-
}
|
|
65457
|
-
if (!isStringArray(entry.files)) {
|
|
65458
|
-
warnConfigIssue(`ignore.overrides[${index}].files must be an array of strings; ignoring this entry.`);
|
|
65459
|
-
return null;
|
|
65460
|
-
}
|
|
65461
|
-
if (entry.rules !== void 0 && !isStringArray(entry.rules)) {
|
|
65462
|
-
warnConfigIssue(`ignore.overrides[${index}].rules must be an array of "plugin/rule" strings or omitted; treating as missing (override would suppress every rule for the matched files).`);
|
|
65463
|
-
return { files: entry.files };
|
|
65464
|
-
}
|
|
65465
|
-
return entry.rules === void 0 ? { files: entry.files } : {
|
|
65466
|
-
files: entry.files,
|
|
65467
|
-
rules: entry.rules
|
|
65468
|
-
};
|
|
65469
|
-
};
|
|
65470
|
-
const compileIgnoreOverrides = (userConfig) => {
|
|
65471
|
-
const overrides = userConfig?.ignore?.overrides;
|
|
65472
|
-
if (overrides === void 0) return [];
|
|
65473
|
-
if (!Array.isArray(overrides)) {
|
|
65474
|
-
warnConfigIssue(`ignore.overrides must be an array of { files, rules } entries; ignoring.`);
|
|
65475
|
-
return [];
|
|
65476
|
-
}
|
|
65477
|
-
return overrides.flatMap((entry, index) => {
|
|
65478
|
-
const validated = validateOverrideEntry(entry, index);
|
|
65479
|
-
if (!validated) return [];
|
|
65480
|
-
const filePatterns = compileGlobPatternsLenient(collectStringList(validated.files), (error) => warnConfigIssue(`ignore.overrides[${index}]: ${error.message}`));
|
|
65481
|
-
if (filePatterns.length === 0) return [];
|
|
65482
|
-
return [{
|
|
65483
|
-
filePatterns,
|
|
65484
|
-
ruleIds: new Set(collectStringList(validated.rules))
|
|
65485
|
-
}];
|
|
65486
|
-
});
|
|
65487
|
-
};
|
|
65488
|
-
const isDiagnosticIgnoredByOverrides = (diagnostic, rootDirectory, overrides) => {
|
|
65489
|
-
if (overrides.length === 0) return false;
|
|
65490
|
-
const relativeFilePath = toRelativePath(diagnostic.filePath, rootDirectory);
|
|
65491
|
-
const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
65492
|
-
return overrides.some((override) => override.filePatterns.some((pattern) => pattern.test(relativeFilePath)) && (override.ruleIds.size === 0 || override.ruleIds.has(ruleIdentifier)));
|
|
65493
|
-
};
|
|
65494
|
-
const SEVERITY_FOR_OVERRIDE = {
|
|
65495
|
-
error: "error",
|
|
65496
|
-
warn: "warning"
|
|
65497
|
-
};
|
|
65498
|
-
const restampSeverity = (diagnostic, override) => {
|
|
65499
|
-
const targetSeverity = SEVERITY_FOR_OVERRIDE[override];
|
|
65500
|
-
if (diagnostic.severity === targetSeverity) return diagnostic;
|
|
65501
|
-
return {
|
|
65502
|
-
...diagnostic,
|
|
65503
|
-
severity: targetSeverity
|
|
65504
|
-
};
|
|
65505
|
-
};
|
|
65506
|
-
/**
|
|
65507
|
-
* Assembles the internal `RuleSeverityControls` shape from a user
|
|
65508
|
-
* config's top-level `rules` and `categories` fields — the
|
|
65509
|
-
* ESLint / oxlint-shaped severity surface.
|
|
65510
|
-
*
|
|
65511
|
-
* Returns `undefined` when neither field is present so the common
|
|
65512
|
-
* path (no severity config at all) stays allocation-free for
|
|
65513
|
-
* downstream consumers.
|
|
65514
|
-
*/
|
|
65515
|
-
const buildRuleSeverityControls = (config) => {
|
|
65516
|
-
if (!config) return void 0;
|
|
65517
|
-
if (config.rules === void 0 && config.categories === void 0 && config.buckets === void 0) return;
|
|
65518
|
-
return {
|
|
65519
|
-
...config.rules !== void 0 ? { rules: config.rules } : {},
|
|
65520
|
-
...config.categories !== void 0 ? { categories: config.categories } : {},
|
|
65521
|
-
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
65522
|
-
};
|
|
65523
|
-
};
|
|
65524
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
65525
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
65526
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
65527
65393
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
65528
65394
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
65529
65395
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -65662,6 +65528,139 @@ const getEquivalentRuleKeys = (ruleKey) => {
|
|
|
65662
65528
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
65663
65529
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
65664
65530
|
};
|
|
65531
|
+
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
65532
|
+
var InvalidGlobPatternError = class extends Error {
|
|
65533
|
+
pattern;
|
|
65534
|
+
reason;
|
|
65535
|
+
constructor(pattern, reason) {
|
|
65536
|
+
super(`Invalid glob pattern ${JSON.stringify(pattern)}: ${reason}`);
|
|
65537
|
+
this.name = "InvalidGlobPatternError";
|
|
65538
|
+
this.pattern = pattern;
|
|
65539
|
+
this.reason = reason;
|
|
65540
|
+
}
|
|
65541
|
+
};
|
|
65542
|
+
const assertGlobPattern = (condition, pattern, reason) => {
|
|
65543
|
+
if (!condition) throw new InvalidGlobPatternError(pattern, reason);
|
|
65544
|
+
};
|
|
65545
|
+
const countGlobWildcards = (pattern) => (pattern.match(/[*?]/g) ?? []).length;
|
|
65546
|
+
const normalizeGlobPattern = (pattern) => pattern.replace(/\\/g, "/").replace(/^\//, "");
|
|
65547
|
+
const PICOMATCH_OPTIONS = {
|
|
65548
|
+
dot: true,
|
|
65549
|
+
strictSlashes: false,
|
|
65550
|
+
windows: false
|
|
65551
|
+
};
|
|
65552
|
+
const compileGlobPattern = (rawPattern) => {
|
|
65553
|
+
assertGlobPattern(typeof rawPattern === "string" && rawPattern.length > 0, String(rawPattern), "pattern must be a non-empty string.");
|
|
65554
|
+
assertGlobPattern(rawPattern.length <= MAX_GLOB_PATTERN_LENGTH_CHARS, rawPattern, `pattern length ${rawPattern.length} exceeds the maximum of ${MAX_GLOB_PATTERN_LENGTH_CHARS} characters.`);
|
|
65555
|
+
const wildcardCount = countGlobWildcards(rawPattern);
|
|
65556
|
+
assertGlobPattern(wildcardCount <= 24, rawPattern, `pattern uses ${wildcardCount} wildcards (\`*\` / \`?\`), exceeding the maximum of 24. This guards against catastrophic backtracking from pathological patterns; split the pattern into multiple smaller entries.`);
|
|
65557
|
+
try {
|
|
65558
|
+
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
65559
|
+
} catch (caughtError) {
|
|
65560
|
+
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
65561
|
+
}
|
|
65562
|
+
};
|
|
65563
|
+
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
65564
|
+
const compiled = [];
|
|
65565
|
+
for (const pattern of patterns) try {
|
|
65566
|
+
compiled.push(compileGlobPattern(pattern));
|
|
65567
|
+
} catch (caughtError) {
|
|
65568
|
+
if (!(caughtError instanceof InvalidGlobPatternError)) throw caughtError;
|
|
65569
|
+
onInvalid(caughtError);
|
|
65570
|
+
}
|
|
65571
|
+
return compiled;
|
|
65572
|
+
};
|
|
65573
|
+
const toRelativePath = (filePath, rootDirectory) => {
|
|
65574
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
65575
|
+
const normalizedRoot = rootDirectory.replace(/\\/g, "/").replace(/\/$/, "") + "/";
|
|
65576
|
+
if (normalizedFilePath.startsWith(normalizedRoot)) return normalizedFilePath.slice(normalizedRoot.length);
|
|
65577
|
+
return normalizedFilePath.replace(/^\.\//, "");
|
|
65578
|
+
};
|
|
65579
|
+
const warnConfigIssue = (message) => {
|
|
65580
|
+
process.stderr.write(`[react-doctor] ${message}\n`);
|
|
65581
|
+
};
|
|
65582
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
65583
|
+
const collectStringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
65584
|
+
const hasMatchingRuleOverride = (ruleIds, ruleIdentifier) => {
|
|
65585
|
+
if (ruleIds.size === 0) return true;
|
|
65586
|
+
for (const ruleId of ruleIds) if (isSameRuleKey(ruleId, ruleIdentifier)) return true;
|
|
65587
|
+
return false;
|
|
65588
|
+
};
|
|
65589
|
+
const validateOverrideEntry = (entry, index) => {
|
|
65590
|
+
if (!isPlainObject(entry)) {
|
|
65591
|
+
warnConfigIssue(`ignore.overrides[${index}] must be an object with { files, rules }; ignoring this entry.`);
|
|
65592
|
+
return null;
|
|
65593
|
+
}
|
|
65594
|
+
if (!isStringArray(entry.files)) {
|
|
65595
|
+
warnConfigIssue(`ignore.overrides[${index}].files must be an array of strings; ignoring this entry.`);
|
|
65596
|
+
return null;
|
|
65597
|
+
}
|
|
65598
|
+
if (entry.rules !== void 0 && !isStringArray(entry.rules)) {
|
|
65599
|
+
warnConfigIssue(`ignore.overrides[${index}].rules must be an array of "plugin/rule" strings or omitted; treating as missing (override would suppress every rule for the matched files).`);
|
|
65600
|
+
return { files: entry.files };
|
|
65601
|
+
}
|
|
65602
|
+
return entry.rules === void 0 ? { files: entry.files } : {
|
|
65603
|
+
files: entry.files,
|
|
65604
|
+
rules: entry.rules
|
|
65605
|
+
};
|
|
65606
|
+
};
|
|
65607
|
+
const compileIgnoreOverrides = (userConfig) => {
|
|
65608
|
+
const overrides = userConfig?.ignore?.overrides;
|
|
65609
|
+
if (overrides === void 0) return [];
|
|
65610
|
+
if (!Array.isArray(overrides)) {
|
|
65611
|
+
warnConfigIssue(`ignore.overrides must be an array of { files, rules } entries; ignoring.`);
|
|
65612
|
+
return [];
|
|
65613
|
+
}
|
|
65614
|
+
return overrides.flatMap((entry, index) => {
|
|
65615
|
+
const validated = validateOverrideEntry(entry, index);
|
|
65616
|
+
if (!validated) return [];
|
|
65617
|
+
const filePatterns = compileGlobPatternsLenient(collectStringList(validated.files), (error) => warnConfigIssue(`ignore.overrides[${index}]: ${error.message}`));
|
|
65618
|
+
if (filePatterns.length === 0) return [];
|
|
65619
|
+
return [{
|
|
65620
|
+
filePatterns,
|
|
65621
|
+
ruleIds: new Set(collectStringList(validated.rules))
|
|
65622
|
+
}];
|
|
65623
|
+
});
|
|
65624
|
+
};
|
|
65625
|
+
const isDiagnosticIgnoredByOverrides = (diagnostic, rootDirectory, overrides) => {
|
|
65626
|
+
if (overrides.length === 0) return false;
|
|
65627
|
+
const relativeFilePath = toRelativePath(diagnostic.filePath, rootDirectory);
|
|
65628
|
+
const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
65629
|
+
return overrides.some((override) => override.filePatterns.some((pattern) => pattern.test(relativeFilePath)) && hasMatchingRuleOverride(override.ruleIds, ruleIdentifier));
|
|
65630
|
+
};
|
|
65631
|
+
const SEVERITY_FOR_OVERRIDE = {
|
|
65632
|
+
error: "error",
|
|
65633
|
+
warn: "warning"
|
|
65634
|
+
};
|
|
65635
|
+
const restampSeverity = (diagnostic, override) => {
|
|
65636
|
+
const targetSeverity = SEVERITY_FOR_OVERRIDE[override];
|
|
65637
|
+
if (diagnostic.severity === targetSeverity) return diagnostic;
|
|
65638
|
+
return {
|
|
65639
|
+
...diagnostic,
|
|
65640
|
+
severity: targetSeverity
|
|
65641
|
+
};
|
|
65642
|
+
};
|
|
65643
|
+
/**
|
|
65644
|
+
* Assembles the internal `RuleSeverityControls` shape from a user
|
|
65645
|
+
* config's top-level `rules` and `categories` fields — the
|
|
65646
|
+
* ESLint / oxlint-shaped severity surface.
|
|
65647
|
+
*
|
|
65648
|
+
* Returns `undefined` when neither field is present so the common
|
|
65649
|
+
* path (no severity config at all) stays allocation-free for
|
|
65650
|
+
* downstream consumers.
|
|
65651
|
+
*/
|
|
65652
|
+
const buildRuleSeverityControls = (config) => {
|
|
65653
|
+
if (!config) return void 0;
|
|
65654
|
+
if (config.rules === void 0 && config.categories === void 0 && config.buckets === void 0) return;
|
|
65655
|
+
return {
|
|
65656
|
+
...config.rules !== void 0 ? { rules: config.rules } : {},
|
|
65657
|
+
...config.categories !== void 0 ? { categories: config.categories } : {},
|
|
65658
|
+
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
65659
|
+
};
|
|
65660
|
+
};
|
|
65661
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
65662
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
65663
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
65665
65664
|
const stripDescriptionTail = (ruleList) => {
|
|
65666
65665
|
const descriptionMatch = ruleList.match(/(?:^|\s)--\s/);
|
|
65667
65666
|
if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
|
|
@@ -66074,6 +66073,10 @@ const classifyByDirectoryCohort = (packageDirectory) => {
|
|
|
66074
66073
|
current = parent;
|
|
66075
66074
|
}
|
|
66076
66075
|
};
|
|
66076
|
+
const clearPackageRoleCache = () => {
|
|
66077
|
+
cachedRoleByPackageDirectory.clear();
|
|
66078
|
+
cachedPackageDirectoryByFilename.clear();
|
|
66079
|
+
};
|
|
66077
66080
|
const classifyPackageRole = (filename) => {
|
|
66078
66081
|
if (!filename) return "unknown";
|
|
66079
66082
|
const packageDirectory = findNearestPackageDirectory(filename);
|
|
@@ -66308,12 +66311,8 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
66308
66311
|
if (rule.tags.includes("migration-hint")) return false;
|
|
66309
66312
|
return getFileContext(diagnostic.filePath) !== "production";
|
|
66310
66313
|
};
|
|
66311
|
-
const
|
|
66312
|
-
for (const
|
|
66313
|
-
return false;
|
|
66314
|
-
};
|
|
66315
|
-
const isAppOnlyRule = (ruleIdentifier) => {
|
|
66316
|
-
for (const appOnlyRuleKey of APP_ONLY_RULE_KEYS) if (isSameRuleKey(appOnlyRuleKey, ruleIdentifier)) return true;
|
|
66314
|
+
const matchesRuleKey = (ruleIdentifier, candidates) => {
|
|
66315
|
+
for (const candidate of candidates) if (isSameRuleKey(candidate, ruleIdentifier)) return true;
|
|
66317
66316
|
return false;
|
|
66318
66317
|
};
|
|
66319
66318
|
const isRnRawTextSuppressedByConfig = (diagnostic) => {
|
|
@@ -66355,12 +66354,13 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
66355
66354
|
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
66356
66355
|
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
66357
66356
|
}
|
|
66357
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
66358
66358
|
if (explicitRuleOverride === void 0) {
|
|
66359
|
-
if (
|
|
66359
|
+
if (matchesRuleKey(ruleIdentifier, APP_ONLY_RULE_KEYS) && isLibraryFile(current.filePath)) return null;
|
|
66360
66360
|
}
|
|
66361
66361
|
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
66362
66362
|
if (userConfig) {
|
|
66363
|
-
if (
|
|
66363
|
+
if (matchesRuleKey(ruleIdentifier, ignoredRules)) return suppress(current, "config");
|
|
66364
66364
|
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
66365
66365
|
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
66366
66366
|
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
@@ -66369,7 +66369,6 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
66369
66369
|
if (respectInlineDisables && current.line > 0) {
|
|
66370
66370
|
const lines = getFileLines(current.filePath);
|
|
66371
66371
|
if (lines) {
|
|
66372
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
66373
66372
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
66374
66373
|
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
66375
66374
|
if (evaluation.nearMissHint) current = {
|
|
@@ -66843,6 +66842,7 @@ const DEFAULT_SURFACE_EXCLUDED_TAGS = {
|
|
|
66843
66842
|
score: ["design"],
|
|
66844
66843
|
ciFailure: ["design"]
|
|
66845
66844
|
};
|
|
66845
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66846
66846
|
const VALID_RULE_SEVERITIES = [
|
|
66847
66847
|
"error",
|
|
66848
66848
|
"warn",
|
|
@@ -66885,7 +66885,6 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
66885
66885
|
];
|
|
66886
66886
|
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
66887
66887
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
66888
|
-
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66889
66888
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
66890
66889
|
const isRuleSeverity = (value) => typeof value === "string" && VALID_RULE_SEVERITIES.includes(value);
|
|
66891
66890
|
const coerceMaybeBooleanString = (fieldName, value) => {
|
|
@@ -66913,7 +66912,7 @@ const validateStringArrayField = (fieldName, value) => {
|
|
|
66913
66912
|
});
|
|
66914
66913
|
};
|
|
66915
66914
|
const validateSurfaceControls = (surface, rawControls) => {
|
|
66916
|
-
if (!
|
|
66915
|
+
if (!isRecord(rawControls)) {
|
|
66917
66916
|
warnConfigIssue(`config field "surfaces.${surface}" must be an object (got ${typeof rawControls}); ignoring this surface.`);
|
|
66918
66917
|
return;
|
|
66919
66918
|
}
|
|
@@ -66937,7 +66936,7 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
66937
66936
|
return validatedSurfaceControls;
|
|
66938
66937
|
};
|
|
66939
66938
|
const validateSurfacesField = (rawSurfaces) => {
|
|
66940
|
-
if (!
|
|
66939
|
+
if (!isRecord(rawSurfaces)) {
|
|
66941
66940
|
warnConfigIssue(`config field "surfaces" must be an object (got ${typeof rawSurfaces}); ignoring this field.`);
|
|
66942
66941
|
return;
|
|
66943
66942
|
}
|
|
@@ -66953,7 +66952,7 @@ const validateSurfacesField = (rawSurfaces) => {
|
|
|
66953
66952
|
return validated;
|
|
66954
66953
|
};
|
|
66955
66954
|
const validateSeverityMap = (fieldName, rawMap, keysAreCategories = false) => {
|
|
66956
|
-
if (!
|
|
66955
|
+
if (!isRecord(rawMap)) {
|
|
66957
66956
|
warnConfigIssue(`config field "${fieldName}" must be an object (got ${typeof rawMap}); ignoring this field.`);
|
|
66958
66957
|
return;
|
|
66959
66958
|
}
|
|
@@ -69259,25 +69258,18 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
69259
69258
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
69260
69259
|
return patterns;
|
|
69261
69260
|
};
|
|
69262
|
-
const
|
|
69263
|
-
const KNIP_JSON_FILENAME = "knip.json";
|
|
69264
|
-
const readJsonFileSafe = (filePath) => {
|
|
69265
|
-
let rawContents;
|
|
69266
|
-
try {
|
|
69267
|
-
rawContents = fs.readFileSync(filePath, "utf-8");
|
|
69268
|
-
} catch {
|
|
69269
|
-
return null;
|
|
69270
|
-
}
|
|
69261
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
69271
69262
|
try {
|
|
69272
|
-
return JSON.parse(
|
|
69263
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
69273
69264
|
} catch {
|
|
69274
|
-
return
|
|
69265
|
+
return fallback;
|
|
69275
69266
|
}
|
|
69276
69267
|
};
|
|
69268
|
+
const KNIP_JSON_FILENAME = "knip.json";
|
|
69277
69269
|
const readKnipConfig = (rootDirectory) => {
|
|
69278
|
-
const knipJson =
|
|
69270
|
+
const knipJson = failOpenReadJson(path.join(rootDirectory, KNIP_JSON_FILENAME), null);
|
|
69279
69271
|
if (isRecord(knipJson)) return knipJson;
|
|
69280
|
-
const packageJson =
|
|
69272
|
+
const packageJson = failOpenReadJson(path.join(rootDirectory, "package.json"), null);
|
|
69281
69273
|
const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
|
|
69282
69274
|
return isRecord(packageKnipConfig) ? packageKnipConfig : null;
|
|
69283
69275
|
};
|
|
@@ -69325,13 +69317,6 @@ const atomicWriteJson = (filePath, value) => {
|
|
|
69325
69317
|
return;
|
|
69326
69318
|
}
|
|
69327
69319
|
};
|
|
69328
|
-
const failOpenReadJson = (filePath, fallback) => {
|
|
69329
|
-
try {
|
|
69330
|
-
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
69331
|
-
} catch {
|
|
69332
|
-
return fallback;
|
|
69333
|
-
}
|
|
69334
|
-
};
|
|
69335
69320
|
const hashFileContents = (filePath) => {
|
|
69336
69321
|
try {
|
|
69337
69322
|
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
@@ -71373,11 +71358,17 @@ const collectUnpluginAutoImportGlobalScopes = ({ rootDirectory, candidateFiles }
|
|
|
71373
71358
|
return scopes.some((scope) => scope.names.length > 0) ? scopes : [];
|
|
71374
71359
|
};
|
|
71375
71360
|
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
71361
|
+
const decodeDiagnostic = (raw) => {
|
|
71362
|
+
const diagnostic = validateDiagnostic$1(raw);
|
|
71363
|
+
return {
|
|
71364
|
+
...diagnostic,
|
|
71365
|
+
relatedLocations: diagnostic.relatedLocations?.map((location) => ({ ...location }))
|
|
71366
|
+
};
|
|
71367
|
+
};
|
|
71376
71368
|
const decodeFileDiagnostics = (raw) => {
|
|
71377
71369
|
if (!Array.isArray(raw)) return null;
|
|
71378
71370
|
try {
|
|
71379
|
-
|
|
71380
|
-
return raw;
|
|
71371
|
+
return raw.map((entry) => decodeDiagnostic(entry));
|
|
71381
71372
|
} catch {
|
|
71382
71373
|
return null;
|
|
71383
71374
|
}
|
|
@@ -74672,10 +74663,6 @@ const NO_HOOKS = {
|
|
|
74672
74663
|
beforeLint: () => void_,
|
|
74673
74664
|
afterLint: () => void_
|
|
74674
74665
|
};
|
|
74675
|
-
const filterMapNullable = (transform) => fromPredicateOption((value) => {
|
|
74676
|
-
const result = transform(value);
|
|
74677
|
-
return result === null ? none() : some(result);
|
|
74678
|
-
});
|
|
74679
74666
|
const fileReader = (filesService, rootDirectory) => (filePath) => {
|
|
74680
74667
|
const lines = runSync(filesService.readLines({
|
|
74681
74668
|
filePath,
|
|
@@ -74699,7 +74686,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
74699
74686
|
* The GitHub viewer-permission lookup is forked onto a background
|
|
74700
74687
|
* fiber here and joined late (it feeds score metadata, not
|
|
74701
74688
|
* diagnostics).
|
|
74702
|
-
* 2. beforeLint hook (e.g. CLI
|
|
74689
|
+
* 2. beforeLint hook (e.g. CLI records project telemetry)
|
|
74703
74690
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
74704
74691
|
* expo/react-native), collected synchronously. The heavier
|
|
74705
74692
|
* content-regex security scan is forked instead (like supply-chain
|
|
@@ -74712,13 +74699,11 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
74712
74699
|
* `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
|
|
74713
74700
|
* socket can't drag out its join; on timeout it fails open to no
|
|
74714
74701
|
* diagnostics — the same outcome class as a Socket outage.
|
|
74715
|
-
* 5. Linter.run runs; DeadCode.run
|
|
74716
|
-
*
|
|
74717
|
-
*
|
|
74718
|
-
*
|
|
74719
|
-
*
|
|
74720
|
-
* fiber is joined (or interrupted, SIGKILLing its worker, on lint
|
|
74721
|
-
* failure) before diagnostics are concatenated. The afterLint hook
|
|
74702
|
+
* 5. Linter.run runs; DeadCode.run forks only when
|
|
74703
|
+
* REACT_DOCTOR_DEAD_CODE_OVERLAP=on. The default "auto" and explicit
|
|
74704
|
+
* "off" paths stay sequential. The fiber is joined (or interrupted,
|
|
74705
|
+
* SIGKILLing its worker, on lint failure) before diagnostics are
|
|
74706
|
+
* concatenated. The afterLint hook
|
|
74722
74707
|
* fires between lint and dead-code. Progress spinner labels AND the
|
|
74723
74708
|
* final diagnostic / score order stay independent of execution
|
|
74724
74709
|
* order, so terminal output is identical either way; supply-chain
|
|
@@ -74794,7 +74779,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
74794
74779
|
const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
|
|
74795
74780
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
74796
74781
|
const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
|
|
74797
|
-
yield* beforeLint(project
|
|
74782
|
+
yield* beforeLint(project);
|
|
74798
74783
|
const isDiffMode = input.includePaths.length > 0;
|
|
74799
74784
|
const showWarnings = input.warnings ?? resolvedConfig.config?.warnings ?? true;
|
|
74800
74785
|
const transform = buildDiagnosticPipeline({
|
|
@@ -74804,7 +74789,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
74804
74789
|
respectInlineDisables: input.respectInlineDisables,
|
|
74805
74790
|
showWarnings
|
|
74806
74791
|
});
|
|
74807
|
-
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(
|
|
74792
|
+
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(fromPredicateOption((diagnostic) => {
|
|
74793
|
+
const filteredDiagnostic = transform.apply(diagnostic);
|
|
74794
|
+
return filteredDiagnostic === null ? none() : some(filteredDiagnostic);
|
|
74795
|
+
})));
|
|
74808
74796
|
const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
|
|
74809
74797
|
const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
|
|
74810
74798
|
...checkReducedMotion(scanDirectory),
|
|
@@ -75359,14 +75347,6 @@ const runEditorScan = async (input) => {
|
|
|
75359
75347
|
error: messageFromUnknown(error)
|
|
75360
75348
|
};
|
|
75361
75349
|
};
|
|
75362
|
-
/**
|
|
75363
|
-
* Back-compat alias: the streaming pipeline holds its caches in
|
|
75364
|
-
* per-pipeline closures that are garbage-collected when the pipeline
|
|
75365
|
-
* goes out of scope, so there is nothing to clear at module scope. The
|
|
75366
|
-
* public CLI's `clearCaches()` still calls this for symmetry with the
|
|
75367
|
-
* other `clear*` helpers.
|
|
75368
|
-
*/
|
|
75369
|
-
const clearAutoSuppressionCaches = () => {};
|
|
75370
75350
|
const lookupOwnRule = (rule) => Object.hasOwn(REACT_DOCTOR_RULE_REGISTRY, rule) ? REACT_DOCTOR_RULE_REGISTRY[rule] : void 0;
|
|
75371
75351
|
/**
|
|
75372
75352
|
* Returns presentation metadata for `<plugin>/<rule>`, or `null` when the
|
|
@@ -76034,10 +76014,7 @@ const createProjectGraph = (options) => {
|
|
|
76034
76014
|
for (const root of roots) try {
|
|
76035
76015
|
for (const workspacePackage of discoverReactSubprojects(root)) {
|
|
76036
76016
|
const directory = normalizeDirectory(workspacePackage.directory);
|
|
76037
|
-
if (!seen.has(directory)) seen.set(directory, {
|
|
76038
|
-
directory,
|
|
76039
|
-
name: workspacePackage.name
|
|
76040
|
-
});
|
|
76017
|
+
if (!seen.has(directory)) seen.set(directory, { directory });
|
|
76041
76018
|
}
|
|
76042
76019
|
} catch (error) {
|
|
76043
76020
|
logger.warn(`Project discovery failed for ${root}: ${messageFromUnknown(error)}`);
|
|
@@ -76062,8 +76039,8 @@ const createProjectGraph = (options) => {
|
|
|
76062
76039
|
clearProjectCache();
|
|
76063
76040
|
clearConfigCache();
|
|
76064
76041
|
clearPackageJsonCache();
|
|
76042
|
+
clearPackageRoleCache();
|
|
76065
76043
|
clearIgnorePatternsCache();
|
|
76066
|
-
clearAutoSuppressionCaches();
|
|
76067
76044
|
clearMinifiedFileCache();
|
|
76068
76045
|
projects = null;
|
|
76069
76046
|
}
|
|
@@ -76459,11 +76436,9 @@ const createScheduler = (options) => {
|
|
|
76459
76436
|
timers.clear();
|
|
76460
76437
|
queue.length = 0;
|
|
76461
76438
|
};
|
|
76462
|
-
const pendingCount = () => queue.length + running + timers.size;
|
|
76463
76439
|
return {
|
|
76464
76440
|
enqueue,
|
|
76465
76441
|
cancelProject,
|
|
76466
|
-
pendingCount,
|
|
76467
76442
|
dispose
|
|
76468
76443
|
};
|
|
76469
76444
|
};
|
|
@@ -77163,6 +77138,9 @@ const METRIC = {
|
|
|
77163
77138
|
*/
|
|
77164
77139
|
const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
77165
77140
|
//#endregion
|
|
77141
|
+
//#region src/cli/utils/is-env-flag-enabled.ts
|
|
77142
|
+
const isEnvFlagEnabled = (value) => value === "1" || value?.toLowerCase() === "true";
|
|
77143
|
+
//#endregion
|
|
77166
77144
|
//#region src/cli/utils/anonymize-text.ts
|
|
77167
77145
|
/**
|
|
77168
77146
|
* Free-text fields can carry both a home-directory path (the OS username) and a
|
|
@@ -77323,7 +77301,6 @@ const resolveTracesSampleRate = () => {
|
|
|
77323
77301
|
* session.
|
|
77324
77302
|
*/
|
|
77325
77303
|
let lspTracesSampleRate = 0;
|
|
77326
|
-
const isEnvFlagEnabled = (value) => value === "1" || value?.toLowerCase() === "true";
|
|
77327
77304
|
const nodeMajorVersion = () => Number.parseInt(process.versions.node.split(".", 1)[0] ?? "", 10) || 0;
|
|
77328
77305
|
const shouldEnableLspTelemetry = () => {
|
|
77329
77306
|
if (isEnvFlagEnabled(process.env.REACT_DOCTOR_NO_TELEMETRY)) return false;
|
|
@@ -77451,5 +77428,5 @@ const startLanguageServer = () => {
|
|
|
77451
77428
|
};
|
|
77452
77429
|
//#endregion
|
|
77453
77430
|
export { startLanguageServer };
|
|
77454
|
-
!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]="
|
|
77455
|
-
//# debugId=
|
|
77431
|
+
!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]="02495a55-39cb-51a9-9d93-04929a1ac660")}catch(e){}}();
|
|
77432
|
+
//# debugId=02495a55-39cb-51a9-9d93-04929a1ac660
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.9.3-dev.
|
|
3
|
+
"version": "0.9.3-dev.c825c59",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"vscode-uri": "^3.1.0",
|
|
74
74
|
"yaml": "^2.9.0",
|
|
75
75
|
"deslop-js": "0.9.3",
|
|
76
|
-
"oxlint-plugin-react-doctor": "0.9.3-dev.
|
|
76
|
+
"oxlint-plugin-react-doctor": "0.9.3-dev.c825c59"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"commander": "^14.0.3",
|
|
84
84
|
"ink-testing-library": "^4.0.0",
|
|
85
85
|
"ora": "^9.4.0",
|
|
86
|
+
"@react-doctor/api": "0.9.3",
|
|
86
87
|
"@react-doctor/core": "0.9.3",
|
|
87
|
-
"@react-doctor/language-server": "0.9.3"
|
|
88
|
-
"@react-doctor/api": "0.9.3"
|
|
88
|
+
"@react-doctor/language-server": "0.9.3"
|
|
89
89
|
},
|
|
90
90
|
"engines": {
|
|
91
91
|
"node": "^20.19.0 || >=22.13.0"
|