react-doctor 0.0.36 → 0.0.39

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.
@@ -0,0 +1,131 @@
1
+ //#region src/types.d.ts
2
+ type FailOnLevel = "error" | "warning" | "none";
3
+ type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "expo" | "react-native" | "tanstack-start" | "unknown";
4
+ interface ProjectInfo {
5
+ rootDirectory: string;
6
+ projectName: string;
7
+ reactVersion: string | null;
8
+ framework: Framework;
9
+ hasTypeScript: boolean;
10
+ hasReactCompiler: boolean;
11
+ sourceFileCount: number;
12
+ }
13
+ interface Diagnostic {
14
+ filePath: string;
15
+ plugin: string;
16
+ rule: string;
17
+ severity: "error" | "warning";
18
+ message: string;
19
+ help: string;
20
+ line: number;
21
+ column: number;
22
+ category: string;
23
+ weight?: number;
24
+ }
25
+ interface ScoreResult {
26
+ score: number;
27
+ label: string;
28
+ }
29
+ interface ReactDoctorIgnoreConfig {
30
+ rules?: string[];
31
+ files?: string[];
32
+ }
33
+ interface ReactDoctorConfig {
34
+ ignore?: ReactDoctorIgnoreConfig;
35
+ lint?: boolean;
36
+ deadCode?: boolean;
37
+ verbose?: boolean;
38
+ diff?: boolean | string;
39
+ failOn?: FailOnLevel;
40
+ customRulesOnly?: boolean;
41
+ share?: boolean;
42
+ textComponents?: string[];
43
+ }
44
+ //#endregion
45
+ //#region src/core/calculate-score-locally.d.ts
46
+ declare const calculateScoreLocally: (diagnostics: Diagnostic[]) => ScoreResult;
47
+ //#endregion
48
+ //#region src/utils/calculate-score-browser.d.ts
49
+ declare const calculateScore: (diagnostics: Diagnostic[]) => Promise<ScoreResult | null>;
50
+ //#endregion
51
+ //#region src/adapters/browser/diagnose.d.ts
52
+ interface BrowserDiagnoseInput {
53
+ rootDirectory: string;
54
+ project: ProjectInfo;
55
+ projectFiles: Record<string, string>;
56
+ lintDiagnostics: Diagnostic[];
57
+ deadCodeDiagnostics?: Diagnostic[];
58
+ userConfig?: ReactDoctorConfig | null;
59
+ score?: ScoreResult | null;
60
+ }
61
+ interface BrowserDiagnoseResult {
62
+ diagnostics: Diagnostic[];
63
+ score: ScoreResult | null;
64
+ project: ProjectInfo;
65
+ elapsedMilliseconds: number;
66
+ }
67
+ declare const diagnose: (input: BrowserDiagnoseInput) => Promise<BrowserDiagnoseResult>;
68
+ //#endregion
69
+ //#region src/core/diagnose-core.d.ts
70
+ interface DiagnoseCoreOptions {
71
+ lint?: boolean;
72
+ deadCode?: boolean;
73
+ includePaths?: string[];
74
+ lintIncludePaths?: string[] | undefined;
75
+ }
76
+ interface DiagnoseCoreResult {
77
+ diagnostics: Diagnostic[];
78
+ score: ScoreResult | null;
79
+ project: ProjectInfo;
80
+ elapsedMilliseconds: number;
81
+ }
82
+ interface DiagnoseRunnerContext {
83
+ resolvedDirectory: string;
84
+ projectInfo: ProjectInfo;
85
+ userConfig: ReactDoctorConfig | null;
86
+ lintIncludePaths: string[] | undefined;
87
+ isDiffMode: boolean;
88
+ }
89
+ interface DiagnoseCoreDeps {
90
+ rootDirectory: string;
91
+ readFileLinesSync: (filePath: string) => string[] | null;
92
+ loadUserConfig: () => ReactDoctorConfig | null;
93
+ discoverProjectInfo: () => ProjectInfo;
94
+ calculateDiagnosticsScore: (diagnostics: Diagnostic[]) => Promise<ScoreResult | null>;
95
+ getExtraDiagnostics?: () => Diagnostic[];
96
+ createRunners: (context: DiagnoseRunnerContext) => {
97
+ runLint: () => Promise<Diagnostic[]>;
98
+ runDeadCode: () => Promise<Diagnostic[]>;
99
+ };
100
+ }
101
+ declare const diagnoseCore: (deps: DiagnoseCoreDeps, options?: DiagnoseCoreOptions) => Promise<DiagnoseCoreResult>;
102
+ //#endregion
103
+ //#region src/adapters/browser/diagnose-browser.d.ts
104
+ interface DiagnoseBrowserInput {
105
+ rootDirectory: string;
106
+ project: ProjectInfo;
107
+ projectFiles: Record<string, string>;
108
+ userConfig?: ReactDoctorConfig | null;
109
+ runOxlint: (input: {
110
+ lintIncludePaths: string[] | undefined;
111
+ customRulesOnly: boolean;
112
+ }) => Promise<Diagnostic[]>;
113
+ }
114
+ declare const diagnoseBrowser: (input: DiagnoseBrowserInput, options?: DiagnoseCoreOptions) => Promise<DiagnoseCoreResult>;
115
+ //#endregion
116
+ //#region src/adapters/browser/process-browser-diagnostics.d.ts
117
+ interface ProcessBrowserDiagnosticsInput {
118
+ rootDirectory: string;
119
+ projectFiles: Record<string, string>;
120
+ diagnostics: Diagnostic[];
121
+ userConfig?: ReactDoctorConfig | null;
122
+ score?: ScoreResult | null;
123
+ }
124
+ interface ProcessBrowserDiagnosticsResult {
125
+ diagnostics: Diagnostic[];
126
+ score: ScoreResult | null;
127
+ }
128
+ declare const processBrowserDiagnostics: (input: ProcessBrowserDiagnosticsInput) => Promise<ProcessBrowserDiagnosticsResult>;
129
+ //#endregion
130
+ export { ScoreResult as _, diagnoseBrowser as a, diagnoseCore as c, diagnose as d, calculateScore as f, ReactDoctorConfig as g, ProjectInfo as h, DiagnoseBrowserInput as i, BrowserDiagnoseInput as l, Diagnostic as m, ProcessBrowserDiagnosticsResult as n, DiagnoseCoreOptions as o, calculateScoreLocally as p, processBrowserDiagnostics as r, DiagnoseCoreResult as s, ProcessBrowserDiagnosticsInput as t, BrowserDiagnoseResult as u };
131
+ //# sourceMappingURL=process-browser-diagnostics-Cahx3_oy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-browser-diagnostics-Cahx3_oy.d.ts","names":[],"sources":["../src/types.ts","../src/core/calculate-score-locally.ts","../src/utils/calculate-score-browser.ts","../src/adapters/browser/diagnose.ts","../src/core/diagnose-core.ts","../src/adapters/browser/diagnose-browser.ts","../src/adapters/browser/process-browser-diagnostics.ts"],"mappings":";KAAY,WAAA;AAAA,KAEA,SAAA;AAAA,UAWK,WAAA;EACf,aAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA,EAAW,SAAA;EACX,aAAA;EACA,gBAAA;EACA,eAAA;AAAA;AAAA,UAiCe,UAAA;EACf,QAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;AAAA;AAAA,UA4Be,WAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAkEe,uBAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAGe,iBAAA;EACf,MAAA,GAAS,uBAAA;EACT,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,MAAA,GAAS,WAAA;EACT,eAAA;EACA,KAAA;EACA,cAAA;AAAA;;;cCvIW,qBAAA,GAAyB,WAAA,EAAa,UAAA,OAAe,WAAA;;;cChCrD,cAAA,GAAwB,WAAA,EAAa,UAAA,OAAe,OAAA,CAAQ,WAAA;;;UCDxD,oBAAA;EACf,aAAA;EACA,OAAA,EAAS,WAAA;EACT,YAAA,EAAc,MAAA;EACd,eAAA,EAAiB,UAAA;EACjB,mBAAA,GAAsB,UAAA;EACtB,UAAA,GAAa,iBAAA;EACb,KAAA,GAAQ,WAAA;AAAA;AAAA,UAGO,qBAAA;EACf,WAAA,EAAa,UAAA;EACb,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,WAAA;EACT,mBAAA;AAAA;AAAA,cAGW,QAAA,GAAkB,KAAA,EAAO,oBAAA,KAAuB,OAAA,CAAQ,qBAAA;;;UCjBpD,mBAAA;EACf,IAAA;EACA,QAAA;EACA,YAAA;EACA,gBAAA;AAAA;AAAA,UAGe,kBAAA;EACf,WAAA,EAAa,UAAA;EACb,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,WAAA;EACT,mBAAA;AAAA;AAAA,UAGe,qBAAA;EACf,iBAAA;EACA,WAAA,EAAa,WAAA;EACb,UAAA,EAAY,iBAAA;EACZ,gBAAA;EACA,UAAA;AAAA;AAAA,UAGe,gBAAA;EACf,aAAA;EACA,iBAAA,GAAoB,QAAA;EACpB,cAAA,QAAsB,iBAAA;EACtB,mBAAA,QAA2B,WAAA;EAC3B,yBAAA,GAA4B,WAAA,EAAa,UAAA,OAAiB,OAAA,CAAQ,WAAA;EAClE,mBAAA,SAA4B,UAAA;EAC5B,aAAA,GAAgB,OAAA,EAAS,qBAAA;IACvB,OAAA,QAAe,OAAA,CAAQ,UAAA;IACvB,WAAA,QAAmB,OAAA,CAAQ,UAAA;EAAA;AAAA;AAAA,cAIlB,YAAA,GACX,IAAA,EAAM,gBAAA,EACN,OAAA,GAAS,mBAAA,KACR,OAAA,CAAQ,kBAAA;;;UCrCM,oBAAA;EACf,aAAA;EACA,OAAA,EAAS,WAAA;EACT,YAAA,EAAc,MAAA;EACd,UAAA,GAAa,iBAAA;EACb,SAAA,GAAY,KAAA;IACV,gBAAA;IACA,eAAA;EAAA,MACI,OAAA,CAAQ,UAAA;AAAA;AAAA,cAGH,eAAA,GACX,KAAA,EAAO,oBAAA,EACP,OAAA,GAAS,mBAAA,KAAwB,OAAA,CAAA,kBAAA;;;UCdlB,8BAAA;EACf,aAAA;EACA,YAAA,EAAc,MAAA;EACd,WAAA,EAAa,UAAA;EACb,UAAA,GAAa,iBAAA;EACb,KAAA,GAAQ,WAAA;AAAA;AAAA,UAGO,+BAAA;EACf,WAAA,EAAa,UAAA;EACb,KAAA,EAAO,WAAA;AAAA;AAAA,cAGI,yBAAA,GACX,KAAA,EAAO,8BAAA,KACN,OAAA,CAAQ,+BAAA"}
@@ -0,0 +1,365 @@
1
+ //#region src/constants.ts
2
+ const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
3
+ const PERFECT_SCORE = 100;
4
+ const SCORE_GOOD_THRESHOLD = 75;
5
+ const SCORE_OK_THRESHOLD = 50;
6
+ const SCORE_API_URL = "https://www.react.doctor/api/score";
7
+ const FETCH_TIMEOUT_MS = 1e4;
8
+ const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
9
+ const ERROR_RULE_PENALTY = 1.5;
10
+ const WARNING_RULE_PENALTY = .75;
11
+ const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
12
+
13
+ //#endregion
14
+ //#region src/core/calculate-score-locally.ts
15
+ const getScoreLabel = (score) => {
16
+ if (score >= SCORE_GOOD_THRESHOLD) return "Great";
17
+ if (score >= SCORE_OK_THRESHOLD) return "Needs work";
18
+ return "Critical";
19
+ };
20
+ const countUniqueRules = (diagnostics) => {
21
+ const errorRules = /* @__PURE__ */ new Set();
22
+ const warningRules = /* @__PURE__ */ new Set();
23
+ for (const diagnostic of diagnostics) {
24
+ const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
25
+ if (diagnostic.severity === "error") errorRules.add(ruleKey);
26
+ else warningRules.add(ruleKey);
27
+ }
28
+ return {
29
+ errorRuleCount: errorRules.size,
30
+ warningRuleCount: warningRules.size
31
+ };
32
+ };
33
+ const scoreFromRuleCounts = (errorRuleCount, warningRuleCount) => {
34
+ const penalty = errorRuleCount * ERROR_RULE_PENALTY + warningRuleCount * WARNING_RULE_PENALTY;
35
+ return Math.max(0, Math.round(PERFECT_SCORE - penalty));
36
+ };
37
+ const calculateScoreLocally = (diagnostics) => {
38
+ const { errorRuleCount, warningRuleCount } = countUniqueRules(diagnostics);
39
+ const score = scoreFromRuleCounts(errorRuleCount, warningRuleCount);
40
+ return {
41
+ score,
42
+ label: getScoreLabel(score)
43
+ };
44
+ };
45
+
46
+ //#endregion
47
+ //#region src/core/try-score-from-api.ts
48
+ const parseScoreResult = (value) => {
49
+ if (typeof value !== "object" || value === null) return null;
50
+ if (!("score" in value) || !("label" in value)) return null;
51
+ const scoreValue = Reflect.get(value, "score");
52
+ const labelValue = Reflect.get(value, "label");
53
+ if (typeof scoreValue !== "number" || typeof labelValue !== "string") return null;
54
+ return {
55
+ score: scoreValue,
56
+ label: labelValue
57
+ };
58
+ };
59
+ const tryScoreFromApi = async (diagnostics, fetchImplementation) => {
60
+ const controller = new AbortController();
61
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
62
+ try {
63
+ const response = await fetchImplementation(SCORE_API_URL, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: JSON.stringify({ diagnostics }),
67
+ signal: controller.signal
68
+ });
69
+ if (!response.ok) return null;
70
+ return parseScoreResult(await response.json());
71
+ } catch {
72
+ return null;
73
+ } finally {
74
+ clearTimeout(timeoutId);
75
+ }
76
+ };
77
+
78
+ //#endregion
79
+ //#region src/utils/calculate-score-browser.ts
80
+ const calculateScore = async (diagnostics) => await tryScoreFromApi(diagnostics, fetch) ?? calculateScoreLocally(diagnostics);
81
+
82
+ //#endregion
83
+ //#region src/utils/match-glob-pattern.ts
84
+ const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
85
+ const compileGlobPattern = (pattern) => {
86
+ const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\//, "");
87
+ let regexSource = "^";
88
+ let characterIndex = 0;
89
+ while (characterIndex < normalizedPattern.length) if (normalizedPattern[characterIndex] === "*" && normalizedPattern[characterIndex + 1] === "*") if (normalizedPattern[characterIndex + 2] === "/") {
90
+ regexSource += "(?:.+/)?";
91
+ characterIndex += 3;
92
+ } else {
93
+ regexSource += ".*";
94
+ characterIndex += 2;
95
+ }
96
+ else if (normalizedPattern[characterIndex] === "*") {
97
+ regexSource += "[^/]*";
98
+ characterIndex++;
99
+ } else if (normalizedPattern[characterIndex] === "?") {
100
+ regexSource += "[^/]";
101
+ characterIndex++;
102
+ } else {
103
+ regexSource += normalizedPattern[characterIndex].replace(REGEX_SPECIAL_CHARACTERS, "\\$&");
104
+ characterIndex++;
105
+ }
106
+ regexSource += "$";
107
+ return new RegExp(regexSource);
108
+ };
109
+
110
+ //#endregion
111
+ //#region src/utils/is-ignored-file.ts
112
+ const toRelativePath = (filePath, rootDirectory) => {
113
+ const normalizedFilePath = filePath.replace(/\\/g, "/");
114
+ const normalizedRoot = rootDirectory.replace(/\\/g, "/").replace(/\/$/, "") + "/";
115
+ if (normalizedFilePath.startsWith(normalizedRoot)) return normalizedFilePath.slice(normalizedRoot.length);
116
+ return normalizedFilePath.replace(/^\.\//, "");
117
+ };
118
+ const compileIgnoredFilePatterns = (userConfig) => Array.isArray(userConfig?.ignore?.files) ? userConfig.ignore.files.map(compileGlobPattern) : [];
119
+ const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
120
+ if (patterns.length === 0) return false;
121
+ const relativePath = toRelativePath(filePath, rootDirectory);
122
+ return patterns.some((pattern) => pattern.test(relativePath));
123
+ };
124
+
125
+ //#endregion
126
+ //#region src/utils/filter-diagnostics.ts
127
+ const resolveCandidateReadPath = (rootDirectory, filePath) => {
128
+ const normalizedFile = filePath.replace(/\\/g, "/");
129
+ if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile) || /^[a-zA-Z]:\\/.test(filePath)) return filePath;
130
+ return `${rootDirectory.replace(/\\/g, "/").replace(/\/$/, "")}/${normalizedFile.replace(/^\.\//, "")}`;
131
+ };
132
+ const OPENING_TAG_PATTERN = /<([A-Z][\w.]*)/;
133
+ const DISABLE_NEXT_LINE_PATTERN = /\/\/\s*react-doctor-disable-next-line\b(?:\s+(.+))?/;
134
+ const DISABLE_LINE_PATTERN = /\/\/\s*react-doctor-disable-line\b(?:\s+(.+))?/;
135
+ const createFileLinesCache = (rootDirectory, readFileLinesSync) => {
136
+ const cache = /* @__PURE__ */ new Map();
137
+ return (filePath) => {
138
+ const cached = cache.get(filePath);
139
+ if (cached !== void 0) return cached;
140
+ const lines = readFileLinesSync(resolveCandidateReadPath(rootDirectory, filePath));
141
+ cache.set(filePath, lines);
142
+ return lines;
143
+ };
144
+ };
145
+ const isInsideTextComponent = (lines, diagnosticLine, textComponentNames) => {
146
+ for (let lineIndex = diagnosticLine - 1; lineIndex >= 0; lineIndex--) {
147
+ const match = lines[lineIndex].match(OPENING_TAG_PATTERN);
148
+ if (!match) continue;
149
+ const fullTagName = match[1];
150
+ const leafTagName = fullTagName.includes(".") ? fullTagName.split(".").at(-1) ?? fullTagName : fullTagName;
151
+ return textComponentNames.has(fullTagName) || textComponentNames.has(leafTagName);
152
+ }
153
+ return false;
154
+ };
155
+ const isRuleSuppressed = (commentRules, ruleId) => {
156
+ if (!commentRules?.trim()) return true;
157
+ return commentRules.split(/[,\s]+/).some((rule) => rule.trim() === ruleId);
158
+ };
159
+ const filterIgnoredDiagnostics = (diagnostics, config, rootDirectory, readFileLinesSync) => {
160
+ const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules : []);
161
+ const ignoredFilePatterns = compileIgnoredFilePatterns(config);
162
+ const textComponentNames = new Set(Array.isArray(config.textComponents) ? config.textComponents : []);
163
+ const hasTextComponents = textComponentNames.size > 0;
164
+ const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);
165
+ return diagnostics.filter((diagnostic) => {
166
+ const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
167
+ if (ignoredRules.has(ruleIdentifier)) return false;
168
+ if (isFileIgnoredByPatterns(diagnostic.filePath, rootDirectory, ignoredFilePatterns)) return false;
169
+ if (hasTextComponents && diagnostic.rule === "rn-no-raw-text" && diagnostic.line > 0) {
170
+ const lines = getFileLines(diagnostic.filePath);
171
+ if (lines && isInsideTextComponent(lines, diagnostic.line, textComponentNames)) return false;
172
+ }
173
+ return true;
174
+ });
175
+ };
176
+ const filterInlineSuppressions = (diagnostics, rootDirectory, readFileLinesSync) => {
177
+ const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);
178
+ return diagnostics.filter((diagnostic) => {
179
+ if (diagnostic.line <= 0) return true;
180
+ const lines = getFileLines(diagnostic.filePath);
181
+ if (!lines) return true;
182
+ const ruleId = `${diagnostic.plugin}/${diagnostic.rule}`;
183
+ const currentLine = lines[diagnostic.line - 1];
184
+ if (currentLine) {
185
+ const lineMatch = currentLine.match(DISABLE_LINE_PATTERN);
186
+ if (lineMatch && isRuleSuppressed(lineMatch[1], ruleId)) return false;
187
+ }
188
+ if (diagnostic.line >= 2) {
189
+ const previousLine = lines[diagnostic.line - 2];
190
+ if (previousLine) {
191
+ const nextLineMatch = previousLine.match(DISABLE_NEXT_LINE_PATTERN);
192
+ if (nextLineMatch && isRuleSuppressed(nextLineMatch[1], ruleId)) return false;
193
+ }
194
+ }
195
+ return true;
196
+ });
197
+ };
198
+
199
+ //#endregion
200
+ //#region src/utils/merge-and-filter-diagnostics.ts
201
+ const mergeAndFilterDiagnostics = (mergedDiagnostics, directory, userConfig, readFileLinesSync) => {
202
+ return filterInlineSuppressions(userConfig ? filterIgnoredDiagnostics(mergedDiagnostics, userConfig, directory, readFileLinesSync) : mergedDiagnostics, directory, readFileLinesSync);
203
+ };
204
+
205
+ //#endregion
206
+ //#region src/core/build-result.ts
207
+ const buildDiagnoseTimedResult = async (input) => {
208
+ const diagnostics = mergeAndFilterDiagnostics(input.mergedDiagnostics, input.rootDirectory, input.userConfig, input.readFileLinesSync);
209
+ const elapsedMilliseconds = globalThis.performance.now() - input.startTime;
210
+ return {
211
+ diagnostics,
212
+ score: input.score !== void 0 ? input.score : await input.calculateDiagnosticsScore(diagnostics),
213
+ elapsedMilliseconds
214
+ };
215
+ };
216
+
217
+ //#endregion
218
+ //#region src/adapters/browser/create-browser-read-file-lines.ts
219
+ const normalizeKey = (rootDirectory, filePath) => {
220
+ const normalizedRoot = rootDirectory.replace(/\\/g, "/").replace(/\/$/, "");
221
+ const normalizedPath = filePath.replace(/\\/g, "/");
222
+ if (normalizedPath.startsWith(normalizedRoot + "/")) return normalizedPath.slice(normalizedRoot.length + 1);
223
+ return normalizedPath.replace(/^\.\//, "");
224
+ };
225
+ const createBrowserReadFileLinesSync = (rootDirectory, projectFiles) => {
226
+ return (absoluteOrRelativePath) => {
227
+ const content = projectFiles[normalizeKey(rootDirectory, absoluteOrRelativePath)];
228
+ if (content === void 0) return null;
229
+ return content.split("\n");
230
+ };
231
+ };
232
+
233
+ //#endregion
234
+ //#region src/adapters/browser/diagnose.ts
235
+ const diagnose = async (input) => {
236
+ if (!input.project.reactVersion) throw new Error("No React dependency found in package.json");
237
+ const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);
238
+ const userConfig = input.userConfig ?? null;
239
+ const deadCodeDiagnostics = input.deadCodeDiagnostics ?? [];
240
+ const mergedDiagnostics = [...input.lintDiagnostics, ...deadCodeDiagnostics];
241
+ const startTime = globalThis.performance.now();
242
+ const timed = await buildDiagnoseTimedResult({
243
+ mergedDiagnostics,
244
+ rootDirectory: input.rootDirectory,
245
+ userConfig,
246
+ readFileLinesSync,
247
+ startTime,
248
+ score: input.score,
249
+ calculateDiagnosticsScore: calculateScore
250
+ });
251
+ return {
252
+ diagnostics: timed.diagnostics,
253
+ score: timed.score,
254
+ project: input.project,
255
+ elapsedMilliseconds: timed.elapsedMilliseconds
256
+ };
257
+ };
258
+
259
+ //#endregion
260
+ //#region src/core/build-diagnose-result.ts
261
+ const buildDiagnoseResult = (params) => ({
262
+ diagnostics: params.diagnostics,
263
+ score: params.score,
264
+ project: params.project,
265
+ elapsedMilliseconds: params.elapsedMilliseconds
266
+ });
267
+
268
+ //#endregion
269
+ //#region src/utils/jsx-include-paths.ts
270
+ const computeJsxIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
271
+
272
+ //#endregion
273
+ //#region src/core/diagnose-core.ts
274
+ const diagnoseCore = async (deps, options = {}) => {
275
+ const { includePaths = [] } = options;
276
+ const isDiffMode = includePaths.length > 0;
277
+ const startTime = globalThis.performance.now();
278
+ const resolvedDirectory = deps.rootDirectory;
279
+ const projectInfo = deps.discoverProjectInfo();
280
+ const userConfig = deps.loadUserConfig();
281
+ const effectiveLint = options.lint ?? userConfig?.lint ?? true;
282
+ const effectiveDeadCode = options.deadCode ?? userConfig?.deadCode ?? true;
283
+ if (!projectInfo.reactVersion) throw new Error("No React dependency found in package.json");
284
+ const lintIncludePaths = options.lintIncludePaths !== void 0 ? options.lintIncludePaths : computeJsxIncludePaths(includePaths);
285
+ const { runLint, runDeadCode } = deps.createRunners({
286
+ resolvedDirectory,
287
+ projectInfo,
288
+ userConfig,
289
+ lintIncludePaths,
290
+ isDiffMode
291
+ });
292
+ const emptyDiagnostics = [];
293
+ const lintPromise = effectiveLint ? runLint().catch((error) => {
294
+ console.error("Lint failed:", error);
295
+ return emptyDiagnostics;
296
+ }) : Promise.resolve(emptyDiagnostics);
297
+ const deadCodePromise = effectiveDeadCode && !isDiffMode ? runDeadCode().catch((error) => {
298
+ console.error("Dead code analysis failed:", error);
299
+ return emptyDiagnostics;
300
+ }) : Promise.resolve(emptyDiagnostics);
301
+ const [lintDiagnostics, deadCodeDiagnostics] = await Promise.all([lintPromise, deadCodePromise]);
302
+ const environmentDiagnostics = deps.getExtraDiagnostics?.() ?? [];
303
+ const timed = await buildDiagnoseTimedResult({
304
+ mergedDiagnostics: [
305
+ ...lintDiagnostics,
306
+ ...deadCodeDiagnostics,
307
+ ...environmentDiagnostics
308
+ ],
309
+ rootDirectory: resolvedDirectory,
310
+ userConfig,
311
+ readFileLinesSync: deps.readFileLinesSync,
312
+ startTime,
313
+ calculateDiagnosticsScore: deps.calculateDiagnosticsScore
314
+ });
315
+ return buildDiagnoseResult({
316
+ diagnostics: timed.diagnostics,
317
+ score: timed.score,
318
+ project: projectInfo,
319
+ elapsedMilliseconds: timed.elapsedMilliseconds
320
+ });
321
+ };
322
+
323
+ //#endregion
324
+ //#region src/adapters/browser/diagnose-browser.ts
325
+ const diagnoseBrowser = async (input, options = {}) => {
326
+ const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);
327
+ return diagnoseCore({
328
+ rootDirectory: input.rootDirectory,
329
+ readFileLinesSync,
330
+ loadUserConfig: () => input.userConfig ?? null,
331
+ discoverProjectInfo: () => input.project,
332
+ calculateDiagnosticsScore: calculateScore,
333
+ createRunners: ({ lintIncludePaths, userConfig }) => ({
334
+ runLint: () => input.runOxlint({
335
+ lintIncludePaths,
336
+ customRulesOnly: userConfig?.customRulesOnly ?? false
337
+ }),
338
+ runDeadCode: async () => []
339
+ })
340
+ }, options);
341
+ };
342
+
343
+ //#endregion
344
+ //#region src/adapters/browser/process-browser-diagnostics.ts
345
+ const processBrowserDiagnostics = async (input) => {
346
+ const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);
347
+ const userConfig = input.userConfig ?? null;
348
+ const timed = await buildDiagnoseTimedResult({
349
+ mergedDiagnostics: input.diagnostics,
350
+ rootDirectory: input.rootDirectory,
351
+ userConfig,
352
+ readFileLinesSync,
353
+ startTime: globalThis.performance.now(),
354
+ score: input.score,
355
+ calculateDiagnosticsScore: calculateScore
356
+ });
357
+ return {
358
+ diagnostics: timed.diagnostics,
359
+ score: timed.score
360
+ };
361
+ };
362
+
363
+ //#endregion
364
+ export { calculateScore as a, diagnose as i, diagnoseBrowser as n, calculateScoreLocally as o, diagnoseCore as r, processBrowserDiagnostics as t };
365
+ //# sourceMappingURL=process-browser-diagnostics-DpaZeYLI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-browser-diagnostics-DpaZeYLI.js","names":["calculateScoreBrowser","calculateScoreBrowser","calculateScoreBrowser"],"sources":["../src/constants.ts","../src/core/calculate-score-locally.ts","../src/core/try-score-from-api.ts","../src/utils/calculate-score-browser.ts","../src/utils/match-glob-pattern.ts","../src/utils/is-ignored-file.ts","../src/utils/filter-diagnostics.ts","../src/utils/merge-and-filter-diagnostics.ts","../src/core/build-result.ts","../src/adapters/browser/create-browser-read-file-lines.ts","../src/adapters/browser/diagnose.ts","../src/core/build-diagnose-result.ts","../src/utils/jsx-include-paths.ts","../src/core/diagnose-core.ts","../src/adapters/browser/diagnose-browser.ts","../src/adapters/browser/process-browser-diagnostics.ts"],"sourcesContent":["export const SOURCE_FILE_PATTERN = /\\.(tsx?|jsx?)$/;\n\nexport const JSX_FILE_PATTERN = /\\.(tsx|jsx)$/;\n\nexport const MILLISECONDS_PER_SECOND = 1000;\n\nexport const ERROR_PREVIEW_LENGTH_CHARS = 200;\n\nexport const PERFECT_SCORE = 100;\n\nexport const SCORE_GOOD_THRESHOLD = 75;\n\nexport const SCORE_OK_THRESHOLD = 50;\n\nexport const SCORE_BAR_WIDTH_CHARS = 50;\n\nexport const SUMMARY_BOX_HORIZONTAL_PADDING_CHARS = 1;\n\nexport const SUMMARY_BOX_OUTER_INDENT_CHARS = 2;\n\nexport const SCORE_API_URL = \"https://www.react.doctor/api/score\";\n\nexport const SHARE_BASE_URL = \"https://www.react.doctor/share\";\n\nexport const FETCH_TIMEOUT_MS = 10_000;\n\nexport const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\n// HACK: Windows CreateProcessW limits total command-line length to 32,767 chars.\n// Use a conservative threshold to leave room for the executable path and quoting overhead.\nexport const SPAWN_ARGS_MAX_LENGTH_CHARS = 24_000;\n\n// HACK: oxlint can SIGABRT on very large file sets due to memory pressure.\n// Cap each batch to avoid OOM crashes on projects with 100+ source files.\nexport const OXLINT_MAX_FILES_PER_BATCH = 500;\n\nexport const OFFLINE_MESSAGE = \"Score calculated locally (offline mode).\";\n\nexport const DEFAULT_BRANCH_CANDIDATES = [\"main\", \"master\"];\n\nexport const ERROR_RULE_PENALTY = 1.5;\n\nexport const WARNING_RULE_PENALTY = 0.75;\n\nexport const MAX_KNIP_RETRIES = 5;\n\nexport const OXLINT_NODE_REQUIREMENT = \"^20.19.0 || >=22.12.0\";\n\nexport const OXLINT_RECOMMENDED_NODE_MAJOR = 24;\n\nexport const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;\n\nexport const IGNORED_DIRECTORIES = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n","import {\n ERROR_RULE_PENALTY,\n PERFECT_SCORE,\n SCORE_GOOD_THRESHOLD,\n SCORE_OK_THRESHOLD,\n WARNING_RULE_PENALTY,\n} from \"../constants.js\";\nimport type { Diagnostic, ScoreResult } from \"../types.js\";\n\nconst getScoreLabel = (score: number): string => {\n if (score >= SCORE_GOOD_THRESHOLD) return \"Great\";\n if (score >= SCORE_OK_THRESHOLD) return \"Needs work\";\n return \"Critical\";\n};\n\nconst countUniqueRules = (\n diagnostics: Diagnostic[],\n): { errorRuleCount: number; warningRuleCount: number } => {\n const errorRules = new Set<string>();\n const warningRules = new Set<string>();\n\n for (const diagnostic of diagnostics) {\n const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;\n if (diagnostic.severity === \"error\") {\n errorRules.add(ruleKey);\n } else {\n warningRules.add(ruleKey);\n }\n }\n\n return { errorRuleCount: errorRules.size, warningRuleCount: warningRules.size };\n};\n\nconst scoreFromRuleCounts = (errorRuleCount: number, warningRuleCount: number): number => {\n const penalty = errorRuleCount * ERROR_RULE_PENALTY + warningRuleCount * WARNING_RULE_PENALTY;\n return Math.max(0, Math.round(PERFECT_SCORE - penalty));\n};\n\nexport const calculateScoreLocally = (diagnostics: Diagnostic[]): ScoreResult => {\n const { errorRuleCount, warningRuleCount } = countUniqueRules(diagnostics);\n const score = scoreFromRuleCounts(errorRuleCount, warningRuleCount);\n return { score, label: getScoreLabel(score) };\n};\n","import { FETCH_TIMEOUT_MS, SCORE_API_URL } from \"../constants.js\";\nimport type { Diagnostic, ScoreResult } from \"../types.js\";\n\ninterface ScoreRequestFetch {\n (input: string | URL, init?: RequestInit): Promise<Response>;\n}\n\nconst parseScoreResult = (value: unknown): ScoreResult | null => {\n if (typeof value !== \"object\" || value === null) return null;\n if (!(\"score\" in value) || !(\"label\" in value)) return null;\n const scoreValue = Reflect.get(value, \"score\");\n const labelValue = Reflect.get(value, \"label\");\n if (typeof scoreValue !== \"number\" || typeof labelValue !== \"string\") return null;\n return { score: scoreValue, label: labelValue };\n};\n\nexport const tryScoreFromApi = async (\n diagnostics: Diagnostic[],\n fetchImplementation: ScoreRequestFetch,\n): Promise<ScoreResult | null> => {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n\n try {\n const response = await fetchImplementation(SCORE_API_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ diagnostics }),\n signal: controller.signal,\n });\n\n if (!response.ok) return null;\n\n return parseScoreResult(await response.json());\n } catch {\n return null;\n } finally {\n clearTimeout(timeoutId);\n }\n};\n","import type { Diagnostic, ScoreResult } from \"../types.js\";\nimport { calculateScoreLocally } from \"../core/calculate-score-locally.js\";\nimport { tryScoreFromApi } from \"../core/try-score-from-api.js\";\n\nexport { calculateScoreLocally } from \"../core/calculate-score-locally.js\";\n\nexport const calculateScore = async (diagnostics: Diagnostic[]): Promise<ScoreResult | null> =>\n (await tryScoreFromApi(diagnostics, fetch)) ?? calculateScoreLocally(diagnostics);\n","const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\\]\\\\]/g;\n\nexport const compileGlobPattern = (pattern: string): RegExp => {\n const normalizedPattern = pattern.replace(/\\\\/g, \"/\").replace(/^\\//, \"\");\n\n let regexSource = \"^\";\n let characterIndex = 0;\n\n while (characterIndex < normalizedPattern.length) {\n if (\n normalizedPattern[characterIndex] === \"*\" &&\n normalizedPattern[characterIndex + 1] === \"*\"\n ) {\n if (normalizedPattern[characterIndex + 2] === \"/\") {\n regexSource += \"(?:.+/)?\";\n characterIndex += 3;\n } else {\n regexSource += \".*\";\n characterIndex += 2;\n }\n } else if (normalizedPattern[characterIndex] === \"*\") {\n regexSource += \"[^/]*\";\n characterIndex++;\n } else if (normalizedPattern[characterIndex] === \"?\") {\n regexSource += \"[^/]\";\n characterIndex++;\n } else {\n regexSource += normalizedPattern[characterIndex].replace(REGEX_SPECIAL_CHARACTERS, \"\\\\$&\");\n characterIndex++;\n }\n }\n\n regexSource += \"$\";\n return new RegExp(regexSource);\n};\n\nexport const matchGlobPattern = (filePath: string, pattern: string): boolean => {\n const normalizedPath = filePath.replace(/\\\\/g, \"/\");\n return compileGlobPattern(pattern).test(normalizedPath);\n};\n","import type { ReactDoctorConfig } from \"../types.js\";\nimport { compileGlobPattern } from \"./match-glob-pattern.js\";\n\nconst toRelativePath = (filePath: string, rootDirectory: string): string => {\n const normalizedFilePath = filePath.replace(/\\\\/g, \"/\");\n const normalizedRoot = rootDirectory.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\") + \"/\";\n\n if (normalizedFilePath.startsWith(normalizedRoot)) {\n return normalizedFilePath.slice(normalizedRoot.length);\n }\n\n return normalizedFilePath.replace(/^\\.\\//, \"\");\n};\n\nexport const compileIgnoredFilePatterns = (userConfig: ReactDoctorConfig | null): RegExp[] =>\n Array.isArray(userConfig?.ignore?.files) ? userConfig.ignore.files.map(compileGlobPattern) : [];\n\nexport const isFileIgnoredByPatterns = (\n filePath: string,\n rootDirectory: string,\n patterns: RegExp[],\n): boolean => {\n if (patterns.length === 0) {\n return false;\n }\n\n const relativePath = toRelativePath(filePath, rootDirectory);\n return patterns.some((pattern) => pattern.test(relativePath));\n};\n","import type { Diagnostic, ReactDoctorConfig } from \"../types.js\";\nimport { compileIgnoredFilePatterns, isFileIgnoredByPatterns } from \"./is-ignored-file.js\";\n\nconst resolveCandidateReadPath = (rootDirectory: string, filePath: string): string => {\n const normalizedFile = filePath.replace(/\\\\/g, \"/\");\n if (\n normalizedFile.startsWith(\"/\") ||\n /^[a-zA-Z]:\\//.test(normalizedFile) ||\n /^[a-zA-Z]:\\\\/.test(filePath)\n ) {\n return filePath;\n }\n const root = rootDirectory.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n return `${root}/${normalizedFile.replace(/^\\.\\//, \"\")}`;\n};\n\nconst OPENING_TAG_PATTERN = /<([A-Z][\\w.]*)/;\nconst DISABLE_NEXT_LINE_PATTERN = /\\/\\/\\s*react-doctor-disable-next-line\\b(?:\\s+(.+))?/;\nconst DISABLE_LINE_PATTERN = /\\/\\/\\s*react-doctor-disable-line\\b(?:\\s+(.+))?/;\n\nconst createFileLinesCache = (\n rootDirectory: string,\n readFileLinesSync: (filePath: string) => string[] | null,\n) => {\n const cache = new Map<string, string[] | null>();\n\n return (filePath: string): string[] | null => {\n const cached = cache.get(filePath);\n if (cached !== undefined) return cached;\n const absolutePath = resolveCandidateReadPath(rootDirectory, filePath);\n const lines = readFileLinesSync(absolutePath);\n cache.set(filePath, lines);\n return lines;\n };\n};\n\nconst isInsideTextComponent = (\n lines: string[],\n diagnosticLine: number,\n textComponentNames: Set<string>,\n): boolean => {\n for (let lineIndex = diagnosticLine - 1; lineIndex >= 0; lineIndex--) {\n const match = lines[lineIndex].match(OPENING_TAG_PATTERN);\n if (!match) continue;\n const fullTagName = match[1];\n const leafTagName = fullTagName.includes(\".\")\n ? (fullTagName.split(\".\").at(-1) ?? fullTagName)\n : fullTagName;\n return textComponentNames.has(fullTagName) || textComponentNames.has(leafTagName);\n }\n return false;\n};\n\nconst isRuleSuppressed = (commentRules: string | undefined, ruleId: string): boolean => {\n if (!commentRules?.trim()) return true;\n return commentRules.split(/[,\\s]+/).some((rule) => rule.trim() === ruleId);\n};\n\nexport const filterIgnoredDiagnostics = (\n diagnostics: Diagnostic[],\n config: ReactDoctorConfig,\n rootDirectory: string,\n readFileLinesSync: (filePath: string) => string[] | null,\n): Diagnostic[] => {\n const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules : []);\n const ignoredFilePatterns = compileIgnoredFilePatterns(config);\n const textComponentNames = new Set(\n Array.isArray(config.textComponents) ? config.textComponents : [],\n );\n const hasTextComponents = textComponentNames.size > 0;\n const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);\n\n return diagnostics.filter((diagnostic) => {\n const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;\n if (ignoredRules.has(ruleIdentifier)) {\n return false;\n }\n\n if (isFileIgnoredByPatterns(diagnostic.filePath, rootDirectory, ignoredFilePatterns)) {\n return false;\n }\n\n if (hasTextComponents && diagnostic.rule === \"rn-no-raw-text\" && diagnostic.line > 0) {\n const lines = getFileLines(diagnostic.filePath);\n if (lines && isInsideTextComponent(lines, diagnostic.line, textComponentNames)) {\n return false;\n }\n }\n\n return true;\n });\n};\n\nexport const filterInlineSuppressions = (\n diagnostics: Diagnostic[],\n rootDirectory: string,\n readFileLinesSync: (filePath: string) => string[] | null,\n): Diagnostic[] => {\n const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);\n\n return diagnostics.filter((diagnostic) => {\n if (diagnostic.line <= 0) return true;\n\n const lines = getFileLines(diagnostic.filePath);\n if (!lines) return true;\n\n const ruleId = `${diagnostic.plugin}/${diagnostic.rule}`;\n\n const currentLine = lines[diagnostic.line - 1];\n if (currentLine) {\n const lineMatch = currentLine.match(DISABLE_LINE_PATTERN);\n if (lineMatch && isRuleSuppressed(lineMatch[1], ruleId)) return false;\n }\n\n if (diagnostic.line >= 2) {\n const previousLine = lines[diagnostic.line - 2];\n if (previousLine) {\n const nextLineMatch = previousLine.match(DISABLE_NEXT_LINE_PATTERN);\n if (nextLineMatch && isRuleSuppressed(nextLineMatch[1], ruleId)) return false;\n }\n }\n\n return true;\n });\n};\n","import type { Diagnostic, ReactDoctorConfig } from \"../types.js\";\nimport { filterIgnoredDiagnostics, filterInlineSuppressions } from \"./filter-diagnostics.js\";\n\nexport const mergeAndFilterDiagnostics = (\n mergedDiagnostics: Diagnostic[],\n directory: string,\n userConfig: ReactDoctorConfig | null,\n readFileLinesSync: (filePath: string) => string[] | null,\n): Diagnostic[] => {\n const filtered = userConfig\n ? filterIgnoredDiagnostics(mergedDiagnostics, userConfig, directory, readFileLinesSync)\n : mergedDiagnostics;\n return filterInlineSuppressions(filtered, directory, readFileLinesSync);\n};\n","import type { Diagnostic, ReactDoctorConfig, ScoreResult } from \"../types.js\";\nimport { mergeAndFilterDiagnostics } from \"../utils/merge-and-filter-diagnostics.js\";\n\nexport interface BuildDiagnoseResultInput {\n mergedDiagnostics: Diagnostic[];\n rootDirectory: string;\n userConfig: ReactDoctorConfig | null;\n readFileLinesSync: (filePath: string) => string[] | null;\n startTime: number;\n score?: ScoreResult | null;\n calculateDiagnosticsScore: (diagnostics: Diagnostic[]) => Promise<ScoreResult | null>;\n}\n\nexport interface BuildDiagnoseTimedResult {\n diagnostics: Diagnostic[];\n score: ScoreResult | null;\n elapsedMilliseconds: number;\n}\n\nexport const buildDiagnoseTimedResult = async (\n input: BuildDiagnoseResultInput,\n): Promise<BuildDiagnoseTimedResult> => {\n const diagnostics = mergeAndFilterDiagnostics(\n input.mergedDiagnostics,\n input.rootDirectory,\n input.userConfig,\n input.readFileLinesSync,\n );\n const elapsedMilliseconds = globalThis.performance.now() - input.startTime;\n const score =\n input.score !== undefined ? input.score : await input.calculateDiagnosticsScore(diagnostics);\n return { diagnostics, score, elapsedMilliseconds };\n};\n","const normalizeKey = (rootDirectory: string, filePath: string): string => {\n const normalizedRoot = rootDirectory.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n const normalizedPath = filePath.replace(/\\\\/g, \"/\");\n if (normalizedPath.startsWith(normalizedRoot + \"/\")) {\n return normalizedPath.slice(normalizedRoot.length + 1);\n }\n return normalizedPath.replace(/^\\.\\//, \"\");\n};\n\nexport const createBrowserReadFileLinesSync = (\n rootDirectory: string,\n projectFiles: Record<string, string>,\n): ((absoluteOrRelativePath: string) => string[] | null) => {\n return (absoluteOrRelativePath: string): string[] | null => {\n const key = normalizeKey(rootDirectory, absoluteOrRelativePath);\n const content = projectFiles[key];\n if (content === undefined) return null;\n return content.split(\"\\n\");\n };\n};\n","import type { Diagnostic, ProjectInfo, ReactDoctorConfig, ScoreResult } from \"../../types.js\";\nimport { buildDiagnoseTimedResult } from \"../../core/build-result.js\";\nimport { calculateScore as calculateScoreBrowser } from \"../../utils/calculate-score-browser.js\";\nimport { createBrowserReadFileLinesSync } from \"./create-browser-read-file-lines.js\";\n\nexport interface BrowserDiagnoseInput {\n rootDirectory: string;\n project: ProjectInfo;\n projectFiles: Record<string, string>;\n lintDiagnostics: Diagnostic[];\n deadCodeDiagnostics?: Diagnostic[];\n userConfig?: ReactDoctorConfig | null;\n score?: ScoreResult | null;\n}\n\nexport interface BrowserDiagnoseResult {\n diagnostics: Diagnostic[];\n score: ScoreResult | null;\n project: ProjectInfo;\n elapsedMilliseconds: number;\n}\n\nexport const diagnose = async (input: BrowserDiagnoseInput): Promise<BrowserDiagnoseResult> => {\n if (!input.project.reactVersion) {\n throw new Error(\"No React dependency found in package.json\");\n }\n\n const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);\n const userConfig = input.userConfig ?? null;\n const deadCodeDiagnostics = input.deadCodeDiagnostics ?? [];\n const mergedDiagnostics = [...input.lintDiagnostics, ...deadCodeDiagnostics];\n const startTime = globalThis.performance.now();\n\n const timed = await buildDiagnoseTimedResult({\n mergedDiagnostics,\n rootDirectory: input.rootDirectory,\n userConfig,\n readFileLinesSync,\n startTime,\n score: input.score,\n calculateDiagnosticsScore: calculateScoreBrowser,\n });\n\n return {\n diagnostics: timed.diagnostics,\n score: timed.score,\n project: input.project,\n elapsedMilliseconds: timed.elapsedMilliseconds,\n };\n};\n","import type { Diagnostic, ProjectInfo, ScoreResult } from \"../types.js\";\n\ninterface BuildDiagnoseResultParams {\n diagnostics: Diagnostic[];\n project: ProjectInfo;\n elapsedMilliseconds: number;\n score: ScoreResult | null;\n}\n\ninterface DiagnoseResultShape {\n diagnostics: Diagnostic[];\n score: ScoreResult | null;\n project: ProjectInfo;\n elapsedMilliseconds: number;\n}\n\nexport const buildDiagnoseResult = (params: BuildDiagnoseResultParams): DiagnoseResultShape => ({\n diagnostics: params.diagnostics,\n score: params.score,\n project: params.project,\n elapsedMilliseconds: params.elapsedMilliseconds,\n});\n","import { JSX_FILE_PATTERN } from \"../constants.js\";\n\nexport const computeJsxIncludePaths = (includePaths: string[]): string[] | undefined =>\n includePaths.length > 0\n ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath))\n : undefined;\n","import type { Diagnostic, ProjectInfo, ReactDoctorConfig, ScoreResult } from \"../types.js\";\nimport { buildDiagnoseResult } from \"./build-diagnose-result.js\";\nimport { buildDiagnoseTimedResult } from \"./build-result.js\";\nimport { computeJsxIncludePaths } from \"../utils/jsx-include-paths.js\";\n\nexport interface DiagnoseCoreOptions {\n lint?: boolean;\n deadCode?: boolean;\n includePaths?: string[];\n lintIncludePaths?: string[] | undefined;\n}\n\nexport interface DiagnoseCoreResult {\n diagnostics: Diagnostic[];\n score: ScoreResult | null;\n project: ProjectInfo;\n elapsedMilliseconds: number;\n}\n\nexport interface DiagnoseRunnerContext {\n resolvedDirectory: string;\n projectInfo: ProjectInfo;\n userConfig: ReactDoctorConfig | null;\n lintIncludePaths: string[] | undefined;\n isDiffMode: boolean;\n}\n\nexport interface DiagnoseCoreDeps {\n rootDirectory: string;\n readFileLinesSync: (filePath: string) => string[] | null;\n loadUserConfig: () => ReactDoctorConfig | null;\n discoverProjectInfo: () => ProjectInfo;\n calculateDiagnosticsScore: (diagnostics: Diagnostic[]) => Promise<ScoreResult | null>;\n getExtraDiagnostics?: () => Diagnostic[];\n createRunners: (context: DiagnoseRunnerContext) => {\n runLint: () => Promise<Diagnostic[]>;\n runDeadCode: () => Promise<Diagnostic[]>;\n };\n}\n\nexport const diagnoseCore = async (\n deps: DiagnoseCoreDeps,\n options: DiagnoseCoreOptions = {},\n): Promise<DiagnoseCoreResult> => {\n const { includePaths = [] } = options;\n const isDiffMode = includePaths.length > 0;\n\n const startTime = globalThis.performance.now();\n const resolvedDirectory = deps.rootDirectory;\n const projectInfo = deps.discoverProjectInfo();\n const userConfig = deps.loadUserConfig();\n\n const effectiveLint = options.lint ?? userConfig?.lint ?? true;\n const effectiveDeadCode = options.deadCode ?? userConfig?.deadCode ?? true;\n\n if (!projectInfo.reactVersion) {\n throw new Error(\"No React dependency found in package.json\");\n }\n\n const lintIncludePaths =\n options.lintIncludePaths !== undefined\n ? options.lintIncludePaths\n : computeJsxIncludePaths(includePaths);\n\n const { runLint, runDeadCode } = deps.createRunners({\n resolvedDirectory,\n projectInfo,\n userConfig,\n lintIncludePaths,\n isDiffMode,\n });\n\n const emptyDiagnostics: Diagnostic[] = [];\n\n const lintPromise = effectiveLint\n ? runLint().catch((error: unknown) => {\n console.error(\"Lint failed:\", error);\n return emptyDiagnostics;\n })\n : Promise.resolve(emptyDiagnostics);\n\n const deadCodePromise =\n effectiveDeadCode && !isDiffMode\n ? runDeadCode().catch((error: unknown) => {\n console.error(\"Dead code analysis failed:\", error);\n return emptyDiagnostics;\n })\n : Promise.resolve(emptyDiagnostics);\n\n const [lintDiagnostics, deadCodeDiagnostics] = await Promise.all([lintPromise, deadCodePromise]);\n const environmentDiagnostics = deps.getExtraDiagnostics?.() ?? [];\n const mergedDiagnostics = [...lintDiagnostics, ...deadCodeDiagnostics, ...environmentDiagnostics];\n const timed = await buildDiagnoseTimedResult({\n mergedDiagnostics,\n rootDirectory: resolvedDirectory,\n userConfig,\n readFileLinesSync: deps.readFileLinesSync,\n startTime,\n calculateDiagnosticsScore: deps.calculateDiagnosticsScore,\n });\n\n return buildDiagnoseResult({\n diagnostics: timed.diagnostics,\n score: timed.score,\n project: projectInfo,\n elapsedMilliseconds: timed.elapsedMilliseconds,\n });\n};\n","import type { Diagnostic, ProjectInfo, ReactDoctorConfig } from \"../../types.js\";\nimport type { DiagnoseCoreOptions } from \"../../core/diagnose-core.js\";\nimport { diagnoseCore } from \"../../core/diagnose-core.js\";\nimport { calculateScore as calculateScoreBrowser } from \"../../utils/calculate-score-browser.js\";\nimport { createBrowserReadFileLinesSync } from \"./create-browser-read-file-lines.js\";\n\nexport interface DiagnoseBrowserInput {\n rootDirectory: string;\n project: ProjectInfo;\n projectFiles: Record<string, string>;\n userConfig?: ReactDoctorConfig | null;\n runOxlint: (input: {\n lintIncludePaths: string[] | undefined;\n customRulesOnly: boolean;\n }) => Promise<Diagnostic[]>;\n}\n\nexport const diagnoseBrowser = async (\n input: DiagnoseBrowserInput,\n options: DiagnoseCoreOptions = {},\n) => {\n const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);\n\n return diagnoseCore(\n {\n rootDirectory: input.rootDirectory,\n readFileLinesSync,\n loadUserConfig: () => input.userConfig ?? null,\n discoverProjectInfo: () => input.project,\n calculateDiagnosticsScore: calculateScoreBrowser,\n createRunners: ({ lintIncludePaths, userConfig }) => ({\n runLint: () =>\n input.runOxlint({\n lintIncludePaths,\n customRulesOnly: userConfig?.customRulesOnly ?? false,\n }),\n runDeadCode: async () => [],\n }),\n },\n options,\n );\n};\n","import type { Diagnostic, ReactDoctorConfig, ScoreResult } from \"../../types.js\";\nimport { buildDiagnoseTimedResult } from \"../../core/build-result.js\";\nimport { calculateScore as calculateScoreBrowser } from \"../../utils/calculate-score-browser.js\";\nimport { createBrowserReadFileLinesSync } from \"./create-browser-read-file-lines.js\";\n\nexport interface ProcessBrowserDiagnosticsInput {\n rootDirectory: string;\n projectFiles: Record<string, string>;\n diagnostics: Diagnostic[];\n userConfig?: ReactDoctorConfig | null;\n score?: ScoreResult | null;\n}\n\nexport interface ProcessBrowserDiagnosticsResult {\n diagnostics: Diagnostic[];\n score: ScoreResult | null;\n}\n\nexport const processBrowserDiagnostics = async (\n input: ProcessBrowserDiagnosticsInput,\n): Promise<ProcessBrowserDiagnosticsResult> => {\n const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);\n const userConfig = input.userConfig ?? null;\n const timed = await buildDiagnoseTimedResult({\n mergedDiagnostics: input.diagnostics,\n rootDirectory: input.rootDirectory,\n userConfig,\n readFileLinesSync,\n startTime: globalThis.performance.now(),\n score: input.score,\n calculateDiagnosticsScore: calculateScoreBrowser,\n });\n return { diagnostics: timed.diagnostics, score: timed.score };\n};\n"],"mappings":"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"}
@@ -1,19 +1,32 @@
1
1
  ---
