react-doctor 0.2.0 → 0.2.2
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/README.md +66 -13
- package/dist/chunk-q7NCDQ7-.js +26 -0
- package/dist/cli.js +7785 -1527
- package/dist/dist-2B-kn9PW.js +17940 -0
- package/dist/dist-BPzE37C6.js +17940 -0
- package/dist/index.d.ts +29 -3
- package/dist/index.js +508 -230
- package/package.json +10 -16
package/dist/index.js
CHANGED
|
@@ -1,33 +1,12 @@
|
|
|
1
|
+
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-q7NCDQ7-.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS,
|
|
6
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES } from "oxlint-plugin-react-doctor";
|
|
7
|
+
import { gzipSync } from "node:zlib";
|
|
6
8
|
import os from "node:os";
|
|
7
|
-
import * as ts from "typescript";
|
|
8
|
-
//#region \0rolldown/runtime.js
|
|
9
|
-
var __create$1 = Object.create;
|
|
10
|
-
var __defProp$1 = Object.defineProperty;
|
|
11
|
-
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
12
|
-
var __getOwnPropNames$1 = Object.getOwnPropertyNames;
|
|
13
|
-
var __getProtoOf$1 = Object.getPrototypeOf;
|
|
14
|
-
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
|
|
15
|
-
var __commonJSMin$1 = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
16
|
-
var __copyProps$1 = (to, from, except, desc) => {
|
|
17
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames$1(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
18
|
-
key = keys[i];
|
|
19
|
-
if (!__hasOwnProp$1.call(to, key) && key !== except) __defProp$1(to, key, {
|
|
20
|
-
get: ((k) => from[k]).bind(null, key),
|
|
21
|
-
enumerable: !(desc = __getOwnPropDesc$1(from, key)) || desc.enumerable
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return to;
|
|
25
|
-
};
|
|
26
|
-
var __toESM$1 = (mod, isNodeMode, target) => (target = mod != null ? __create$1(__getProtoOf$1(mod)) : {}, __copyProps$1(isNodeMode || !mod || !mod.__esModule ? __defProp$1(target, "default", {
|
|
27
|
-
value: mod,
|
|
28
|
-
enumerable: true
|
|
29
|
-
}) : target, mod));
|
|
30
|
-
//#endregion
|
|
9
|
+
import * as ts$1 from "typescript";
|
|
31
10
|
//#region ../types/dist/index.js
|
|
32
11
|
const REACT_NATIVE_DEPENDENCY_NAMES = new Set([
|
|
33
12
|
"react-native",
|
|
@@ -113,12 +92,31 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
113
92
|
"out",
|
|
114
93
|
"storybook-static"
|
|
115
94
|
]);
|
|
95
|
+
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
96
|
+
"EACCES",
|
|
97
|
+
"EPERM",
|
|
98
|
+
"ENOENT",
|
|
99
|
+
"ENOTDIR"
|
|
100
|
+
]);
|
|
101
|
+
const isIgnorableReaddirError = (error) => {
|
|
102
|
+
if (typeof error !== "object" || error === null) return false;
|
|
103
|
+
const errorCode = error.code;
|
|
104
|
+
return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
|
|
105
|
+
};
|
|
106
|
+
const readDirectoryEntries = (directoryPath) => {
|
|
107
|
+
try {
|
|
108
|
+
return fs.readdirSync(directoryPath, { withFileTypes: true });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (isIgnorableReaddirError(error)) return [];
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
116
114
|
const countSourceFilesViaFilesystem = (rootDirectory) => {
|
|
117
115
|
let count = 0;
|
|
118
116
|
const stack = [rootDirectory];
|
|
119
117
|
while (stack.length > 0) {
|
|
120
118
|
const currentDirectory = stack.pop();
|
|
121
|
-
const entries =
|
|
119
|
+
const entries = readDirectoryEntries(currentDirectory);
|
|
122
120
|
for (const entry of entries) {
|
|
123
121
|
if (entry.isDirectory()) {
|
|
124
122
|
if (!entry.name.startsWith(".") && !IGNORED_DIRECTORIES.has(entry.name)) stack.push(path.join(currentDirectory, entry.name));
|
|
@@ -156,7 +154,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
|
|
|
156
154
|
if (error instanceof SyntaxError) return {};
|
|
157
155
|
if (error instanceof Error && "code" in error) {
|
|
158
156
|
const { code } = error;
|
|
159
|
-
if (code === "EISDIR" || code === "EACCES") return {};
|
|
157
|
+
if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
|
|
160
158
|
}
|
|
161
159
|
throw error;
|
|
162
160
|
}
|
|
@@ -527,6 +525,13 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
527
525
|
version: null
|
|
528
526
|
};
|
|
529
527
|
};
|
|
528
|
+
const isDirectory = (directoryPath) => {
|
|
529
|
+
try {
|
|
530
|
+
return fs.statSync(directoryPath).isDirectory();
|
|
531
|
+
} catch {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
530
535
|
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
531
536
|
"apps",
|
|
532
537
|
"libs",
|
|
@@ -537,8 +542,8 @@ const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
|
537
542
|
const collected = [];
|
|
538
543
|
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
539
544
|
const candidatePath = path.join(rootDirectory, candidate);
|
|
540
|
-
if (!
|
|
541
|
-
for (const entry of
|
|
545
|
+
if (!isDirectory(candidatePath)) continue;
|
|
546
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
542
547
|
if (!entry.isDirectory()) continue;
|
|
543
548
|
const projectDirectory = path.join(candidatePath, entry.name);
|
|
544
549
|
if (isFile(path.join(projectDirectory, "project.json")) || isFile(path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
@@ -580,17 +585,17 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
|
580
585
|
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
581
586
|
if (!cleanPattern.includes("*")) {
|
|
582
587
|
const directoryPath = path.join(rootDirectory, cleanPattern);
|
|
583
|
-
if (
|
|
588
|
+
if (isDirectory(directoryPath) && isFile(path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
584
589
|
return [];
|
|
585
590
|
}
|
|
586
591
|
const wildcardIndex = cleanPattern.indexOf("*");
|
|
587
592
|
const baseDirectory = path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
588
593
|
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
589
|
-
if (!
|
|
594
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
590
595
|
const resolved = [];
|
|
591
|
-
for (const entry of
|
|
592
|
-
const entryPath = path.join(baseDirectory, entry, suffixAfterWildcard);
|
|
593
|
-
if (
|
|
596
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
597
|
+
const entryPath = path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
598
|
+
if (isDirectory(entryPath) && isFile(path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
594
599
|
}
|
|
595
600
|
return resolved;
|
|
596
601
|
};
|
|
@@ -654,19 +659,6 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
654
659
|
}
|
|
655
660
|
return result;
|
|
656
661
|
};
|
|
657
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
658
|
-
"react",
|
|
659
|
-
"react-native",
|
|
660
|
-
"next"
|
|
661
|
-
]);
|
|
662
|
-
const hasReactDependency = (packageJson) => {
|
|
663
|
-
const allDependencies = {
|
|
664
|
-
...packageJson.peerDependencies,
|
|
665
|
-
...packageJson.dependencies,
|
|
666
|
-
...packageJson.devDependencies
|
|
667
|
-
};
|
|
668
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
669
|
-
};
|
|
670
662
|
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
671
663
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
672
664
|
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
@@ -694,13 +686,13 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
694
686
|
"peerDependencies"
|
|
695
687
|
]
|
|
696
688
|
}) : null;
|
|
697
|
-
const shouldUseReactFallback =
|
|
689
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
698
690
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
699
691
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
700
692
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
701
693
|
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
702
694
|
return {
|
|
703
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion :
|
|
695
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
704
696
|
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
705
697
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
706
698
|
};
|
|
@@ -767,6 +759,19 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
|
767
759
|
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
768
760
|
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
769
761
|
};
|
|
762
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
763
|
+
"react",
|
|
764
|
+
"react-native",
|
|
765
|
+
"next"
|
|
766
|
+
]);
|
|
767
|
+
const hasReactDependency = (packageJson) => {
|
|
768
|
+
const allDependencies = {
|
|
769
|
+
...packageJson.peerDependencies,
|
|
770
|
+
...packageJson.dependencies,
|
|
771
|
+
...packageJson.devDependencies
|
|
772
|
+
};
|
|
773
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
774
|
+
};
|
|
770
775
|
const listWorkspacePackages = (rootDirectory) => {
|
|
771
776
|
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
772
777
|
if (!isFile(packageJsonPath)) return [];
|
|
@@ -835,7 +840,7 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
|
835
840
|
});
|
|
836
841
|
}
|
|
837
842
|
}
|
|
838
|
-
const entries =
|
|
843
|
+
const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
|
|
839
844
|
for (const entry of entries) {
|
|
840
845
|
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
841
846
|
pendingDirectories.push(path.join(currentDirectory, entry.name));
|
|
@@ -844,7 +849,7 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
|
844
849
|
return packages;
|
|
845
850
|
};
|
|
846
851
|
const discoverReactSubprojects = (rootDirectory) => {
|
|
847
|
-
if (!
|
|
852
|
+
if (!isDirectory(rootDirectory)) return [];
|
|
848
853
|
const manifestPackages = listManifestWorkspacePackages(rootDirectory);
|
|
849
854
|
if (manifestPackages.length > 0) return manifestPackages;
|
|
850
855
|
return discoverReactSubprojectsByFilesystem(rootDirectory);
|
|
@@ -2986,6 +2991,136 @@ const getDiagnosticRuleIdentity = (diagnostic) => ({
|
|
|
2986
2991
|
category: diagnostic.category,
|
|
2987
2992
|
tags: diagnostic.plugin === "react-doctor" ? reactDoctorPlugin.rules[diagnostic.rule]?.tags ?? [] : []
|
|
2988
2993
|
});
|
|
2994
|
+
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
2995
|
+
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
2996
|
+
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
2997
|
+
"effect/no-derived-state": "react-doctor/no-derived-state",
|
|
2998
|
+
"effect/no-event-handler": "react-doctor/no-event-handler",
|
|
2999
|
+
"effect/no-initialize-state": "react-doctor/no-initialize-state",
|
|
3000
|
+
"effect/no-pass-data-to-parent": "react-doctor/no-pass-data-to-parent",
|
|
3001
|
+
"effect/no-pass-live-state-to-parent": "react-doctor/no-pass-live-state-to-parent",
|
|
3002
|
+
"effect/no-reset-all-state-on-prop-change": "react-doctor/no-reset-all-state-on-prop-change",
|
|
3003
|
+
"jsx-a11y/alt-text": "react-doctor/alt-text",
|
|
3004
|
+
"jsx-a11y/anchor-ambiguous-text": "react-doctor/anchor-ambiguous-text",
|
|
3005
|
+
"jsx-a11y/anchor-has-content": "react-doctor/anchor-has-content",
|
|
3006
|
+
"jsx-a11y/anchor-is-valid": "react-doctor/anchor-is-valid",
|
|
3007
|
+
"jsx-a11y/aria-activedescendant-has-tabindex": "react-doctor/aria-activedescendant-has-tabindex",
|
|
3008
|
+
"jsx-a11y/aria-props": "react-doctor/aria-props",
|
|
3009
|
+
"jsx-a11y/aria-proptypes": "react-doctor/aria-proptypes",
|
|
3010
|
+
"jsx-a11y/aria-role": "react-doctor/aria-role",
|
|
3011
|
+
"jsx-a11y/aria-unsupported-elements": "react-doctor/aria-unsupported-elements",
|
|
3012
|
+
"jsx-a11y/autocomplete-valid": "react-doctor/autocomplete-valid",
|
|
3013
|
+
"jsx-a11y/click-events-have-key-events": "react-doctor/click-events-have-key-events",
|
|
3014
|
+
"jsx-a11y/control-has-associated-label": "react-doctor/control-has-associated-label",
|
|
3015
|
+
"jsx-a11y/heading-has-content": "react-doctor/heading-has-content",
|
|
3016
|
+
"jsx-a11y/html-has-lang": "react-doctor/html-has-lang",
|
|
3017
|
+
"jsx-a11y/iframe-has-title": "react-doctor/iframe-has-title",
|
|
3018
|
+
"jsx-a11y/img-redundant-alt": "react-doctor/img-redundant-alt",
|
|
3019
|
+
"jsx-a11y/interactive-supports-focus": "react-doctor/interactive-supports-focus",
|
|
3020
|
+
"jsx-a11y/label-has-associated-control": "react-doctor/label-has-associated-control",
|
|
3021
|
+
"jsx-a11y/lang": "react-doctor/lang",
|
|
3022
|
+
"jsx-a11y/media-has-caption": "react-doctor/media-has-caption",
|
|
3023
|
+
"jsx-a11y/mouse-events-have-key-events": "react-doctor/mouse-events-have-key-events",
|
|
3024
|
+
"jsx-a11y/no-access-key": "react-doctor/no-access-key",
|
|
3025
|
+
"jsx-a11y/no-aria-hidden-on-focusable": "react-doctor/no-aria-hidden-on-focusable",
|
|
3026
|
+
"jsx-a11y/no-autofocus": "react-doctor/no-autofocus",
|
|
3027
|
+
"jsx-a11y/no-distracting-elements": "react-doctor/no-distracting-elements",
|
|
3028
|
+
"jsx-a11y/no-interactive-element-to-noninteractive-role": "react-doctor/no-interactive-element-to-noninteractive-role",
|
|
3029
|
+
"jsx-a11y/no-noninteractive-element-interactions": "react-doctor/no-noninteractive-element-interactions",
|
|
3030
|
+
"jsx-a11y/no-noninteractive-element-to-interactive-role": "react-doctor/no-noninteractive-element-to-interactive-role",
|
|
3031
|
+
"jsx-a11y/no-noninteractive-tabindex": "react-doctor/no-noninteractive-tabindex",
|
|
3032
|
+
"jsx-a11y/no-redundant-roles": "react-doctor/no-redundant-roles",
|
|
3033
|
+
"jsx-a11y/no-static-element-interactions": "react-doctor/no-static-element-interactions",
|
|
3034
|
+
"jsx-a11y/prefer-tag-over-role": "react-doctor/prefer-tag-over-role",
|
|
3035
|
+
"jsx-a11y/role-has-required-aria-props": "react-doctor/role-has-required-aria-props",
|
|
3036
|
+
"jsx-a11y/role-supports-aria-props": "react-doctor/role-supports-aria-props",
|
|
3037
|
+
"jsx-a11y/scope": "react-doctor/scope",
|
|
3038
|
+
"jsx-a11y/tabindex-no-positive": "react-doctor/tabindex-no-positive",
|
|
3039
|
+
"react-hooks/exhaustive-deps": "react-doctor/exhaustive-deps",
|
|
3040
|
+
"react-hooks/rules-of-hooks": "react-doctor/rules-of-hooks",
|
|
3041
|
+
"react-perf/jsx-no-jsx-as-prop": "react-doctor/jsx-no-jsx-as-prop",
|
|
3042
|
+
"react-perf/jsx-no-new-array-as-prop": "react-doctor/jsx-no-new-array-as-prop",
|
|
3043
|
+
"react-perf/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
3044
|
+
"react-perf/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
3045
|
+
"react-refresh/only-export-components": "react-doctor/only-export-components",
|
|
3046
|
+
"react/button-has-type": "react-doctor/button-has-type",
|
|
3047
|
+
"react/checked-requires-onchange-or-readonly": "react-doctor/checked-requires-onchange-or-readonly",
|
|
3048
|
+
"react/display-name": "react-doctor/display-name",
|
|
3049
|
+
"react/exhaustive-deps": "react-doctor/exhaustive-deps",
|
|
3050
|
+
"react/forbid-component-props": "react-doctor/forbid-component-props",
|
|
3051
|
+
"react/forbid-dom-props": "react-doctor/forbid-dom-props",
|
|
3052
|
+
"react/forbid-elements": "react-doctor/forbid-elements",
|
|
3053
|
+
"react/forward-ref-uses-ref": "react-doctor/forward-ref-uses-ref",
|
|
3054
|
+
"react/hook-use-state": "react-doctor/hook-use-state",
|
|
3055
|
+
"react/iframe-missing-sandbox": "react-doctor/iframe-missing-sandbox",
|
|
3056
|
+
"react/jsx-boolean-value": "react-doctor/jsx-boolean-value",
|
|
3057
|
+
"react/jsx-curly-brace-presence": "react-doctor/jsx-curly-brace-presence",
|
|
3058
|
+
"react/jsx-filename-extension": "react-doctor/jsx-filename-extension",
|
|
3059
|
+
"react/jsx-fragments": "react-doctor/jsx-fragments",
|
|
3060
|
+
"react/jsx-handler-names": "react-doctor/jsx-handler-names",
|
|
3061
|
+
"react/jsx-key": "react-doctor/jsx-key",
|
|
3062
|
+
"react/jsx-max-depth": "react-doctor/jsx-max-depth",
|
|
3063
|
+
"react/jsx-no-comment-textnodes": "react-doctor/jsx-no-comment-textnodes",
|
|
3064
|
+
"react/jsx-no-constructed-context-values": "react-doctor/jsx-no-constructed-context-values",
|
|
3065
|
+
"react/jsx-no-duplicate-props": "react-doctor/jsx-no-duplicate-props",
|
|
3066
|
+
"react/jsx-no-jsx-as-prop": "react-doctor/jsx-no-jsx-as-prop",
|
|
3067
|
+
"react/jsx-no-new-array-as-prop": "react-doctor/jsx-no-new-array-as-prop",
|
|
3068
|
+
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
3069
|
+
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
3070
|
+
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
3071
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
3072
|
+
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
3073
|
+
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
3074
|
+
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
3075
|
+
"react/jsx-props-no-spread-multi": "react-doctor/jsx-props-no-spread-multi",
|
|
3076
|
+
"react/jsx-props-no-spreading": "react-doctor/jsx-props-no-spreading",
|
|
3077
|
+
"react/no-array-index-key": "react-doctor/no-array-index-key",
|
|
3078
|
+
"react/no-children-prop": "react-doctor/no-children-prop",
|
|
3079
|
+
"react/no-clone-element": "react-doctor/no-clone-element",
|
|
3080
|
+
"react/no-danger": "react-doctor/no-danger",
|
|
3081
|
+
"react/no-danger-with-children": "react-doctor/no-danger-with-children",
|
|
3082
|
+
"react/no-did-mount-set-state": "react-doctor/no-did-mount-set-state",
|
|
3083
|
+
"react/no-did-update-set-state": "react-doctor/no-did-update-set-state",
|
|
3084
|
+
"react/no-direct-mutation-state": "react-doctor/no-direct-mutation-state",
|
|
3085
|
+
"react/no-find-dom-node": "react-doctor/no-find-dom-node",
|
|
3086
|
+
"react/no-is-mounted": "react-doctor/no-is-mounted",
|
|
3087
|
+
"react/no-multi-comp": "react-doctor/no-multi-comp",
|
|
3088
|
+
"react/no-namespace": "react-doctor/no-namespace",
|
|
3089
|
+
"react/no-react-children": "react-doctor/no-react-children",
|
|
3090
|
+
"react/no-redundant-should-component-update": "react-doctor/no-redundant-should-component-update",
|
|
3091
|
+
"react/no-render-return-value": "react-doctor/no-render-return-value",
|
|
3092
|
+
"react/no-set-state": "react-doctor/no-set-state",
|
|
3093
|
+
"react/no-string-refs": "react-doctor/no-string-refs",
|
|
3094
|
+
"react/no-this-in-sfc": "react-doctor/no-this-in-sfc",
|
|
3095
|
+
"react/no-unescaped-entities": "react-doctor/no-unescaped-entities",
|
|
3096
|
+
"react/no-unknown-property": "react-doctor/no-unknown-property",
|
|
3097
|
+
"react/no-unsafe": "react-doctor/no-unsafe",
|
|
3098
|
+
"react/no-unstable-nested-components": "react-doctor/no-unstable-nested-components",
|
|
3099
|
+
"react/no-will-update-set-state": "react-doctor/no-will-update-set-state",
|
|
3100
|
+
"react/only-export-components": "react-doctor/only-export-components",
|
|
3101
|
+
"react/prefer-es6-class": "react-doctor/prefer-es6-class",
|
|
3102
|
+
"react/prefer-function-component": "react-doctor/prefer-function-component",
|
|
3103
|
+
"react/react-in-jsx-scope": "react-doctor/react-in-jsx-scope",
|
|
3104
|
+
"react/require-render-return": "react-doctor/require-render-return",
|
|
3105
|
+
"react/rules-of-hooks": "react-doctor/rules-of-hooks",
|
|
3106
|
+
"react/self-closing-comp": "react-doctor/self-closing-comp",
|
|
3107
|
+
"react/state-in-constructor": "react-doctor/state-in-constructor",
|
|
3108
|
+
"react/style-prop-object": "react-doctor/style-prop-object",
|
|
3109
|
+
"react/void-dom-elements-no-children": "react-doctor/void-dom-elements-no-children"
|
|
3110
|
+
};
|
|
3111
|
+
const NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS = /* @__PURE__ */ new Map();
|
|
3112
|
+
for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY)) {
|
|
3113
|
+
const aliases = NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(nativeRuleKey) ?? [];
|
|
3114
|
+
aliases.push(legacyRuleKey);
|
|
3115
|
+
NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
|
|
3116
|
+
}
|
|
3117
|
+
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
3118
|
+
const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
|
|
3119
|
+
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => canonicalizeRuleKey(candidateRuleKey) === canonicalizeRuleKey(targetRuleKey);
|
|
3120
|
+
const getEquivalentRuleKeys = (ruleKey) => {
|
|
3121
|
+
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
3122
|
+
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
3123
|
+
};
|
|
2989
3124
|
/**
|
|
2990
3125
|
* Resolves the user-configured severity override for a rule.
|
|
2991
3126
|
* Per-rule overrides win over per-category overrides. Returns
|
|
@@ -2994,7 +3129,14 @@ const getDiagnosticRuleIdentity = (diagnostic) => ({
|
|
|
2994
3129
|
*/
|
|
2995
3130
|
const resolveRuleSeverityOverride = (input, controls) => {
|
|
2996
3131
|
if (!controls) return void 0;
|
|
2997
|
-
|
|
3132
|
+
const exactRuleOverride = controls.rules?.[input.ruleKey];
|
|
3133
|
+
if (exactRuleOverride !== void 0) return exactRuleOverride;
|
|
3134
|
+
for (const equivalentRuleKey of getEquivalentRuleKeys(input.ruleKey)) {
|
|
3135
|
+
if (equivalentRuleKey === input.ruleKey) continue;
|
|
3136
|
+
const equivalentRuleOverride = controls.rules?.[equivalentRuleKey];
|
|
3137
|
+
if (equivalentRuleOverride !== void 0) return equivalentRuleOverride;
|
|
3138
|
+
}
|
|
3139
|
+
return input.category !== void 0 ? controls.categories?.[input.category] : void 0;
|
|
2998
3140
|
};
|
|
2999
3141
|
const SEVERITY_FOR_OVERRIDE = {
|
|
3000
3142
|
error: "error",
|
|
@@ -3203,14 +3345,19 @@ const describeFailure = (error) => {
|
|
|
3203
3345
|
if (error instanceof Error && error.message) return error.message;
|
|
3204
3346
|
return String(error);
|
|
3205
3347
|
};
|
|
3206
|
-
const calculateScore = async (diagnostics) => {
|
|
3348
|
+
const calculateScore = async (diagnostics, options = {}) => {
|
|
3207
3349
|
const controller = new AbortController();
|
|
3208
3350
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
3351
|
+
const requestUrl = options.isCi ? `${SCORE_API_URL}?ci=1` : SCORE_API_URL;
|
|
3209
3352
|
try {
|
|
3210
|
-
const
|
|
3353
|
+
const compressedBody = gzipSync(JSON.stringify({ diagnostics: stripFilePaths(diagnostics) }));
|
|
3354
|
+
const response = await fetch(requestUrl, {
|
|
3211
3355
|
method: "POST",
|
|
3212
|
-
headers: {
|
|
3213
|
-
|
|
3356
|
+
headers: {
|
|
3357
|
+
"Content-Type": "application/json",
|
|
3358
|
+
"Content-Encoding": "gzip"
|
|
3359
|
+
},
|
|
3360
|
+
body: compressedBody,
|
|
3214
3361
|
signal: controller.signal
|
|
3215
3362
|
});
|
|
3216
3363
|
if (!response.ok) {
|
|
@@ -3297,60 +3444,6 @@ const canOxlintExtendConfig = (configPath) => {
|
|
|
3297
3444
|
if (extendsEntries.length === 0) return true;
|
|
3298
3445
|
return extendsEntries.some((entry) => typeof entry === "string" && isLocalPathExtend(entry));
|
|
3299
3446
|
};
|
|
3300
|
-
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
3301
|
-
const REDUCED_MOTION_FILE_GLOBS = [
|
|
3302
|
-
"*.ts",
|
|
3303
|
-
"*.tsx",
|
|
3304
|
-
"*.js",
|
|
3305
|
-
"*.jsx",
|
|
3306
|
-
"*.css",
|
|
3307
|
-
"*.scss"
|
|
3308
|
-
];
|
|
3309
|
-
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
3310
|
-
filePath: "package.json",
|
|
3311
|
-
plugin: "react-doctor",
|
|
3312
|
-
rule: "require-reduced-motion",
|
|
3313
|
-
severity: "error",
|
|
3314
|
-
message: "Project uses a motion library but has no prefers-reduced-motion handling — required for accessibility (WCAG 2.3.3)",
|
|
3315
|
-
help: "Add `useReducedMotion()` from your animation library, or a `@media (prefers-reduced-motion: reduce)` CSS query",
|
|
3316
|
-
line: 0,
|
|
3317
|
-
column: 0,
|
|
3318
|
-
category: "Accessibility"
|
|
3319
|
-
};
|
|
3320
|
-
const checkReducedMotion = (rootDirectory) => {
|
|
3321
|
-
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
3322
|
-
if (!isFile(packageJsonPath)) return [];
|
|
3323
|
-
let hasMotionLibrary = false;
|
|
3324
|
-
try {
|
|
3325
|
-
const packageJson = readPackageJson(packageJsonPath);
|
|
3326
|
-
const allDependencies = {
|
|
3327
|
-
...packageJson.dependencies,
|
|
3328
|
-
...packageJson.devDependencies
|
|
3329
|
-
};
|
|
3330
|
-
hasMotionLibrary = Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName));
|
|
3331
|
-
} catch {
|
|
3332
|
-
return [];
|
|
3333
|
-
}
|
|
3334
|
-
if (!hasMotionLibrary) return [];
|
|
3335
|
-
const result = spawnSync("git", [
|
|
3336
|
-
"grep",
|
|
3337
|
-
"-ql",
|
|
3338
|
-
"-E",
|
|
3339
|
-
REDUCED_MOTION_GREP_PATTERN,
|
|
3340
|
-
"--",
|
|
3341
|
-
...REDUCED_MOTION_FILE_GLOBS
|
|
3342
|
-
], {
|
|
3343
|
-
cwd: rootDirectory,
|
|
3344
|
-
stdio: [
|
|
3345
|
-
"ignore",
|
|
3346
|
-
"pipe",
|
|
3347
|
-
"pipe"
|
|
3348
|
-
]
|
|
3349
|
-
});
|
|
3350
|
-
if (result.error) return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
3351
|
-
if (result.status === 0) return [];
|
|
3352
|
-
return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
3353
|
-
};
|
|
3354
3447
|
const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
|
|
3355
3448
|
const FALSY_VALUES = new Set([
|
|
3356
3449
|
"false",
|
|
@@ -3530,6 +3623,148 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
3530
3623
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
3531
3624
|
return patterns;
|
|
3532
3625
|
};
|
|
3626
|
+
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
3627
|
+
const resolveTsConfigPath = (rootDirectory) => {
|
|
3628
|
+
for (const filename of TSCONFIG_FILENAMES$1) {
|
|
3629
|
+
const candidate = path.join(rootDirectory, filename);
|
|
3630
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
3631
|
+
}
|
|
3632
|
+
};
|
|
3633
|
+
const collectDeadCodeIgnorePatterns = (rootDirectory, userConfig) => {
|
|
3634
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3635
|
+
const sources = [
|
|
3636
|
+
readIgnoreFile(path.join(rootDirectory, ".gitignore")),
|
|
3637
|
+
collectIgnorePatterns(rootDirectory),
|
|
3638
|
+
userConfig?.ignore?.files ?? []
|
|
3639
|
+
];
|
|
3640
|
+
for (const source of sources) for (const pattern of source) seen.add(pattern);
|
|
3641
|
+
return [...seen].filter((pattern) => pattern.length > 0);
|
|
3642
|
+
};
|
|
3643
|
+
const toRelativeFilePath = (rootDirectory, filePath) => {
|
|
3644
|
+
const relative = toRelativePath(filePath, rootDirectory);
|
|
3645
|
+
return relative.length > 0 ? relative : filePath.replace(/\\/g, "/");
|
|
3646
|
+
};
|
|
3647
|
+
const checkDeadCode = async (options) => {
|
|
3648
|
+
const { rootDirectory, userConfig } = options;
|
|
3649
|
+
if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
|
|
3650
|
+
const { analyze, defineConfig } = await import("./dist-BPzE37C6.js");
|
|
3651
|
+
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
|
|
3652
|
+
const result = await analyze(defineConfig({
|
|
3653
|
+
rootDir: rootDirectory,
|
|
3654
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
3655
|
+
...ignorePatterns.length > 0 ? { ignorePatterns } : {}
|
|
3656
|
+
}));
|
|
3657
|
+
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
3658
|
+
const diagnostics = [];
|
|
3659
|
+
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
3660
|
+
filePath: toRelative(unusedFile.path),
|
|
3661
|
+
plugin: "deslop",
|
|
3662
|
+
rule: "unused-file",
|
|
3663
|
+
severity: "warning",
|
|
3664
|
+
message: "Unused file — not reachable from any entry point",
|
|
3665
|
+
help: "Delete the file if it is truly unreachable, or import it from an entry point.",
|
|
3666
|
+
line: 0,
|
|
3667
|
+
column: 0,
|
|
3668
|
+
category: "Dead Code"
|
|
3669
|
+
});
|
|
3670
|
+
for (const unusedExport of result.unusedExports) {
|
|
3671
|
+
const label = unusedExport.isTypeOnly ? "type export" : "export";
|
|
3672
|
+
diagnostics.push({
|
|
3673
|
+
filePath: toRelative(unusedExport.path),
|
|
3674
|
+
plugin: "deslop",
|
|
3675
|
+
rule: unusedExport.isTypeOnly ? "unused-type" : "unused-export",
|
|
3676
|
+
severity: "warning",
|
|
3677
|
+
message: `Unused ${label}: \`${unusedExport.name}\``,
|
|
3678
|
+
help: "Drop the `export` keyword (or remove the declaration) if no other module uses this symbol.",
|
|
3679
|
+
line: unusedExport.line,
|
|
3680
|
+
column: unusedExport.column,
|
|
3681
|
+
category: "Dead Code"
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
for (const unusedDependency of result.unusedDependencies) {
|
|
3685
|
+
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
3686
|
+
diagnostics.push({
|
|
3687
|
+
filePath: "package.json",
|
|
3688
|
+
plugin: "deslop",
|
|
3689
|
+
rule: unusedDependency.isDevDependency ? "unused-dev-dependency" : "unused-dependency",
|
|
3690
|
+
severity: "warning",
|
|
3691
|
+
message: `Unused ${label}: \`${unusedDependency.name}\``,
|
|
3692
|
+
help: "Remove the dependency from package.json if it is genuinely unused.",
|
|
3693
|
+
line: 0,
|
|
3694
|
+
column: 0,
|
|
3695
|
+
category: "Dead Code"
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
for (const cycle of result.circularDependencies) {
|
|
3699
|
+
if (cycle.files.length === 0) continue;
|
|
3700
|
+
diagnostics.push({
|
|
3701
|
+
filePath: toRelative(cycle.files[0]),
|
|
3702
|
+
plugin: "deslop",
|
|
3703
|
+
rule: "circular-dependency",
|
|
3704
|
+
severity: "warning",
|
|
3705
|
+
message: `Circular import cycle: ${cycle.files.map(toRelative).join(" → ")}`,
|
|
3706
|
+
help: "Break the cycle by extracting the shared code into a third module that both files import.",
|
|
3707
|
+
line: 0,
|
|
3708
|
+
column: 0,
|
|
3709
|
+
category: "Dead Code"
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3712
|
+
return diagnostics;
|
|
3713
|
+
};
|
|
3714
|
+
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
3715
|
+
const REDUCED_MOTION_FILE_GLOBS = [
|
|
3716
|
+
"*.ts",
|
|
3717
|
+
"*.tsx",
|
|
3718
|
+
"*.js",
|
|
3719
|
+
"*.jsx",
|
|
3720
|
+
"*.css",
|
|
3721
|
+
"*.scss"
|
|
3722
|
+
];
|
|
3723
|
+
const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
|
|
3724
|
+
filePath: "package.json",
|
|
3725
|
+
plugin: "react-doctor",
|
|
3726
|
+
rule: "require-reduced-motion",
|
|
3727
|
+
severity: "error",
|
|
3728
|
+
message: "Project uses a motion library but has no prefers-reduced-motion handling — required for accessibility (WCAG 2.3.3)",
|
|
3729
|
+
help: "Add `useReducedMotion()` from your animation library, or a `@media (prefers-reduced-motion: reduce)` CSS query",
|
|
3730
|
+
line: 0,
|
|
3731
|
+
column: 0,
|
|
3732
|
+
category: "Accessibility"
|
|
3733
|
+
};
|
|
3734
|
+
const checkReducedMotion = (rootDirectory) => {
|
|
3735
|
+
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
3736
|
+
if (!isFile(packageJsonPath)) return [];
|
|
3737
|
+
let hasMotionLibrary = false;
|
|
3738
|
+
try {
|
|
3739
|
+
const packageJson = readPackageJson(packageJsonPath);
|
|
3740
|
+
const allDependencies = {
|
|
3741
|
+
...packageJson.dependencies,
|
|
3742
|
+
...packageJson.devDependencies
|
|
3743
|
+
};
|
|
3744
|
+
hasMotionLibrary = Object.keys(allDependencies).some((packageName) => MOTION_LIBRARY_PACKAGES.has(packageName));
|
|
3745
|
+
} catch {
|
|
3746
|
+
return [];
|
|
3747
|
+
}
|
|
3748
|
+
if (!hasMotionLibrary) return [];
|
|
3749
|
+
const result = spawnSync("git", [
|
|
3750
|
+
"grep",
|
|
3751
|
+
"-ql",
|
|
3752
|
+
"-E",
|
|
3753
|
+
REDUCED_MOTION_GREP_PATTERN,
|
|
3754
|
+
"--",
|
|
3755
|
+
...REDUCED_MOTION_FILE_GLOBS
|
|
3756
|
+
], {
|
|
3757
|
+
cwd: rootDirectory,
|
|
3758
|
+
stdio: [
|
|
3759
|
+
"ignore",
|
|
3760
|
+
"pipe",
|
|
3761
|
+
"pipe"
|
|
3762
|
+
]
|
|
3763
|
+
});
|
|
3764
|
+
if (result.error) return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
3765
|
+
if (result.status === 0) return [];
|
|
3766
|
+
return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
3767
|
+
};
|
|
3533
3768
|
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
3534
3769
|
return (filePath) => {
|
|
3535
3770
|
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(rootDirectory, filePath);
|
|
@@ -3659,7 +3894,7 @@ const isRuleListedInComment = (ruleList, ruleId) => {
|
|
|
3659
3894
|
if (!trimmed) return true;
|
|
3660
3895
|
const ruleSection = stripDescriptionTail(trimmed).trim();
|
|
3661
3896
|
if (!ruleSection) return true;
|
|
3662
|
-
return ruleSection.split(/[,\s]+/).some((token) => token.trim()
|
|
3897
|
+
return ruleSection.split(/[,\s]+/).some((token) => isSameRuleKey(token.trim(), ruleId));
|
|
3663
3898
|
};
|
|
3664
3899
|
const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
3665
3900
|
const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
|
|
@@ -3811,6 +4046,10 @@ const isInsideStringOnlyWrapper = (lines, diagnosticLine, diagnosticColumn, wrap
|
|
|
3811
4046
|
}
|
|
3812
4047
|
return false;
|
|
3813
4048
|
};
|
|
4049
|
+
const isIgnoredRule = (ignoredRules, ruleIdentifier) => {
|
|
4050
|
+
for (const ignoredRule of ignoredRules) if (isSameRuleKey(ignoredRule, ruleIdentifier)) return true;
|
|
4051
|
+
return false;
|
|
4052
|
+
};
|
|
3814
4053
|
const filterIgnoredDiagnostics = (diagnostics, config, rootDirectory, readFileLinesSync) => {
|
|
3815
4054
|
const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules.filter((rule) => typeof rule === "string") : []);
|
|
3816
4055
|
const ignoredFilePatterns = compileIgnoredFilePatterns(config);
|
|
@@ -3821,8 +4060,7 @@ const filterIgnoredDiagnostics = (diagnostics, config, rootDirectory, readFileLi
|
|
|
3821
4060
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
3822
4061
|
const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);
|
|
3823
4062
|
return diagnostics.filter((diagnostic) => {
|
|
3824
|
-
|
|
3825
|
-
if (ignoredRules.has(ruleIdentifier)) return false;
|
|
4063
|
+
if (isIgnoredRule(ignoredRules, `${diagnostic.plugin}/${diagnostic.rule}`)) return false;
|
|
3826
4064
|
if (isFileIgnoredByPatterns(diagnostic.filePath, rootDirectory, ignoredFilePatterns)) return false;
|
|
3827
4065
|
if (isDiagnosticIgnoredByOverrides(diagnostic, rootDirectory, compiledOverrides)) return false;
|
|
3828
4066
|
if ((hasTextComponents || hasRawTextWrappers) && diagnostic.rule === "rn-no-raw-text" && diagnostic.line > 0) {
|
|
@@ -3850,11 +4088,24 @@ const filterInlineSuppressions = (diagnostics, rootDirectory, readFileLinesSync)
|
|
|
3850
4088
|
}] : [diagnostic];
|
|
3851
4089
|
});
|
|
3852
4090
|
};
|
|
3853
|
-
const
|
|
4091
|
+
const TEST_FILE_DIRECTORY_PATTERN = /(?:^|\/)(?:__tests__|__test__|tests|test|__mocks__|cypress|e2e|playwright)\//;
|
|
4092
|
+
const TEST_FILE_SUFFIX_PATTERN = /\.(?:test|spec|stories|story|fixture|fixtures)\.(?:[cm]?[jt]sx?)$/;
|
|
4093
|
+
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
4094
|
+
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
4095
|
+
const stripAboveSourceRoot = (relativePath) => {
|
|
4096
|
+
const fixtureMatch = FIXTURE_PROJECT_PATTERN.exec(relativePath);
|
|
4097
|
+
if (fixtureMatch === null) return relativePath;
|
|
4098
|
+
let lastIdx = -1;
|
|
4099
|
+
for (const match of relativePath.matchAll(SOURCE_ROOT_PATTERN)) if (match.index !== void 0 && match.index > lastIdx) lastIdx = match.index;
|
|
4100
|
+
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
4101
|
+
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
4102
|
+
};
|
|
3854
4103
|
const isTestFilePath = (relativePath) => {
|
|
3855
4104
|
if (relativePath.length === 0) return false;
|
|
3856
4105
|
const forwardSlashed = relativePath.replaceAll("\\", "/");
|
|
3857
|
-
|
|
4106
|
+
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return true;
|
|
4107
|
+
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
4108
|
+
return TEST_FILE_DIRECTORY_PATTERN.test(scoped);
|
|
3858
4109
|
};
|
|
3859
4110
|
const testFileResultCache = /* @__PURE__ */ new Map();
|
|
3860
4111
|
const clearAutoSuppressionCaches = () => {
|
|
@@ -3862,7 +4113,9 @@ const clearAutoSuppressionCaches = () => {
|
|
|
3862
4113
|
};
|
|
3863
4114
|
const shouldAutoSuppress = (diagnostic) => {
|
|
3864
4115
|
const filePath = diagnostic.filePath;
|
|
3865
|
-
|
|
4116
|
+
const rule = diagnostic.plugin === "react-doctor" ? reactDoctorPlugin.rules[diagnostic.rule] : null;
|
|
4117
|
+
if (rule?.tags?.includes("test-noise")) {
|
|
4118
|
+
if (rule.tags.includes("migration-hint")) return false;
|
|
3866
4119
|
let isTest = testFileResultCache.get(filePath);
|
|
3867
4120
|
if (isTest === void 0) {
|
|
3868
4121
|
isTest = isTestFilePath(filePath);
|
|
@@ -3879,9 +4132,13 @@ const mergeAndFilterDiagnostics = (mergedDiagnostics, directory, userConfig, rea
|
|
|
3879
4132
|
return filterInlineSuppressions(filtered, directory, readFileLinesSync);
|
|
3880
4133
|
};
|
|
3881
4134
|
const combineDiagnostics = (input) => {
|
|
3882
|
-
const { lintDiagnostics, directory, isDiffMode, userConfig, readFileLinesSync = createNodeReadFileLinesSync(directory), includeEnvironmentChecks = true, respectInlineDisables } = input;
|
|
3883
|
-
const
|
|
3884
|
-
return mergeAndFilterDiagnostics([
|
|
4135
|
+
const { lintDiagnostics, directory, isDiffMode, userConfig, readFileLinesSync = createNodeReadFileLinesSync(directory), includeEnvironmentChecks = true, respectInlineDisables, extraDiagnostics = [] } = input;
|
|
4136
|
+
const environmentDiagnostics = isDiffMode || !includeEnvironmentChecks ? [] : checkReducedMotion(directory);
|
|
4137
|
+
return mergeAndFilterDiagnostics([
|
|
4138
|
+
...lintDiagnostics,
|
|
4139
|
+
...environmentDiagnostics,
|
|
4140
|
+
...extraDiagnostics
|
|
4141
|
+
], directory, userConfig, readFileLinesSync, { respectInlineDisables });
|
|
3885
4142
|
};
|
|
3886
4143
|
const findFirstLintConfigInDirectory = (directory) => {
|
|
3887
4144
|
for (const filename of ADOPTABLE_LINT_CONFIG_FILENAMES) {
|
|
@@ -4004,11 +4261,11 @@ const getUncommittedChangedFiles = (directory) => {
|
|
|
4004
4261
|
const getDiffInfo = (directory, explicitBaseBranch) => {
|
|
4005
4262
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) throw new Error("Diff base branch cannot be empty.");
|
|
4006
4263
|
const currentBranch = getCurrentBranch(directory);
|
|
4007
|
-
if (!currentBranch) return null;
|
|
4264
|
+
if (!currentBranch && !explicitBaseBranch) return null;
|
|
4008
4265
|
const baseBranch = explicitBaseBranch ?? detectDefaultBranch(directory);
|
|
4009
4266
|
if (!baseBranch) return null;
|
|
4010
4267
|
if (explicitBaseBranch && !branchExists(directory, explicitBaseBranch)) throw new Error(`Diff base branch "${explicitBaseBranch}" does not exist (run \`git fetch\` to update remote refs).`);
|
|
4011
|
-
if (currentBranch === baseBranch) {
|
|
4268
|
+
if (currentBranch !== null && currentBranch === baseBranch) {
|
|
4012
4269
|
const uncommittedFiles = getUncommittedChangedFiles(directory);
|
|
4013
4270
|
if (uncommittedFiles.length === 0) return null;
|
|
4014
4271
|
return {
|
|
@@ -4035,6 +4292,7 @@ const VALID_RULE_SEVERITIES = [
|
|
|
4035
4292
|
];
|
|
4036
4293
|
const BOOLEAN_FIELD_NAMES = [
|
|
4037
4294
|
"lint",
|
|
4295
|
+
"deadCode",
|
|
4038
4296
|
"verbose",
|
|
4039
4297
|
"customRulesOnly",
|
|
4040
4298
|
"share",
|
|
@@ -4249,12 +4507,7 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
|
|
|
4249
4507
|
while (stack.length > 0) {
|
|
4250
4508
|
const current = stack.pop();
|
|
4251
4509
|
if (current === void 0) continue;
|
|
4252
|
-
|
|
4253
|
-
try {
|
|
4254
|
-
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
4255
|
-
} catch {
|
|
4256
|
-
continue;
|
|
4257
|
-
}
|
|
4510
|
+
const entries = readDirectoryEntries(current);
|
|
4258
4511
|
for (const entry of entries) {
|
|
4259
4512
|
if (entry.isDirectory()) {
|
|
4260
4513
|
if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
@@ -4312,7 +4565,7 @@ const resolveConfigRootDir = (config, configSourceDirectory) => {
|
|
|
4312
4565
|
if (trimmedRootDir.length === 0) return null;
|
|
4313
4566
|
const resolvedRootDir = path.isAbsolute(trimmedRootDir) ? trimmedRootDir : path.resolve(configSourceDirectory, trimmedRootDir);
|
|
4314
4567
|
if (resolvedRootDir === configSourceDirectory) return null;
|
|
4315
|
-
if (!
|
|
4568
|
+
if (!isDirectory(resolvedRootDir)) {
|
|
4316
4569
|
logger.warn(`react-doctor config "rootDir" points to "${rawRootDir}" (resolved to ${resolvedRootDir}), which is not a directory. Ignoring.`);
|
|
4317
4570
|
return null;
|
|
4318
4571
|
}
|
|
@@ -4345,7 +4598,7 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
4345
4598
|
const stack = [rootDirectory];
|
|
4346
4599
|
while (stack.length > 0) {
|
|
4347
4600
|
const currentDirectory = stack.pop();
|
|
4348
|
-
const entries =
|
|
4601
|
+
const entries = readDirectoryEntries(currentDirectory);
|
|
4349
4602
|
for (const entry of entries) {
|
|
4350
4603
|
const absolutePath = path.join(currentDirectory, entry.name);
|
|
4351
4604
|
if (entry.isDirectory()) {
|
|
@@ -4395,10 +4648,13 @@ const buildCapabilities = (project) => {
|
|
|
4395
4648
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
4396
4649
|
return capabilities;
|
|
4397
4650
|
};
|
|
4398
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags) => {
|
|
4651
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
|
|
4399
4652
|
if (requires) {
|
|
4400
4653
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
4401
4654
|
}
|
|
4655
|
+
if (disabledBy) {
|
|
4656
|
+
for (const capability of disabledBy) if (capabilities.has(capability)) return false;
|
|
4657
|
+
}
|
|
4402
4658
|
if (tags) {
|
|
4403
4659
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
4404
4660
|
}
|
|
@@ -4431,23 +4687,6 @@ const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
|
|
|
4431
4687
|
availableRuleNames: readPluginRuleNames(pluginSpecifier)
|
|
4432
4688
|
};
|
|
4433
4689
|
};
|
|
4434
|
-
const YOU_MIGHT_NOT_NEED_EFFECT_NAMESPACE = "effect";
|
|
4435
|
-
const resolveYouMightNotNeedEffectPlugin = (customRulesOnly) => {
|
|
4436
|
-
if (customRulesOnly) return null;
|
|
4437
|
-
let pluginSpecifier;
|
|
4438
|
-
try {
|
|
4439
|
-
pluginSpecifier = esmRequire$1.resolve("eslint-plugin-react-you-might-not-need-an-effect");
|
|
4440
|
-
} catch {
|
|
4441
|
-
return null;
|
|
4442
|
-
}
|
|
4443
|
-
return {
|
|
4444
|
-
entry: {
|
|
4445
|
-
name: YOU_MIGHT_NOT_NEED_EFFECT_NAMESPACE,
|
|
4446
|
-
specifier: pluginSpecifier
|
|
4447
|
-
},
|
|
4448
|
-
availableRuleNames: readPluginRuleNames(pluginSpecifier)
|
|
4449
|
-
};
|
|
4450
|
-
};
|
|
4451
4690
|
const filterRulesToAvailable = (rules, pluginNamespace, availableRuleNames) => {
|
|
4452
4691
|
if (availableRuleNames.size === 0) return rules;
|
|
4453
4692
|
const ruleKeyPrefix = `${pluginNamespace}/`;
|
|
@@ -4466,26 +4705,36 @@ const resolveSettingsRootDirectory = (rootDirectory) => {
|
|
|
4466
4705
|
if (!fs.existsSync(rootDirectory)) return rootDirectory;
|
|
4467
4706
|
return fs.realpathSync(rootDirectory);
|
|
4468
4707
|
};
|
|
4708
|
+
const applyRuleSeverityControls = (rules, severityControls) => {
|
|
4709
|
+
const enabledRules = {};
|
|
4710
|
+
for (const [ruleKey, defaultSeverity] of Object.entries(rules)) {
|
|
4711
|
+
const severity = resolveRuleSeverityOverride({ ruleKey }, severityControls) ?? defaultSeverity;
|
|
4712
|
+
if (severity === "off") continue;
|
|
4713
|
+
enabledRules[ruleKey] = severity;
|
|
4714
|
+
}
|
|
4715
|
+
return enabledRules;
|
|
4716
|
+
};
|
|
4469
4717
|
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls }) => {
|
|
4470
4718
|
const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
4471
|
-
const reactCompilerRules = reactHooksJsPlugin ? filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames) : {};
|
|
4472
|
-
const youMightNotNeedEffectPlugin = resolveYouMightNotNeedEffectPlugin(customRulesOnly);
|
|
4473
|
-
const youMightNotNeedEffectRules = youMightNotNeedEffectPlugin ? filterRulesToAvailable(YOU_MIGHT_NOT_NEED_EFFECT_RULES, YOU_MIGHT_NOT_NEED_EFFECT_NAMESPACE, youMightNotNeedEffectPlugin.availableRuleNames) : {};
|
|
4719
|
+
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
4474
4720
|
const jsPlugins = [];
|
|
4475
4721
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
4476
|
-
if (youMightNotNeedEffectPlugin) jsPlugins.push(youMightNotNeedEffectPlugin.entry);
|
|
4477
4722
|
const capabilities = buildCapabilities(project);
|
|
4478
4723
|
const enabledReactDoctorRules = {};
|
|
4479
|
-
for (const
|
|
4480
|
-
const
|
|
4724
|
+
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
4725
|
+
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
4726
|
+
if (!rule) continue;
|
|
4727
|
+
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
4481
4728
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
4482
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags)) continue;
|
|
4483
|
-
const
|
|
4484
|
-
ruleKey:
|
|
4729
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
|
|
4730
|
+
const explicitSeverity = resolveRuleSeverityOverride({
|
|
4731
|
+
ruleKey: registryEntry.key,
|
|
4485
4732
|
category: rule.category
|
|
4486
|
-
}, severityControls)
|
|
4733
|
+
}, severityControls);
|
|
4734
|
+
if (rule.defaultEnabled === false && explicitSeverity === void 0) continue;
|
|
4735
|
+
const severity = explicitSeverity ?? rule.severity;
|
|
4487
4736
|
if (severity === "off") continue;
|
|
4488
|
-
enabledReactDoctorRules[
|
|
4737
|
+
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
4489
4738
|
}
|
|
4490
4739
|
return {
|
|
4491
4740
|
...extendsPaths.length > 0 ? { extends: extendsPaths } : {},
|
|
@@ -4498,7 +4747,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
4498
4747
|
style: "off",
|
|
4499
4748
|
nursery: "off"
|
|
4500
4749
|
},
|
|
4501
|
-
plugins:
|
|
4750
|
+
plugins: [],
|
|
4502
4751
|
jsPlugins: [...jsPlugins, pluginPath],
|
|
4503
4752
|
settings: { "react-doctor": {
|
|
4504
4753
|
framework: project.framework,
|
|
@@ -4506,10 +4755,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
4506
4755
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
4507
4756
|
} },
|
|
4508
4757
|
rules: {
|
|
4509
|
-
...customRulesOnly ? {} : BUILTIN_REACT_RULES,
|
|
4510
|
-
...customRulesOnly ? {} : BUILTIN_A11Y_RULES,
|
|
4511
4758
|
...reactCompilerRules,
|
|
4512
|
-
...youMightNotNeedEffectRules,
|
|
4513
4759
|
...enabledReactDoctorRules
|
|
4514
4760
|
}
|
|
4515
4761
|
};
|
|
@@ -4530,43 +4776,43 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
4530
4776
|
isReactNamespaceBinding: false
|
|
4531
4777
|
};
|
|
4532
4778
|
const getScriptKind = (filename) => {
|
|
4533
|
-
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
4534
|
-
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
4535
|
-
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
4536
|
-
return ts.ScriptKind.JS;
|
|
4779
|
+
if (filename.endsWith(".tsx")) return ts$1.ScriptKind.TSX;
|
|
4780
|
+
if (filename.endsWith(".jsx")) return ts$1.ScriptKind.JSX;
|
|
4781
|
+
if (filename.endsWith(".ts")) return ts$1.ScriptKind.TS;
|
|
4782
|
+
return ts$1.ScriptKind.JS;
|
|
4537
4783
|
};
|
|
4538
4784
|
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
4539
4785
|
const unwrapExpression = (expression) => {
|
|
4540
4786
|
let currentExpression = expression;
|
|
4541
|
-
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
4787
|
+
while (ts$1.isParenthesizedExpression(currentExpression) || ts$1.isAsExpression(currentExpression) || ts$1.isSatisfiesExpression(currentExpression) || ts$1.isNonNullExpression(currentExpression) || ts$1.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
4542
4788
|
return currentExpression;
|
|
4543
4789
|
};
|
|
4544
4790
|
const getStaticPropertyName = (node) => {
|
|
4545
4791
|
if (!node) return null;
|
|
4546
|
-
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
4547
|
-
if (ts.isComputedPropertyName(node)) {
|
|
4792
|
+
if (ts$1.isIdentifier(node) || ts$1.isStringLiteral(node) || ts$1.isNumericLiteral(node)) return node.text;
|
|
4793
|
+
if (ts$1.isComputedPropertyName(node)) {
|
|
4548
4794
|
const expression = unwrapExpression(node.expression);
|
|
4549
|
-
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
|
|
4795
|
+
if (ts$1.isStringLiteral(expression) || ts$1.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
|
|
4550
4796
|
}
|
|
4551
4797
|
return null;
|
|
4552
4798
|
};
|
|
4553
4799
|
const findBindingIdentifier = (bindingName, identifierName) => {
|
|
4554
|
-
if (ts.isIdentifier(bindingName)) return bindingName.text === identifierName ? bindingName : null;
|
|
4800
|
+
if (ts$1.isIdentifier(bindingName)) return bindingName.text === identifierName ? bindingName : null;
|
|
4555
4801
|
for (const element of bindingName.elements) {
|
|
4556
|
-
if (ts.isOmittedExpression(element)) continue;
|
|
4802
|
+
if (ts$1.isOmittedExpression(element)) continue;
|
|
4557
4803
|
const nestedIdentifier = findBindingIdentifier(element.name, identifierName);
|
|
4558
4804
|
if (nestedIdentifier) return nestedIdentifier;
|
|
4559
4805
|
}
|
|
4560
4806
|
return null;
|
|
4561
4807
|
};
|
|
4562
4808
|
const bindingNameHasIdentifier = (bindingName, identifierName) => {
|
|
4563
|
-
if (ts.isIdentifier(bindingName)) return bindingName.text === identifierName;
|
|
4809
|
+
if (ts$1.isIdentifier(bindingName)) return bindingName.text === identifierName;
|
|
4564
4810
|
return bindingName.elements.some((element) => {
|
|
4565
|
-
if (ts.isOmittedExpression(element)) return false;
|
|
4811
|
+
if (ts$1.isOmittedExpression(element)) return false;
|
|
4566
4812
|
return bindingNameHasIdentifier(element.name, identifierName);
|
|
4567
4813
|
});
|
|
4568
4814
|
};
|
|
4569
|
-
const getDirectBindingIdentifier = (bindingName) => ts.isIdentifier(bindingName) ? bindingName : null;
|
|
4815
|
+
const getDirectBindingIdentifier = (bindingName) => ts$1.isIdentifier(bindingName) ? bindingName : null;
|
|
4570
4816
|
const isReactUseObjectBindingElement = (bindingElement) => {
|
|
4571
4817
|
const bindingIdentifier = getDirectBindingIdentifier(bindingElement.name);
|
|
4572
4818
|
if (!bindingIdentifier) return false;
|
|
@@ -4575,12 +4821,12 @@ const isReactUseObjectBindingElement = (bindingElement) => {
|
|
|
4575
4821
|
};
|
|
4576
4822
|
const isReactRequireCall = (expression) => {
|
|
4577
4823
|
const unwrappedExpression = unwrapExpression(expression);
|
|
4578
|
-
return ts.isCallExpression(unwrappedExpression) && ts.isIdentifier(unwrappedExpression.expression) && unwrappedExpression.expression.text === REQUIRE_IDENTIFIER && unwrappedExpression.arguments.length === 1 && ts.isStringLiteral(unwrappedExpression.arguments[0]) && unwrappedExpression.arguments[0].text === REACT_MODULE_SOURCE;
|
|
4824
|
+
return ts$1.isCallExpression(unwrappedExpression) && ts$1.isIdentifier(unwrappedExpression.expression) && unwrappedExpression.expression.text === REQUIRE_IDENTIFIER && unwrappedExpression.arguments.length === 1 && ts$1.isStringLiteral(unwrappedExpression.arguments[0]) && unwrappedExpression.arguments[0].text === REACT_MODULE_SOURCE;
|
|
4579
4825
|
};
|
|
4580
4826
|
const getModuleSource = (node) => {
|
|
4581
4827
|
let currentNode = node;
|
|
4582
4828
|
while (currentNode) {
|
|
4583
|
-
if (ts.isImportDeclaration(currentNode) && ts.isStringLiteral(currentNode.moduleSpecifier)) return currentNode.moduleSpecifier.text;
|
|
4829
|
+
if (ts$1.isImportDeclaration(currentNode) && ts$1.isStringLiteral(currentNode.moduleSpecifier)) return currentNode.moduleSpecifier.text;
|
|
4584
4830
|
currentNode = currentNode.parent;
|
|
4585
4831
|
}
|
|
4586
4832
|
return null;
|
|
@@ -4597,40 +4843,40 @@ const isReactObjectBindingName = (bindingPattern, identifierName) => bindingPatt
|
|
|
4597
4843
|
return isReactUseObjectBindingElement(bindingElement);
|
|
4598
4844
|
});
|
|
4599
4845
|
const isReactRequireBindingDeclaration = (node, identifierName) => {
|
|
4600
|
-
if (!ts.isVariableDeclaration(node)) return false;
|
|
4846
|
+
if (!ts$1.isVariableDeclaration(node)) return false;
|
|
4601
4847
|
if (!node.initializer) return false;
|
|
4602
4848
|
if (!isReactRequireCall(node.initializer)) return false;
|
|
4603
|
-
if (ts.isIdentifier(node.name)) return node.name.text === identifierName;
|
|
4604
|
-
return ts.isObjectBindingPattern(node.name) && isReactObjectBindingName(node.name, identifierName);
|
|
4849
|
+
if (ts$1.isIdentifier(node.name)) return node.name.text === identifierName;
|
|
4850
|
+
return ts$1.isObjectBindingPattern(node.name) && isReactObjectBindingName(node.name, identifierName);
|
|
4605
4851
|
};
|
|
4606
4852
|
const collectReactImportBindings = (sourceFile) => {
|
|
4607
4853
|
const namespaceNames = /* @__PURE__ */ new Set();
|
|
4608
4854
|
const useImportNames = /* @__PURE__ */ new Set();
|
|
4609
4855
|
for (const statement of sourceFile.statements) {
|
|
4610
|
-
if (ts.isImportDeclaration(statement)) {
|
|
4611
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
4856
|
+
if (ts$1.isImportDeclaration(statement)) {
|
|
4857
|
+
if (!ts$1.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
4612
4858
|
if (statement.moduleSpecifier.text !== REACT_MODULE_SOURCE) continue;
|
|
4613
4859
|
const importClause = statement.importClause;
|
|
4614
4860
|
if (!importClause) continue;
|
|
4615
4861
|
if (importClause.name) namespaceNames.add(importClause.name.text);
|
|
4616
4862
|
const namedBindings = importClause.namedBindings;
|
|
4617
4863
|
if (!namedBindings) continue;
|
|
4618
|
-
if (ts.isNamespaceImport(namedBindings)) {
|
|
4864
|
+
if (ts$1.isNamespaceImport(namedBindings)) {
|
|
4619
4865
|
namespaceNames.add(namedBindings.name.text);
|
|
4620
4866
|
continue;
|
|
4621
4867
|
}
|
|
4622
4868
|
for (const importSpecifier of namedBindings.elements) if (getImportedName(importSpecifier) === USE_IDENTIFIER) useImportNames.add(importSpecifier.name.text);
|
|
4623
4869
|
continue;
|
|
4624
4870
|
}
|
|
4625
|
-
if (!ts.isVariableStatement(statement)) continue;
|
|
4871
|
+
if (!ts$1.isVariableStatement(statement)) continue;
|
|
4626
4872
|
for (const declaration of statement.declarationList.declarations) {
|
|
4627
4873
|
if (!declaration.initializer) continue;
|
|
4628
4874
|
if (!isReactRequireCall(declaration.initializer)) continue;
|
|
4629
|
-
if (ts.isIdentifier(declaration.name)) {
|
|
4875
|
+
if (ts$1.isIdentifier(declaration.name)) {
|
|
4630
4876
|
namespaceNames.add(declaration.name.text);
|
|
4631
4877
|
continue;
|
|
4632
4878
|
}
|
|
4633
|
-
if (ts.isObjectBindingPattern(declaration.name)) collectReactObjectBindingNames(declaration.name, useImportNames);
|
|
4879
|
+
if (ts$1.isObjectBindingPattern(declaration.name)) collectReactObjectBindingNames(declaration.name, useImportNames);
|
|
4634
4880
|
}
|
|
4635
4881
|
}
|
|
4636
4882
|
return {
|
|
@@ -4641,24 +4887,24 @@ const collectReactImportBindings = (sourceFile) => {
|
|
|
4641
4887
|
const findBindingElement = (identifier) => {
|
|
4642
4888
|
let currentNode = identifier.parent;
|
|
4643
4889
|
while (currentNode) {
|
|
4644
|
-
if (ts.isBindingElement(currentNode)) return currentNode;
|
|
4645
|
-
if (ts.isVariableDeclaration(currentNode) || ts.isParameter(currentNode)) return null;
|
|
4890
|
+
if (ts$1.isBindingElement(currentNode)) return currentNode;
|
|
4891
|
+
if (ts$1.isVariableDeclaration(currentNode) || ts$1.isParameter(currentNode)) return null;
|
|
4646
4892
|
currentNode = currentNode.parent;
|
|
4647
4893
|
}
|
|
4648
4894
|
return null;
|
|
4649
4895
|
};
|
|
4650
4896
|
const declarationBindsIdentifier = (node, identifierName) => {
|
|
4651
|
-
if (ts.isVariableDeclaration(node) || ts.isParameter(node)) return bindingNameHasIdentifier(node.name, identifierName);
|
|
4652
|
-
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) return node.name?.text === identifierName;
|
|
4897
|
+
if (ts$1.isVariableDeclaration(node) || ts$1.isParameter(node)) return bindingNameHasIdentifier(node.name, identifierName);
|
|
4898
|
+
if (ts$1.isFunctionDeclaration(node) || ts$1.isClassDeclaration(node)) return node.name?.text === identifierName;
|
|
4653
4899
|
return false;
|
|
4654
4900
|
};
|
|
4655
|
-
const isScopeBoundary = (node) => ts.isFunctionLike(node) || ts.isClassLike(node) || ts.isBlock(node) || ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isCatchClause(node) || ts.isSourceFile(node) || ts.isModuleBlock(node);
|
|
4901
|
+
const isScopeBoundary = (node) => ts$1.isFunctionLike(node) || ts$1.isClassLike(node) || ts$1.isBlock(node) || ts$1.isForStatement(node) || ts$1.isForInStatement(node) || ts$1.isForOfStatement(node) || ts$1.isCatchClause(node) || ts$1.isSourceFile(node) || ts$1.isModuleBlock(node);
|
|
4656
4902
|
const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
|
|
4657
4903
|
if (isReactRequireBindingDeclaration(node, identifierName)) return false;
|
|
4658
4904
|
if (declarationBindsIdentifier(node, identifierName)) return true;
|
|
4659
4905
|
if (node !== scopeNode && isScopeBoundary(node)) return false;
|
|
4660
4906
|
let didFindBinding = false;
|
|
4661
|
-
ts.forEachChild(node, (child) => {
|
|
4907
|
+
ts$1.forEachChild(node, (child) => {
|
|
4662
4908
|
if (didFindBinding) return;
|
|
4663
4909
|
didFindBinding = scopeContainsNonImportBinding(child, scopeNode, identifierName);
|
|
4664
4910
|
});
|
|
@@ -4678,26 +4924,26 @@ const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
|
4678
4924
|
const isReactNamespaceExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
4679
4925
|
const unwrappedExpression = unwrapExpression(expression);
|
|
4680
4926
|
if (isReactRequireCall(unwrappedExpression)) return true;
|
|
4681
|
-
if (!ts.isIdentifier(unwrappedExpression)) return false;
|
|
4927
|
+
if (!ts$1.isIdentifier(unwrappedExpression)) return false;
|
|
4682
4928
|
if (reactImportBindings.namespaceNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
|
|
4683
4929
|
return resolveIdentifierBinding(unwrappedExpression, reactImportBindings, sourceFile, visitedDeclarations)?.isReactNamespaceBinding ?? false;
|
|
4684
4930
|
};
|
|
4685
4931
|
const isReactUseExpression = (expression, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
4686
4932
|
if (!expression) return false;
|
|
4687
4933
|
const unwrappedExpression = unwrapExpression(expression);
|
|
4688
|
-
if (ts.isIdentifier(unwrappedExpression)) {
|
|
4934
|
+
if (ts$1.isIdentifier(unwrappedExpression)) {
|
|
4689
4935
|
if (reactImportBindings.useImportNames.has(unwrappedExpression.text) && !isIdentifierShadowedByLocalBinding(unwrappedExpression, sourceFile)) return true;
|
|
4690
4936
|
if (unwrappedExpression.text === USE_IDENTIFIER) return false;
|
|
4691
4937
|
return resolveIdentifierBinding(unwrappedExpression, reactImportBindings, sourceFile, visitedDeclarations)?.isReactUseBinding ?? false;
|
|
4692
4938
|
}
|
|
4693
|
-
if (ts.isPropertyAccessExpression(unwrappedExpression) && unwrappedExpression.name.text === USE_IDENTIFIER && isReactNamespaceExpression(unwrappedExpression.expression, reactImportBindings, sourceFile, visitedDeclarations)) return true;
|
|
4694
|
-
if (ts.isElementAccessExpression(unwrappedExpression) && ts.isStringLiteral(unwrappedExpression.argumentExpression) && unwrappedExpression.argumentExpression.text === USE_IDENTIFIER) return isReactNamespaceExpression(unwrappedExpression.expression, reactImportBindings, sourceFile, visitedDeclarations);
|
|
4939
|
+
if (ts$1.isPropertyAccessExpression(unwrappedExpression) && unwrappedExpression.name.text === USE_IDENTIFIER && isReactNamespaceExpression(unwrappedExpression.expression, reactImportBindings, sourceFile, visitedDeclarations)) return true;
|
|
4940
|
+
if (ts$1.isElementAccessExpression(unwrappedExpression) && ts$1.isStringLiteral(unwrappedExpression.argumentExpression) && unwrappedExpression.argumentExpression.text === USE_IDENTIFIER) return isReactNamespaceExpression(unwrappedExpression.expression, reactImportBindings, sourceFile, visitedDeclarations);
|
|
4695
4941
|
return false;
|
|
4696
4942
|
};
|
|
4697
4943
|
const isReactUseObjectBinding = (identifier, variableDeclaration, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
4698
4944
|
const bindingElement = findBindingElement(identifier);
|
|
4699
4945
|
if (!bindingElement) return false;
|
|
4700
|
-
if (!ts.isObjectBindingPattern(bindingElement.parent)) return false;
|
|
4946
|
+
if (!ts$1.isObjectBindingPattern(bindingElement.parent)) return false;
|
|
4701
4947
|
if (!variableDeclaration.initializer) return false;
|
|
4702
4948
|
if (!isReactNamespaceExpression(variableDeclaration.initializer, reactImportBindings, sourceFile, visitedDeclarations)) return false;
|
|
4703
4949
|
return isReactUseObjectBindingElement(bindingElement);
|
|
@@ -4709,23 +4955,23 @@ const getVariableDeclarationResolution = (variableDeclaration, identifierName, r
|
|
|
4709
4955
|
const nestedVisitedDeclarations = new Set(visitedDeclarations);
|
|
4710
4956
|
nestedVisitedDeclarations.add(variableDeclaration);
|
|
4711
4957
|
return {
|
|
4712
|
-
isReactNamespaceBinding: ts.isIdentifier(variableDeclaration.name) && variableDeclaration.initializer !== void 0 && isReactNamespaceExpression(variableDeclaration.initializer, reactImportBindings, sourceFile, new Set(nestedVisitedDeclarations)),
|
|
4958
|
+
isReactNamespaceBinding: ts$1.isIdentifier(variableDeclaration.name) && variableDeclaration.initializer !== void 0 && isReactNamespaceExpression(variableDeclaration.initializer, reactImportBindings, sourceFile, new Set(nestedVisitedDeclarations)),
|
|
4713
4959
|
isReactUseBinding: isReactUseExpression(variableDeclaration.initializer, reactImportBindings, sourceFile, new Set(nestedVisitedDeclarations)) || isReactUseObjectBinding(bindingIdentifier, variableDeclaration, reactImportBindings, sourceFile, new Set(nestedVisitedDeclarations))
|
|
4714
4960
|
};
|
|
4715
4961
|
};
|
|
4716
4962
|
const getImportResolution = (node, identifierName) => {
|
|
4717
|
-
if (ts.isImportSpecifier(node) && node.name.text === identifierName) return getModuleSource(node) === REACT_MODULE_SOURCE && getImportedName(node) === USE_IDENTIFIER ? REACT_USE_BINDING_RESOLUTION : LOCAL_BINDING_RESOLUTION;
|
|
4718
|
-
if (ts.isNamespaceImport(node) && node.name.text === identifierName) return getModuleSource(node) === REACT_MODULE_SOURCE ? REACT_NAMESPACE_BINDING_RESOLUTION : LOCAL_BINDING_RESOLUTION;
|
|
4719
|
-
if (ts.isImportClause(node) && node.name?.text === identifierName) return getModuleSource(node) === REACT_MODULE_SOURCE ? REACT_NAMESPACE_BINDING_RESOLUTION : LOCAL_BINDING_RESOLUTION;
|
|
4963
|
+
if (ts$1.isImportSpecifier(node) && node.name.text === identifierName) return getModuleSource(node) === REACT_MODULE_SOURCE && getImportedName(node) === USE_IDENTIFIER ? REACT_USE_BINDING_RESOLUTION : LOCAL_BINDING_RESOLUTION;
|
|
4964
|
+
if (ts$1.isNamespaceImport(node) && node.name.text === identifierName) return getModuleSource(node) === REACT_MODULE_SOURCE ? REACT_NAMESPACE_BINDING_RESOLUTION : LOCAL_BINDING_RESOLUTION;
|
|
4965
|
+
if (ts$1.isImportClause(node) && node.name?.text === identifierName) return getModuleSource(node) === REACT_MODULE_SOURCE ? REACT_NAMESPACE_BINDING_RESOLUTION : LOCAL_BINDING_RESOLUTION;
|
|
4720
4966
|
return null;
|
|
4721
4967
|
};
|
|
4722
4968
|
const getDeclarationResolution = (node, identifierName, reactImportBindings, sourceFile, visitedDeclarations) => {
|
|
4723
4969
|
const importResolution = getImportResolution(node, identifierName);
|
|
4724
4970
|
if (importResolution) return importResolution;
|
|
4725
|
-
if (ts.isVariableDeclaration(node)) return getVariableDeclarationResolution(node, identifierName, reactImportBindings, sourceFile, visitedDeclarations);
|
|
4726
|
-
if (ts.isParameter(node)) return bindingNameHasIdentifier(node.name, identifierName) ? LOCAL_BINDING_RESOLUTION : null;
|
|
4727
|
-
if (ts.isFunctionDeclaration(node) && node.name?.text === identifierName) return LOCAL_BINDING_RESOLUTION;
|
|
4728
|
-
if (ts.isClassDeclaration(node) && node.name?.text === identifierName) return LOCAL_BINDING_RESOLUTION;
|
|
4971
|
+
if (ts$1.isVariableDeclaration(node)) return getVariableDeclarationResolution(node, identifierName, reactImportBindings, sourceFile, visitedDeclarations);
|
|
4972
|
+
if (ts$1.isParameter(node)) return bindingNameHasIdentifier(node.name, identifierName) ? LOCAL_BINDING_RESOLUTION : null;
|
|
4973
|
+
if (ts$1.isFunctionDeclaration(node) && node.name?.text === identifierName) return LOCAL_BINDING_RESOLUTION;
|
|
4974
|
+
if (ts$1.isClassDeclaration(node) && node.name?.text === identifierName) return LOCAL_BINDING_RESOLUTION;
|
|
4729
4975
|
return null;
|
|
4730
4976
|
};
|
|
4731
4977
|
const isNestedScopeBoundary = (node, scopeNode) => node !== scopeNode && isScopeBoundary(node);
|
|
@@ -4734,14 +4980,14 @@ const findResolutionInSubtree = (node, scopeNode, identifierName, reactImportBin
|
|
|
4734
4980
|
if (declarationResolution) return declarationResolution;
|
|
4735
4981
|
if (isNestedScopeBoundary(node, scopeNode)) return null;
|
|
4736
4982
|
let resolution = null;
|
|
4737
|
-
ts.forEachChild(node, (child) => {
|
|
4983
|
+
ts$1.forEachChild(node, (child) => {
|
|
4738
4984
|
if (resolution) return;
|
|
4739
4985
|
resolution = findResolutionInSubtree(child, scopeNode, identifierName, reactImportBindings, sourceFile, visitedDeclarations);
|
|
4740
4986
|
});
|
|
4741
4987
|
return resolution;
|
|
4742
4988
|
};
|
|
4743
4989
|
const findResolutionInFunctionParameters = (node, identifierName, reactImportBindings) => {
|
|
4744
|
-
if (!ts.isFunctionLike(node)) return null;
|
|
4990
|
+
if (!ts$1.isFunctionLike(node)) return null;
|
|
4745
4991
|
for (const parameter of node.parameters) {
|
|
4746
4992
|
const parameterResolution = getDeclarationResolution(parameter, identifierName, reactImportBindings, parameter.getSourceFile(), /* @__PURE__ */ new Set());
|
|
4747
4993
|
if (parameterResolution) return parameterResolution;
|
|
@@ -4752,7 +4998,7 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
|
|
|
4752
4998
|
const parameterResolution = findResolutionInFunctionParameters(scopeNode, identifierName, reactImportBindings);
|
|
4753
4999
|
if (parameterResolution) return parameterResolution;
|
|
4754
5000
|
let resolution = null;
|
|
4755
|
-
ts.forEachChild(scopeNode, (child) => {
|
|
5001
|
+
ts$1.forEachChild(scopeNode, (child) => {
|
|
4756
5002
|
if (resolution) return;
|
|
4757
5003
|
resolution = findResolutionInSubtree(child, scopeNode, identifierName, reactImportBindings, sourceFile, visitedDeclarations);
|
|
4758
5004
|
});
|
|
@@ -4770,22 +5016,22 @@ const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, v
|
|
|
4770
5016
|
}
|
|
4771
5017
|
return null;
|
|
4772
5018
|
};
|
|
4773
|
-
const isUseCallIdentifier = (node) => node.text === USE_IDENTIFIER && ts.isCallExpression(node.parent) && node.parent.expression === node;
|
|
5019
|
+
const isUseCallIdentifier = (node) => node.text === USE_IDENTIFIER && ts$1.isCallExpression(node.parent) && node.parent.expression === node;
|
|
4774
5020
|
const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
4775
5021
|
let matchedIdentifier = null;
|
|
4776
5022
|
const visit = (node) => {
|
|
4777
5023
|
if (matchedIdentifier) return;
|
|
4778
|
-
if (ts.isIdentifier(node) && isUseCallIdentifier(node) && node.getStart(sourceFile) === useOffset) {
|
|
5024
|
+
if (ts$1.isIdentifier(node) && isUseCallIdentifier(node) && node.getStart(sourceFile) === useOffset) {
|
|
4779
5025
|
matchedIdentifier = node;
|
|
4780
5026
|
return;
|
|
4781
5027
|
}
|
|
4782
|
-
ts.forEachChild(node, visit);
|
|
5028
|
+
ts$1.forEachChild(node, visit);
|
|
4783
5029
|
};
|
|
4784
5030
|
visit(sourceFile);
|
|
4785
5031
|
return matchedIdentifier;
|
|
4786
5032
|
};
|
|
4787
5033
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
4788
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
5034
|
+
const sourceFile = ts$1.createSourceFile(filename, sourceText, ts$1.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
4789
5035
|
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
4790
5036
|
if (!useIdentifier) return null;
|
|
4791
5037
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
@@ -4887,7 +5133,13 @@ const SANITIZED_ENV = (() => {
|
|
|
4887
5133
|
}
|
|
4888
5134
|
return sanitized;
|
|
4889
5135
|
})();
|
|
4890
|
-
const OXLINT_SPAWN_TIMEOUT_MS =
|
|
5136
|
+
const OXLINT_SPAWN_TIMEOUT_MS = (() => {
|
|
5137
|
+
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
5138
|
+
if (raw === void 0) return 6e4;
|
|
5139
|
+
const parsed = Number(raw);
|
|
5140
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return 6e4;
|
|
5141
|
+
return parsed;
|
|
5142
|
+
})();
|
|
4891
5143
|
const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolve, reject) => {
|
|
4892
5144
|
const child = spawn(nodeBinaryPath, args, {
|
|
4893
5145
|
cwd: rootDirectory,
|
|
@@ -5067,6 +5319,7 @@ const runOxlint = async (options) => {
|
|
|
5067
5319
|
const spawnLintBatches = async () => {
|
|
5068
5320
|
const allDiagnostics = [];
|
|
5069
5321
|
const droppedFiles = [];
|
|
5322
|
+
let firstDropReason = null;
|
|
5070
5323
|
const spawnLintBatch = async (batch) => {
|
|
5071
5324
|
const batchArgs = [...baseArgs, ...batch];
|
|
5072
5325
|
try {
|
|
@@ -5075,6 +5328,7 @@ const runOxlint = async (options) => {
|
|
|
5075
5328
|
if (!isSplittableOxlintBatchError(error)) throw error;
|
|
5076
5329
|
if (batch.length <= 1) {
|
|
5077
5330
|
droppedFiles.push(...batch);
|
|
5331
|
+
if (firstDropReason === null && error instanceof Error) firstDropReason = error.message;
|
|
5078
5332
|
return [];
|
|
5079
5333
|
}
|
|
5080
5334
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
@@ -5086,7 +5340,8 @@ const runOxlint = async (options) => {
|
|
|
5086
5340
|
const previewCount = 3;
|
|
5087
5341
|
const previewFiles = droppedFiles.slice(0, previewCount).join(", ");
|
|
5088
5342
|
const remainderHint = droppedFiles.length > previewCount ? `, +${droppedFiles.length - previewCount} more` : "";
|
|
5089
|
-
|
|
5343
|
+
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
5344
|
+
onPartialFailure(`${droppedFiles.length} file(s) failed to lint and were skipped (${previewFiles}${remainderHint})${reasonHint}`);
|
|
5090
5345
|
}
|
|
5091
5346
|
return dedupeDiagnostics(allDiagnostics);
|
|
5092
5347
|
};
|
|
@@ -5133,7 +5388,8 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
5133
5388
|
result: {
|
|
5134
5389
|
diagnostics: result.diagnostics,
|
|
5135
5390
|
score: result.score,
|
|
5136
|
-
skippedChecks:
|
|
5391
|
+
skippedChecks: result.skippedChecks,
|
|
5392
|
+
...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
|
|
5137
5393
|
project: result.project,
|
|
5138
5394
|
elapsedMilliseconds: result.elapsedMilliseconds
|
|
5139
5395
|
}
|
|
@@ -5156,32 +5412,54 @@ const diagnose = async (directory, options = {}) => {
|
|
|
5156
5412
|
const lintIncludePaths = computeJsxIncludePaths(includePaths) ?? resolveLintIncludePaths(resolvedDirectory, userConfig);
|
|
5157
5413
|
const readFileLinesSync = createNodeReadFileLinesSync(resolvedDirectory);
|
|
5158
5414
|
const effectiveLint = options.lint ?? userConfig?.lint ?? true;
|
|
5415
|
+
const effectiveDeadCode = options.deadCode ?? userConfig?.deadCode ?? true;
|
|
5159
5416
|
const effectiveRespectInlineDisables = options.respectInlineDisables ?? userConfig?.respectInlineDisables ?? true;
|
|
5160
5417
|
const ignoredTags = new Set(userConfig?.ignore?.tags ?? []);
|
|
5418
|
+
const lintPromise = effectiveLint ? runOxlint({
|
|
5419
|
+
rootDirectory: resolvedDirectory,
|
|
5420
|
+
project: projectInfo,
|
|
5421
|
+
includePaths: lintIncludePaths,
|
|
5422
|
+
customRulesOnly: userConfig?.customRulesOnly ?? false,
|
|
5423
|
+
respectInlineDisables: effectiveRespectInlineDisables,
|
|
5424
|
+
adoptExistingLintConfig: userConfig?.adoptExistingLintConfig ?? true,
|
|
5425
|
+
ignoredTags,
|
|
5426
|
+
userConfig
|
|
5427
|
+
}).catch((error) => {
|
|
5428
|
+
console.error("Lint failed:", error);
|
|
5429
|
+
return EMPTY_DIAGNOSTICS;
|
|
5430
|
+
}) : Promise.resolve(EMPTY_DIAGNOSTICS);
|
|
5431
|
+
const shouldRunDeadCode = effectiveDeadCode && !isDiffMode;
|
|
5432
|
+
let deadCodeFailureReason = null;
|
|
5433
|
+
const deadCodePromise = shouldRunDeadCode ? checkDeadCode({
|
|
5434
|
+
rootDirectory: resolvedDirectory,
|
|
5435
|
+
userConfig
|
|
5436
|
+
}).catch((error) => {
|
|
5437
|
+
deadCodeFailureReason = error instanceof Error ? error.message : String(error);
|
|
5438
|
+
return EMPTY_DIAGNOSTICS;
|
|
5439
|
+
}) : Promise.resolve(EMPTY_DIAGNOSTICS);
|
|
5440
|
+
const [lintDiagnostics, deadCodeDiagnostics] = await Promise.all([lintPromise, deadCodePromise]);
|
|
5161
5441
|
const diagnostics = combineDiagnostics({
|
|
5162
|
-
lintDiagnostics
|
|
5163
|
-
rootDirectory: resolvedDirectory,
|
|
5164
|
-
project: projectInfo,
|
|
5165
|
-
includePaths: lintIncludePaths,
|
|
5166
|
-
customRulesOnly: userConfig?.customRulesOnly ?? false,
|
|
5167
|
-
respectInlineDisables: effectiveRespectInlineDisables,
|
|
5168
|
-
adoptExistingLintConfig: userConfig?.adoptExistingLintConfig ?? true,
|
|
5169
|
-
ignoredTags,
|
|
5170
|
-
userConfig
|
|
5171
|
-
}).catch((error) => {
|
|
5172
|
-
console.error("Lint failed:", error);
|
|
5173
|
-
return EMPTY_DIAGNOSTICS;
|
|
5174
|
-
}) : EMPTY_DIAGNOSTICS,
|
|
5442
|
+
lintDiagnostics,
|
|
5175
5443
|
directory: resolvedDirectory,
|
|
5176
5444
|
isDiffMode,
|
|
5177
5445
|
userConfig,
|
|
5178
5446
|
readFileLinesSync,
|
|
5179
|
-
respectInlineDisables: effectiveRespectInlineDisables
|
|
5447
|
+
respectInlineDisables: effectiveRespectInlineDisables,
|
|
5448
|
+
extraDiagnostics: deadCodeDiagnostics
|
|
5180
5449
|
});
|
|
5181
5450
|
const elapsedMilliseconds = globalThis.performance.now() - startTime;
|
|
5451
|
+
const score = await calculateScore(diagnostics);
|
|
5452
|
+
const skippedChecks = [];
|
|
5453
|
+
const skippedCheckReasons = {};
|
|
5454
|
+
if (deadCodeFailureReason !== null) {
|
|
5455
|
+
skippedChecks.push("dead-code");
|
|
5456
|
+
skippedCheckReasons["dead-code"] = deadCodeFailureReason;
|
|
5457
|
+
}
|
|
5182
5458
|
return {
|
|
5183
5459
|
diagnostics,
|
|
5184
|
-
score
|
|
5460
|
+
score,
|
|
5461
|
+
skippedChecks,
|
|
5462
|
+
...Object.keys(skippedCheckReasons).length > 0 ? { skippedCheckReasons } : {},
|
|
5185
5463
|
project: projectInfo,
|
|
5186
5464
|
elapsedMilliseconds
|
|
5187
5465
|
};
|