react-doctor 0.0.25 → 0.0.27

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/utils/get-diff-files.ts","../src/index.ts"],"mappings":";KAAY,SAAA;AAAA,UAEK,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,UAwBe,QAAA;EACf,aAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;AAAA;AAAA,UA2Ce,uBAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAGe,iBAAA;EACf,MAAA,GAAS,uBAAA;EACT,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;AAAA;;;cCpFW,WAAA,GAAe,SAAA,UAAmB,kBAAA,cAA8B,QAAA;AAAA,cAiBhE,iBAAA,GAAqB,SAAA;;;UCjFjB,eAAA;EACf,IAAA;EACA,QAAA;EACA,YAAA;AAAA;AAAA,UAGe,cAAA;EACf,WAAA,EAAa,UAAA;EACb,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,WAAA;EACT,mBAAA;AAAA;AAAA,cAGW,QAAA,GACX,SAAA,UACA,OAAA,GAAS,eAAA,KACR,OAAA,CAAQ,cAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/utils/get-diff-files.ts","../src/index.ts"],"mappings":";KAAY,SAAA;AAAA,UAEK,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,UAyBe,QAAA;EACf,aAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;AAAA;AAAA,UA2Ce,uBAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAGe,iBAAA;EACf,MAAA,GAAS,uBAAA;EACT,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;AAAA;;;cCrFW,WAAA,GAAe,SAAA,UAAmB,kBAAA,cAA8B,QAAA;AAAA,cAiBhE,iBAAA,GAAqB,SAAA;;;UCnFjB,eAAA;EACf,IAAA;EACA,QAAA;EACA,YAAA;AAAA;AAAA,UAGe,cAAA;EACf,WAAA,EAAa,UAAA;EACb,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,WAAA;EACT,mBAAA;AAAA;AAAA,cAGW,QAAA,GACX,SAAA,UACA,OAAA,GAAS,eAAA,KACR,OAAA,CAAQ,cAAA"}
package/dist/index.js CHANGED
@@ -13,14 +13,67 @@ const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?)$/;
13
13
  const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
14
14
  const ERROR_PREVIEW_LENGTH_CHARS = 200;
15
15
  const SCORE_API_URL = "https://www.react.doctor/api/score";
16
+ const FETCH_TIMEOUT_MS = 1e4;
16
17
  const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