2
2
  name: react-doctor
3
- description: Run after making React changes to catch issues early. Use when reviewing code, finishing a feature, or fixing bugs in a React project.
4
- version: 1.0.0
3
+ description: Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics.
4
+ version: "1.0.0"
5
5
  ---
6
6
 
7
7
  # React Doctor
8
8
 
9
- Scans your React codebase for security, performance, correctness, and architecture issues. Outputs a 0-100 score with actionable diagnostics.
9
+ Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0100 health score.
10
10
 
11
- ## Usage
11
+ ## After making React code changes:
12
+
13
+ Run `npx -y react-doctor@latest . --verbose --diff` and check the score did not regress.
14
+
15
+ If the score dropped, fix the regressions before committing.
16
+
17
+ ## For general cleanup or code improvement:
18
+
19
+ Run `npx -y react-doctor@latest . --verbose` (without `--diff`) to scan the full codebase. Fix issues by severity — errors first, then warnings.
20
+
21
+ ## Command
12
22
 
13
23
  ```bash
14
24
  npx -y react-doctor@latest . --verbose --diff
15
25
  ```
16
26
 
17
- ## Workflow
18
-
19
- Run after making changes to catch issues early. Fix errors first, then re-run to verify the score improved.
27
+ | Flag | Purpose |
28
+ | ----------- | --------------------------------------------- |
29
+ | `.` | Scan current directory |
30
+ | `--verbose` | Show affected files and line numbers per rule |
31
+ | `--diff` | Only scan changed files vs base branch |
32
+ | `--score` | Output only the numeric score |
@@ -0,0 +1,2 @@
1
+ import { a as diagnoseBrowser, d as diagnose, f as calculateScore, i as DiagnoseBrowserInput, l as BrowserDiagnoseInput, n as ProcessBrowserDiagnosticsResult, o as DiagnoseCoreOptions, p as calculateScoreLocally, r as processBrowserDiagnostics, s as DiagnoseCoreResult, t as ProcessBrowserDiagnosticsInput, u as BrowserDiagnoseResult } from "./process-browser-diagnostics-Cahx3_oy.js";
2
+ export { type BrowserDiagnoseInput, type BrowserDiagnoseResult, type DiagnoseBrowserInput, type DiagnoseCoreOptions, type DiagnoseCoreResult, type ProcessBrowserDiagnosticsInput, type ProcessBrowserDiagnosticsResult, calculateScore, calculateScoreLocally, diagnose, diagnoseBrowser, processBrowserDiagnostics };
package/dist/worker.js ADDED
@@ -0,0 +1,3 @@
1
+ import { a as calculateScore, i as diagnose, n as diagnoseBrowser, o as calculateScoreLocally, t as processBrowserDiagnostics } from "./process-browser-diagnostics-DpaZeYLI.js";
2
+
3
+ export { calculateScore, calculateScoreLocally, diagnose, diagnoseBrowser, processBrowserDiagnostics };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.0.36",
3
+ "version": "0.0.39",
4
4
  "description": "Diagnose and fix performance issues in your React app",
5
5
  "keywords": [
6
6
  "diagnostics",
@@ -36,9 +36,17 @@
36
36
  "types": "./dist/index.d.ts",
37
37
  "default": "./dist/index.js"
38
38
  },
39
+ "./browser": {
40
+ "types": "./dist/browser.d.ts",
41
+ "default": "./dist/browser.js"
42
+ },
39
43
  "./oxlint-plugin": {
40
44
  "types": "./dist/react-doctor-plugin.d.ts",
41
45
  "default": "./dist/react-doctor-plugin.js"
46
+ },
47
+ "./worker": {
48
+ "types": "./dist/worker.d.ts",
49
+ "default": "./dist/worker.js"
42
50
  }
43
51
  },
44
52
  "dependencies": {