react-doctor 0.2.14-dev.6e59f10 → 0.2.14-dev.8b313ba
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 +46 -6
- package/dist/index.js +24 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -7726,6 +7726,28 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
7726
7726
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
7727
7727
|
return patterns;
|
|
7728
7728
|
};
|
|
7729
|
+
/**
|
|
7730
|
+
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
7731
|
+
* the input when it cannot be realpath'd (broken symlink, permission
|
|
7732
|
+
* error) so a best-effort normalization never throws.
|
|
7733
|
+
*
|
|
7734
|
+
* deslop's dead-code module graph is collected with `fast-glob` (which
|
|
7735
|
+
* keeps the scan root's symlinks intact) while imports are resolved
|
|
7736
|
+
* through `oxc-resolver` (which returns realpath'd targets). When the
|
|
7737
|
+
* project root sits behind a symlink — e.g. macOS iCloud-synced
|
|
7738
|
+
* `~/Documents` / `~/Desktop`, or a symlinked checkout — those two path
|
|
7739
|
+
* spaces diverge: every resolved import misses the graph and the files
|
|
7740
|
+
* they point at (commonly every `@/…` alias target) are mis-reported as
|
|
7741
|
+
* unreachable. Canonicalizing the root before the scan keeps both path
|
|
7742
|
+
* spaces in agreement.
|
|
7743
|
+
*/
|
|
7744
|
+
const toCanonicalPath = (filePath) => {
|
|
7745
|
+
try {
|
|
7746
|
+
return fs.realpathSync(filePath);
|
|
7747
|
+
} catch {
|
|
7748
|
+
return filePath;
|
|
7749
|
+
}
|
|
7750
|
+
};
|
|
7729
7751
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
7730
7752
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
7731
7753
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
@@ -7982,7 +8004,8 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
|
|
|
7982
8004
|
});
|
|
7983
8005
|
});
|
|
7984
8006
|
const checkDeadCode = async (options) => {
|
|
7985
|
-
const {
|
|
8007
|
+
const { userConfig } = options;
|
|
8008
|
+
const rootDirectory = toCanonicalPath(options.rootDirectory);
|
|
7986
8009
|
if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
|
|
7987
8010
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
|
|
7988
8011
|
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
@@ -10824,6 +10847,20 @@ const groupBy = (items, keyFn) => {
|
|
|
10824
10847
|
}
|
|
10825
10848
|
return groups;
|
|
10826
10849
|
};
|
|
10850
|
+
/**
|
|
10851
|
+
* Whether a diagnostic's rule has a published per-rule fix recipe at
|
|
10852
|
+
* `${PROMPTS_RULES_BASE_URL}/react-doctor/<rule>.md`
|
|
10853
|
+
* (see `buildRulePromptUrl`).
|
|
10854
|
+
*
|
|
10855
|
+
* Recipes are generated from react-doctor's own engine rules, so only
|
|
10856
|
+
* those resolve. Dead-code (`deslop`), the synthetic environment and
|
|
10857
|
+
* supply-chain checks (`require-reduced-motion`, `require-pnpm-hardening`
|
|
10858
|
+
* — `react-doctor`-namespaced but not engine rules), and adopted
|
|
10859
|
+
* third-party plugins (`eslint`, `unicorn`, `react-hooks-js`, …) have no
|
|
10860
|
+
* recipe, so advertising "fetch the fix recipe" for them sends agents to
|
|
10861
|
+
* a 404. Gate the directive on this predicate.
|
|
10862
|
+
*/
|
|
10863
|
+
const hasPublishedFixRecipe = (diagnostic) => diagnostic.plugin === "react-doctor" && Object.hasOwn(reactDoctorPlugin.rules, diagnostic.rule);
|
|
10827
10864
|
//#endregion
|
|
10828
10865
|
//#region ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
10829
10866
|
const ANSI_BACKGROUND_OFFSET = 10;
|
|
@@ -14012,7 +14049,7 @@ const compareByRulePriority = (ruleKeyA, ruleKeyB, rulePriority) => {
|
|
|
14012
14049
|
};
|
|
14013
14050
|
const sortRuleGroupsByImportance = (diagnosticGroups, rulePriority) => diagnosticGroups.toSorted(([ruleKeyA], [ruleKeyB]) => compareByRulePriority(ruleKeyA, ruleKeyB, rulePriority));
|
|
14014
14051
|
const FETCH_FIX_RECIPE_LABEL = "Fetch & follow the canonical fix recipe before fixing";
|
|
14015
|
-
const formatFixRecipeLine = (diagnostic) => `${FETCH_FIX_RECIPE_LABEL}: ${buildRulePromptUrl(diagnostic.plugin, diagnostic.rule)}
|
|
14052
|
+
const formatFixRecipeLine = (diagnostic) => hasPublishedFixRecipe(diagnostic) ? `${FETCH_FIX_RECIPE_LABEL}: ${buildRulePromptUrl(diagnostic.plugin, diagnostic.rule)}` : null;
|
|
14016
14053
|
//#endregion
|
|
14017
14054
|
//#region src/cli/utils/box-text.ts
|
|
14018
14055
|
const ESCAPE = String.fromCharCode(27);
|
|
@@ -14296,7 +14333,8 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
|
|
|
14296
14333
|
];
|
|
14297
14334
|
if (firstDiagnostic.help) sections.push("", `Suggestion: ${firstDiagnostic.help}`);
|
|
14298
14335
|
if (firstDiagnostic.url) sections.push("", `Docs: ${firstDiagnostic.url}`);
|
|
14299
|
-
|
|
14336
|
+
const fixRecipeLine = formatFixRecipeLine(firstDiagnostic);
|
|
14337
|
+
if (fixRecipeLine) sections.push("", fixRecipeLine);
|
|
14300
14338
|
sections.push("", "Files:");
|
|
14301
14339
|
const fileSites = buildVerboseSiteMap(ruleDiagnostics);
|
|
14302
14340
|
for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
|
|
@@ -14715,7 +14753,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
|
|
|
14715
14753
|
const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
|
|
14716
14754
|
//#endregion
|
|
14717
14755
|
//#region src/cli/utils/version.ts
|
|
14718
|
-
const VERSION = "0.2.14-dev.
|
|
14756
|
+
const VERSION = "0.2.14-dev.8b313ba";
|
|
14719
14757
|
//#endregion
|
|
14720
14758
|
//#region src/inspect.ts
|
|
14721
14759
|
const silentConsole = makeNoopConsole();
|
|
@@ -15029,7 +15067,9 @@ const buildHandoffPayload = (input) => {
|
|
|
15029
15067
|
topGroups.forEach(([ruleKey, ruleDiagnostics], index) => {
|
|
15030
15068
|
const representative = ruleDiagnostics[0];
|
|
15031
15069
|
const severityLabel = representative.severity === "error" ? "ERROR" : "WARN";
|
|
15032
|
-
lines.push(`${index + 1}. ${severityLabel} ${representative.category}: ${representative.title ?? ruleKey} (×${ruleDiagnostics.length})`, ` ${representative.message}
|
|
15070
|
+
lines.push(`${index + 1}. ${severityLabel} ${representative.category}: ${representative.title ?? ruleKey} (×${ruleDiagnostics.length})`, ` ${representative.message}`);
|
|
15071
|
+
const fixRecipeLine = formatFixRecipeLine(representative);
|
|
15072
|
+
if (fixRecipeLine) lines.push(` ${fixRecipeLine}`);
|
|
15033
15073
|
const uniqueFiles = [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.filePath))];
|
|
15034
15074
|
for (const filePath of uniqueFiles.slice(0, 3)) {
|
|
15035
15075
|
const firstSite = ruleDiagnostics.find((diagnostic) => diagnostic.filePath === filePath && diagnostic.line > 0);
|
|
@@ -15040,7 +15080,7 @@ const buildHandoffPayload = (input) => {
|
|
|
15040
15080
|
});
|
|
15041
15081
|
lines.push("");
|
|
15042
15082
|
if (diagnosticsDirectory) lines.push(`Full results for all ${input.diagnostics.length} issues (diagnostics.json + a .txt per rule): ${diagnosticsDirectory}`, "");
|
|
15043
|
-
lines.push("Read each file and fix the root cause — don't suppress.
|
|
15083
|
+
lines.push("Read each file and fix the root cause — don't suppress or silence the rule.", "", "Verify against the real thing, don't assume: confirm each change matches the canonical fix recipe you fetched for that rule, then re-run `npx react-doctor@latest --verbose` and check the issue is actually gone against the real tool before moving on.", "", "Teach me as you go: for every issue you touch, explain it in plain language (no jargon) — what the problem is, why it's a problem, and how serious it is in human terms. Describe the real-world impact and severity concretely (e.g. \"this crashes the page for users on Safari\" vs. \"this is a minor cleanup with no user impact\") so I understand why it matters, not just what changed.", "", "Then work through the rest from the full results above.");
|
|
15044
15084
|
return lines.join("\n");
|
|
15045
15085
|
};
|
|
15046
15086
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -4711,6 +4711,28 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
4711
4711
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
4712
4712
|
return patterns;
|
|
4713
4713
|
};
|
|
4714
|
+
/**
|
|
4715
|
+
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
4716
|
+
* the input when it cannot be realpath'd (broken symlink, permission
|
|
4717
|
+
* error) so a best-effort normalization never throws.
|
|
4718
|
+
*
|
|
4719
|
+
* deslop's dead-code module graph is collected with `fast-glob` (which
|
|
4720
|
+
* keeps the scan root's symlinks intact) while imports are resolved
|
|
4721
|
+
* through `oxc-resolver` (which returns realpath'd targets). When the
|
|
4722
|
+
* project root sits behind a symlink — e.g. macOS iCloud-synced
|
|
4723
|
+
* `~/Documents` / `~/Desktop`, or a symlinked checkout — those two path
|
|
4724
|
+
* spaces diverge: every resolved import misses the graph and the files
|
|
4725
|
+
* they point at (commonly every `@/…` alias target) are mis-reported as
|
|
4726
|
+
* unreachable. Canonicalizing the root before the scan keeps both path
|
|
4727
|
+
* spaces in agreement.
|
|
4728
|
+
*/
|
|
4729
|
+
const toCanonicalPath = (filePath) => {
|
|
4730
|
+
try {
|
|
4731
|
+
return fs.realpathSync(filePath);
|
|
4732
|
+
} catch {
|
|
4733
|
+
return filePath;
|
|
4734
|
+
}
|
|
4735
|
+
};
|
|
4714
4736
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
4715
4737
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
4716
4738
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
@@ -4967,7 +4989,8 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
|
|
|
4967
4989
|
});
|
|
4968
4990
|
});
|
|
4969
4991
|
const checkDeadCode = async (options) => {
|
|
4970
|
-
const {
|
|
4992
|
+
const { userConfig } = options;
|
|
4993
|
+
const rootDirectory = toCanonicalPath(options.rootDirectory);
|
|
4971
4994
|
if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
|
|
4972
4995
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
|
|
4973
4996
|
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.2.14-dev.
|
|
3
|
+
"version": "0.2.14-dev.8b313ba",
|
|
4
4
|
"description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"oxlint": "^1.66.0",
|
|
60
60
|
"prompts": "^2.4.2",
|
|
61
61
|
"typescript": ">=5.0.4 <7",
|
|
62
|
-
"oxlint-plugin-react-doctor": "0.2.14-dev.
|
|
62
|
+
"oxlint-plugin-react-doctor": "0.2.14-dev.8b313ba"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"@xterm/headless": "^6.0.0",
|
|
68
68
|
"commander": "^14.0.3",
|
|
69
69
|
"ora": "^9.4.0",
|
|
70
|
-
"@react-doctor/
|
|
71
|
-
"@react-doctor/
|
|
70
|
+
"@react-doctor/core": "0.2.14",
|
|
71
|
+
"@react-doctor/api": "0.2.14"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
74
74
|
"node": "^20.19.0 || >=22.12.0"
|