18
+ const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
17
19
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
20
+ const MAX_KNIP_RETRIES = 5;
21
+ const AMI_WEBSITE_URL = "https://ami.dev";
22
+ const AMI_INSTALL_URL = `${AMI_WEBSITE_URL}/install.sh`;
23
+
24
+ //#endregion
25
+ //#region src/utils/proxy-fetch.ts
26
+ const readNpmConfigValue = (key) => {
27
+ try {
28
+ const value = execSync(`npm config get ${key}`, {
29
+ encoding: "utf-8",
30
+ stdio: [
31
+ "pipe",
32
+ "pipe",
33
+ "ignore"
34
+ ]
35
+ }).trim();
36
+ if (value && value !== "null" && value !== "undefined") return value;
37
+ } catch {}
38
+ };
39
+ const resolveProxyUrl = () => process.env.HTTPS_PROXY ?? process.env.https_proxy ?? process.env.HTTP_PROXY ?? process.env.http_proxy ?? readNpmConfigValue("https-proxy") ?? readNpmConfigValue("proxy");
40
+ let isProxyUrlResolved = false;
41
+ let resolvedProxyUrl;
42
+ const getProxyUrl = () => {
43
+ if (isProxyUrlResolved) return resolvedProxyUrl;
44
+ isProxyUrlResolved = true;
45
+ resolvedProxyUrl = resolveProxyUrl();
46
+ return resolvedProxyUrl;
47
+ };
48
+ const createProxyDispatcher = async (proxyUrl) => {
49
+ try {
50
+ const { ProxyAgent } = await import("undici");
51
+ return new ProxyAgent(proxyUrl);
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+ const proxyFetch = async (url, init) => {
57
+ const controller = new AbortController();
58
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
59
+ try {
60
+ const proxyUrl = getProxyUrl();
61
+ const dispatcher = proxyUrl ? await createProxyDispatcher(proxyUrl) : null;
62
+ return await fetch(url, {
63
+ ...init,
64
+ signal: controller.signal,
65
+ ...dispatcher ? { dispatcher } : {}
66
+ });
67
+ } finally {
68
+ clearTimeout(timeoutId);
69
+ }
70
+ };
18
71
 
19
72
  //#endregion
20
73
  //#region src/utils/calculate-score.ts
21
74
  const calculateScore = async (diagnostics) => {
22
75
  try {
23
- const response = await fetch(SCORE_API_URL, {
76
+ const response = await proxyFetch(SCORE_API_URL, {
24
77
  method: "POST",
25
78
  headers: { "Content-Type": "application/json" },
26
79
  body: JSON.stringify({ diagnostics })
@@ -82,6 +135,79 @@ const checkReducedMotion = (rootDirectory) => {
82
135
  }
83
136
  };
84
137
 
138
+ //#endregion
139
+ //#region src/utils/match-glob-pattern.ts
140
+ const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
141
+ const compileGlobPattern = (pattern) => {
142
+ const normalizedPattern = pattern.replace(/\\/g, "/");
143
+ let regexSource = "^";
144
+ let characterIndex = 0;
145
+ while (characterIndex < normalizedPattern.length) if (normalizedPattern[characterIndex] === "*" && normalizedPattern[characterIndex + 1] === "*") if (normalizedPattern[characterIndex + 2] === "/") {
146
+ regexSource += "(?:.+/)?";
147
+ characterIndex += 3;
148
+ } else {
149
+ regexSource += ".*";
150
+ characterIndex += 2;
151
+ }
152
+ else if (normalizedPattern[characterIndex] === "*") {
153
+ regexSource += "[^/]*";
154
+ characterIndex++;
155
+ } else if (normalizedPattern[characterIndex] === "?") {
156
+ regexSource += "[^/]";
157
+ characterIndex++;
158
+ } else {
159
+ regexSource += normalizedPattern[characterIndex].replace(REGEX_SPECIAL_CHARACTERS, "\\$&");
160
+ characterIndex++;
161
+ }
162
+ regexSource += "$";
163
+ return new RegExp(regexSource);
164
+ };
165
+
166
+ //#endregion
167
+ //#region src/utils/filter-diagnostics.ts
168
+ const filterIgnoredDiagnostics = (diagnostics, config) => {
169
+ const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules : []);
170
+ const ignoredFilePatterns = Array.isArray(config.ignore?.files) ? config.ignore.files.map(compileGlobPattern) : [];
171
+ if (ignoredRules.size === 0 && ignoredFilePatterns.length === 0) return diagnostics;
172
+ return diagnostics.filter((diagnostic) => {
173
+ const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
174
+ if (ignoredRules.has(ruleIdentifier)) return false;
175
+ const normalizedPath = diagnostic.filePath.replace(/\\/g, "/").replace(/^\.\//, "");
176
+ if (ignoredFilePatterns.some((pattern) => pattern.test(normalizedPath))) return false;
177
+ return true;
178
+ });
179
+ };
180
+
181
+ //#endregion
182
+ //#region src/utils/combine-diagnostics.ts
183
+ const computeJsxIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
184
+ const combineDiagnostics = (lintDiagnostics, deadCodeDiagnostics, directory, isDiffMode, userConfig) => {
185
+ const allDiagnostics = [
186
+ ...lintDiagnostics,
187
+ ...deadCodeDiagnostics,
188
+ ...isDiffMode ? [] : checkReducedMotion(directory)
189
+ ];
190
+ return userConfig ? filterIgnoredDiagnostics(allDiagnostics, userConfig) : allDiagnostics;
191
+ };
192
+
193
+ //#endregion
194
+ //#region src/utils/find-monorepo-root.ts
195
+ const isMonorepoRoot = (directory) => {
196
+ if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))) return true;
197
+ const packageJsonPath = path.join(directory, "package.json");
198
+ if (!fs.existsSync(packageJsonPath)) return false;
199
+ const packageJson = readPackageJson(packageJsonPath);
200
+ return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
201
+ };
202
+ const findMonorepoRoot = (startDirectory) => {
203
+ let currentDirectory = path.dirname(startDirectory);
204
+ while (currentDirectory !== path.dirname(currentDirectory)) {
205
+ if (isMonorepoRoot(currentDirectory)) return currentDirectory;
206
+ currentDirectory = path.dirname(currentDirectory);
207
+ }
208
+ return null;
209
+ };
210
+
85
211
  //#endregion
86
212
  //#region src/utils/discover-project.ts
87
213
  const REACT_COMPILER_PACKAGES = new Set([
@@ -183,27 +309,14 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
183
309
  if (fs.existsSync(directoryPath) && fs.existsSync(path.join(directoryPath, "package.json"))) return [directoryPath];
184
310
  return [];
185
311
  }
186
- const baseDirectory = path.join(rootDirectory, cleanPattern.slice(0, cleanPattern.indexOf("*")));
312
+ const wildcardIndex = cleanPattern.indexOf("*");
313
+ const baseDirectory = path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
314
+ const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
187
315
  if (!fs.existsSync(baseDirectory) || !fs.statSync(baseDirectory).isDirectory()) return [];
188
- return fs.readdirSync(baseDirectory).map((entry) => path.join(baseDirectory, entry)).filter((entryPath) => fs.statSync(entryPath).isDirectory() && fs.existsSync(path.join(entryPath, "package.json")));
189
- };
190
- const isMonorepoRoot = (directory) => {
191
- if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))) return true;
192
- const packageJsonPath = path.join(directory, "package.json");
193
- if (!fs.existsSync(packageJsonPath)) return false;
194
- const packageJson = readPackageJson(packageJsonPath);
195
- return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
196
- };
197
- const findMonorepoRoot$1 = (startDirectory) => {
198
- let currentDirectory = path.dirname(startDirectory);
199
- while (currentDirectory !== path.dirname(currentDirectory)) {
200
- if (isMonorepoRoot(currentDirectory)) return currentDirectory;
201
- currentDirectory = path.dirname(currentDirectory);
202
- }
203
- return null;
316
+ return fs.readdirSync(baseDirectory).map((entry) => path.join(baseDirectory, entry, suffixAfterWildcard)).filter((entryPath) => fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory() && fs.existsSync(path.join(entryPath, "package.json")));
204
317
  };
205
318
  const findDependencyInfoFromMonorepoRoot = (directory) => {
206
- const monorepoRoot = findMonorepoRoot$1(directory);
319
+ const monorepoRoot = findMonorepoRoot(directory);
207
320
  if (!monorepoRoot) return {
208
321
  reactVersion: null,
209
322
  framework: "unknown"
@@ -289,49 +402,6 @@ const discoverProject = (directory) => {
289
402
  };
290
403
  };
291
404
 
292
- //#endregion
293
- //#region src/utils/match-glob-pattern.ts
294
- const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
295
- const compileGlobPattern = (pattern) => {
296
- const normalizedPattern = pattern.replace(/\\/g, "/");
297
- let regexSource = "^";
298
- let characterIndex = 0;
299
- while (characterIndex < normalizedPattern.length) if (normalizedPattern[characterIndex] === "*" && normalizedPattern[characterIndex + 1] === "*") if (normalizedPattern[characterIndex + 2] === "/") {
300
- regexSource += "(?:.+/)?";
301
- characterIndex += 3;
302
- } else {
303
- regexSource += ".*";
304
- characterIndex += 2;
305
- }
306
- else if (normalizedPattern[characterIndex] === "*") {
307
- regexSource += "[^/]*";
308
- characterIndex++;
309
- } else if (normalizedPattern[characterIndex] === "?") {
310
- regexSource += "[^/]";
311
- characterIndex++;
312
- } else {
313
- regexSource += normalizedPattern[characterIndex].replace(REGEX_SPECIAL_CHARACTERS, "\\$&");
314
- characterIndex++;
315
- }
316
- regexSource += "$";
317
- return new RegExp(regexSource);
318
- };
319
-
320
- //#endregion
321
- //#region src/utils/filter-diagnostics.ts
322
- const filterIgnoredDiagnostics = (diagnostics, config) => {
323
- const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules : []);
324
- const ignoredFilePatterns = Array.isArray(config.ignore?.files) ? config.ignore.files.map(compileGlobPattern) : [];
325
- if (ignoredRules.size === 0 && ignoredFilePatterns.length === 0) return diagnostics;
326
- return diagnostics.filter((diagnostic) => {
327
- const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
328
- if (ignoredRules.has(ruleIdentifier)) return false;
329
- const normalizedPath = diagnostic.filePath.replace(/\\/g, "/").replace(/^\.\//, "");
330
- if (ignoredFilePatterns.some((pattern) => pattern.test(normalizedPath))) return false;
331
- return true;
332
- });
333
- };
334
-
335
405
  //#endregion
336
406
  //#region src/utils/load-config.ts
337
407
  const CONFIG_FILENAME = "react-doctor.config.json";
@@ -416,24 +486,10 @@ const silenced = async (fn) => {
416
486
  console.error = originalError;
417
487
  }
418
488
  };
419
- const findMonorepoRoot = (directory) => {
420
- let currentDirectory = path.dirname(directory);
421
- while (currentDirectory !== path.dirname(currentDirectory)) {
422
- if (fs.existsSync(path.join(currentDirectory, "pnpm-workspace.yaml")) || (() => {
423
- const packageJsonPath = path.join(currentDirectory, "package.json");
424
- if (!fs.existsSync(packageJsonPath)) return false;
425
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
426
- return Array.isArray(packageJson.workspaces) || packageJson.workspaces?.packages;
427
- })()) return currentDirectory;
428
- currentDirectory = path.dirname(currentDirectory);
429
- }
430
- return null;
431
- };
432
489
  const CONFIG_LOADING_ERROR_PATTERN = /Error loading .*\/([a-z-]+)\.config\./;
433
490
  const extractFailedPluginName = (error) => {
434
491
  return String(error).match(CONFIG_LOADING_ERROR_PATTERN)?.[1] ?? null;
435
492
  };
436
- const MAX_KNIP_RETRIES = 5;
437
493
  const runKnipWithOptions = async (knipCwd, workspaceName) => {
438
494
  const options = await silenced(() => createOptions({
439
495
  cwd: knipCwd,
@@ -818,6 +874,69 @@ const resolvePluginPath = () => {
818
874
  const resolveDiagnosticCategory = (plugin, rule) => {
819
875
  return RULE_CATEGORY_MAP[`${plugin}/${rule}`] ?? PLUGIN_CATEGORY_MAP[plugin] ?? "Other";
820
876
  };
877
+ const estimateArgsLength = (args) => args.reduce((total, argument) => total + argument.length + 1, 0);
878
+ const batchIncludePaths = (baseArgs, includePaths) => {
879
+ const baseArgsLength = estimateArgsLength(baseArgs);
880
+ const batches = [];
881
+ let currentBatch = [];
882
+ let currentBatchLength = baseArgsLength;
883
+ for (const filePath of includePaths) {
884
+ const entryLength = filePath.length + 1;
885
+ if (currentBatch.length > 0 && currentBatchLength + entryLength > SPAWN_ARGS_MAX_LENGTH_CHARS) {
886
+ batches.push(currentBatch);
887
+ currentBatch = [];
888
+ currentBatchLength = baseArgsLength;
889
+ }
890
+ currentBatch.push(filePath);
891
+ currentBatchLength += entryLength;
892
+ }
893
+ if (currentBatch.length > 0) batches.push(currentBatch);
894
+ return batches;
895
+ };
896
+ const spawnOxlint = (args, rootDirectory) => new Promise((resolve, reject) => {
897
+ const child = spawn(process.execPath, args, { cwd: rootDirectory });
898
+ const stdoutBuffers = [];
899
+ const stderrBuffers = [];
900
+ child.stdout.on("data", (buffer) => stdoutBuffers.push(buffer));
901
+ child.stderr.on("data", (buffer) => stderrBuffers.push(buffer));
902
+ child.on("error", (error) => reject(/* @__PURE__ */ new Error(`Failed to run oxlint: ${error.message}`)));
903
+ child.on("close", () => {
904
+ const output = Buffer.concat(stdoutBuffers).toString("utf-8").trim();
905
+ if (!output) {
906
+ const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
907
+ if (stderrOutput) {
908
+ reject(/* @__PURE__ */ new Error(`Failed to run oxlint: ${stderrOutput}`));
909
+ return;
910
+ }
911
+ }
912
+ resolve(output);
913
+ });
914
+ });
915
+ const parseOxlintOutput = (stdout) => {
916
+ if (!stdout) return [];
917
+ let output;
918
+ try {
919
+ output = JSON.parse(stdout);
920
+ } catch {
921
+ throw new Error(`Failed to parse oxlint output: ${stdout.slice(0, ERROR_PREVIEW_LENGTH_CHARS)}`);
922
+ }
923
+ return output.diagnostics.filter((diagnostic) => diagnostic.code && JSX_FILE_PATTERN.test(diagnostic.filename)).map((diagnostic) => {
924
+ const { plugin, rule } = parseRuleCode(diagnostic.code);
925
+ const primaryLabel = diagnostic.labels[0];
926
+ const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule);
927
+ return {
928
+ filePath: diagnostic.filename,
929
+ plugin,
930
+ rule,
931
+ severity: diagnostic.severity,
932
+ message: cleaned.message,
933
+ help: cleaned.help,
934
+ line: primaryLabel?.span.line ?? 0,
935
+ column: primaryLabel?.span.column ?? 0,
936
+ category: resolveDiagnosticCategory(plugin, rule)
937
+ };
938
+ });
939
+ };
821
940
  const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompiler, includePaths) => {
822
941
  if (includePaths !== void 0 && includePaths.length === 0) return [];
823
942
  const configPath = path.join(os.tmpdir(), `react-doctor-oxlintrc-${process.pid}.json`);
@@ -829,58 +948,21 @@ const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompil
829
948
  const restoreDisableDirectives = neutralizeDisableDirectives(rootDirectory);
830
949
  try {
831
950
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
832
- const args = [
951
+ const baseArgs = [
833
952
  resolveOxlintBinary(),
834
953
  "-c",
835
954
  configPath,
836
955
  "--format",
837
956
  "json"
838
957
  ];
839
- if (hasTypeScript) args.push("--tsconfig", "./tsconfig.json");
840
- if (includePaths !== void 0) args.push(...includePaths);
841
- else args.push(".");
842
- const stdout = await new Promise((resolve, reject) => {
843
- const child = spawn(process.execPath, args, { cwd: rootDirectory });
844
- const stdoutBuffers = [];
845
- const stderrBuffers = [];
846
- child.stdout.on("data", (buffer) => stdoutBuffers.push(buffer));
847
- child.stderr.on("data", (buffer) => stderrBuffers.push(buffer));
848
- child.on("error", (error) => reject(/* @__PURE__ */ new Error(`Failed to run oxlint: ${error.message}`)));
849
- child.on("close", () => {
850
- const output = Buffer.concat(stdoutBuffers).toString("utf-8").trim();
851
- if (!output) {
852
- const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
853
- if (stderrOutput) {
854
- reject(/* @__PURE__ */ new Error(`Failed to run oxlint: ${stderrOutput}`));
855
- return;
856
- }
857
- }
858
- resolve(output);
859
- });
860
- });
861
- if (!stdout) return [];
862
- let output;
863
- try {
864
- output = JSON.parse(stdout);
865
- } catch {
866
- throw new Error(`Failed to parse oxlint output: ${stdout.slice(0, ERROR_PREVIEW_LENGTH_CHARS)}`);
958
+ if (hasTypeScript) baseArgs.push("--tsconfig", "./tsconfig.json");
959
+ const fileBatches = includePaths !== void 0 ? batchIncludePaths(baseArgs, includePaths) : [["."]];
960
+ const allDiagnostics = [];
961
+ for (const batch of fileBatches) {
962
+ const stdout = await spawnOxlint([...baseArgs, ...batch], rootDirectory);
963
+ allDiagnostics.push(...parseOxlintOutput(stdout));
867
964
  }
868
- return output.diagnostics.filter((diagnostic) => diagnostic.code && JSX_FILE_PATTERN.test(diagnostic.filename)).map((diagnostic) => {
869
- const { plugin, rule } = parseRuleCode(diagnostic.code);
870
- const primaryLabel = diagnostic.labels[0];
871
- const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule);
872
- return {
873
- filePath: diagnostic.filename,
874
- plugin,
875
- rule,
876
- severity: diagnostic.severity,
877
- message: cleaned.message,
878
- help: cleaned.help,
879
- line: primaryLabel?.span.line ?? 0,
880
- column: primaryLabel?.span.column ?? 0,
881
- category: resolveDiagnosticCategory(plugin, rule)
882
- };
883
- });
965
+ return allDiagnostics;
884
966
  } finally {
885
967
  restoreDisableDirectives();
886
968
  if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
@@ -979,22 +1061,18 @@ const diagnose = async (directory, options = {}) => {
979
1061
  const effectiveLint = options.lint ?? userConfig?.lint ?? true;
980
1062
  const effectiveDeadCode = options.deadCode ?? userConfig?.deadCode ?? true;
981
1063
  if (!projectInfo.reactVersion) throw new Error("No React dependency found in package.json");
982
- const jsxIncludePaths = isDiffMode ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
1064
+ const jsxIncludePaths = computeJsxIncludePaths(includePaths);
1065
+ const emptyDiagnostics = [];
983
1066
  const lintPromise = effectiveLint ? runOxlint(resolvedDirectory, projectInfo.hasTypeScript, projectInfo.framework, projectInfo.hasReactCompiler, jsxIncludePaths).catch((error) => {
984
1067
  console.error("Lint failed:", error);
985
- return [];
986
- }) : Promise.resolve([]);
1068
+ return emptyDiagnostics;
1069
+ }) : Promise.resolve(emptyDiagnostics);
987
1070
  const deadCodePromise = effectiveDeadCode && !isDiffMode ? runKnip(resolvedDirectory).catch((error) => {
988
1071
  console.error("Dead code analysis failed:", error);
989
- return [];
990
- }) : Promise.resolve([]);
1072
+ return emptyDiagnostics;
1073
+ }) : Promise.resolve(emptyDiagnostics);
991
1074
  const [lintDiagnostics, deadCodeDiagnostics] = await Promise.all([lintPromise, deadCodePromise]);
992
- const allDiagnostics = [
993
- ...lintDiagnostics,
994
- ...deadCodeDiagnostics,
995
- ...isDiffMode ? [] : checkReducedMotion(resolvedDirectory)
996
- ];
997
- const diagnostics = userConfig ? filterIgnoredDiagnostics(allDiagnostics, userConfig) : allDiagnostics;
1075
+ const diagnostics = combineDiagnostics(lintDiagnostics, deadCodeDiagnostics, resolvedDirectory, isDiffMode, userConfig);
998
1076
  const elapsedMilliseconds = performance.now() - startTime;
999
1077
  return {
1000
1078
  diagnostics,