react-doctor 0.0.41 → 0.0.42

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,20 +1,14 @@
1
1
  //#region src/constants.ts
2
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
3
  const SCORE_API_URL = "https://www.react.doctor/api/score";
7
4
  const FETCH_TIMEOUT_MS = 1e4;
8
- const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
9
5
  const ERROR_RULE_PENALTY = 1.5;
10
6
  const WARNING_RULE_PENALTY = .75;
11
- const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
12
-
13
7
  //#endregion
14
8
  //#region src/core/calculate-score-locally.ts
15
9
  const getScoreLabel = (score) => {
16
- if (score >= SCORE_GOOD_THRESHOLD) return "Great";
17
- if (score >= SCORE_OK_THRESHOLD) return "Needs work";
10
+ if (score >= 75) return "Great";
11
+ if (score >= 50) return "Needs work";
18
12
  return "Critical";
19
13
  };
20
14
  const countUniqueRules = (diagnostics) => {
@@ -32,7 +26,7 @@ const countUniqueRules = (diagnostics) => {
32
26
  };
33
27
  const scoreFromRuleCounts = (errorRuleCount, warningRuleCount) => {
34
28
  const penalty = errorRuleCount * ERROR_RULE_PENALTY + warningRuleCount * WARNING_RULE_PENALTY;
35
- return Math.max(0, Math.round(PERFECT_SCORE - penalty));
29
+ return Math.max(0, Math.round(100 - penalty));
36
30
  };
37
31
  const calculateScoreLocally = (diagnostics) => {
38
32
  const { errorRuleCount, warningRuleCount } = countUniqueRules(diagnostics);
@@ -42,7 +36,6 @@ const calculateScoreLocally = (diagnostics) => {
42
36
  label: getScoreLabel(score)
43
37
  };
44
38
  };
45
-
46
39
  //#endregion
47
40
  //#region src/core/try-score-from-api.ts
48
41
  const parseScoreResult = (value) => {
@@ -74,11 +67,9 @@ const tryScoreFromApi = async (diagnostics, fetchImplementation) => {
74
67
  clearTimeout(timeoutId);
75
68
  }
76
69
  };
77
-
78
70
  //#endregion
79
71
  //#region src/utils/calculate-score-browser.ts
80
72
  const calculateScore = async (diagnostics) => await tryScoreFromApi(diagnostics, fetch) ?? calculateScoreLocally(diagnostics);
81
-
82
73
  //#endregion
83
74
  //#region src/utils/match-glob-pattern.ts
84
75
  const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
@@ -106,7 +97,6 @@ const compileGlobPattern = (pattern) => {
106
97
  regexSource += "$";
107
98
  return new RegExp(regexSource);
108
99
  };
109
-
110
100
  //#endregion
111
101
  //#region src/utils/is-ignored-file.ts
112
102
  const toRelativePath = (filePath, rootDirectory) => {
@@ -121,7 +111,6 @@ const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
121
111
  const relativePath = toRelativePath(filePath, rootDirectory);
122
112
  return patterns.some((pattern) => pattern.test(relativePath));
123
113
  };
124
-
125
114
  //#endregion
126
115
  //#region src/utils/filter-diagnostics.ts
127
116
  const resolveCandidateReadPath = (rootDirectory, filePath) => {
@@ -195,13 +184,11 @@ const filterInlineSuppressions = (diagnostics, rootDirectory, readFileLinesSync)
195
184
  return true;
196
185
  });
197
186
  };
198
-
199
187
  //#endregion
200
188
  //#region src/utils/merge-and-filter-diagnostics.ts
201
189
  const mergeAndFilterDiagnostics = (mergedDiagnostics, directory, userConfig, readFileLinesSync) => {
202
190
  return filterInlineSuppressions(userConfig ? filterIgnoredDiagnostics(mergedDiagnostics, userConfig, directory, readFileLinesSync) : mergedDiagnostics, directory, readFileLinesSync);
203
191
  };
204
-
205
192
  //#endregion
206
193
  //#region src/core/build-result.ts
207
194
  const buildDiagnoseTimedResult = async (input) => {
@@ -213,7 +200,6 @@ const buildDiagnoseTimedResult = async (input) => {
213
200
  elapsedMilliseconds
214
201
  };
215
202
  };
216
-
217
203
  //#endregion
218
204
  //#region src/adapters/browser/create-browser-read-file-lines.ts
219
205
  const normalizeKey = (rootDirectory, filePath) => {
@@ -229,7 +215,6 @@ const createBrowserReadFileLinesSync = (rootDirectory, projectFiles) => {
229
215
  return content.split("\n");
230
216
  };
231
217
  };
232
-
233
218
  //#endregion
234
219
  //#region src/adapters/browser/diagnose.ts
235
220
  const diagnose = async (input) => {
@@ -255,7 +240,6 @@ const diagnose = async (input) => {
255
240
  elapsedMilliseconds: timed.elapsedMilliseconds
256
241
  };
257
242
  };
258
-
259
243
  //#endregion
260
244
  //#region src/core/build-diagnose-result.ts
261
245
  const buildDiagnoseResult = (params) => ({
@@ -264,11 +248,9 @@ const buildDiagnoseResult = (params) => ({
264
248
  project: params.project,
265
249
  elapsedMilliseconds: params.elapsedMilliseconds
266
250
  });
267
-
268
251
  //#endregion
269
252
  //#region src/utils/jsx-include-paths.ts
270
253
  const computeJsxIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
271
-
272
254
  //#endregion
273
255
  //#region src/core/diagnose-core.ts
274
256
  const diagnoseCore = async (deps, options = {}) => {
@@ -319,7 +301,6 @@ const diagnoseCore = async (deps, options = {}) => {
319
301
  elapsedMilliseconds: timed.elapsedMilliseconds
320
302
  });
321
303
  };
322
-
323
304
  //#endregion
324
305
  //#region src/adapters/browser/diagnose-browser.ts
325
306
  const diagnoseBrowser = async (input, options = {}) => {
@@ -339,7 +320,6 @@ const diagnoseBrowser = async (input, options = {}) => {
339
320
  })
340
321
  }, options);
341
322
  };
342
-
343
323
  //#endregion
344
324
  //#region src/adapters/browser/process-browser-diagnostics.ts
345
325
  const processBrowserDiagnostics = async (input) => {
@@ -359,7 +339,7 @@ const processBrowserDiagnostics = async (input) => {
359
339
  score: timed.score
360
340
  };
361
341
  };
362
-
363
342
  //#endregion
364
343
  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
344
+
345
+ //# sourceMappingURL=process-browser-diagnostics-BHiLPUJT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-browser-diagnostics-BHiLPUJT.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 KNIP_CONFIG_LOCATIONS = [\n \"knip.json\",\n \"knip.jsonc\",\n \".knip.json\",\n \".knip.jsonc\",\n \"knip.ts\",\n \"knip.js\",\n \"knip.config.ts\",\n \"knip.config.js\",\n];\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,12 +1,4 @@
1
1
  //#region src/plugin/constants.ts
2
- const GIANT_COMPONENT_LINE_THRESHOLD = 300;
3
- const CASCADING_SET_STATE_THRESHOLD = 3;
4
- const RELATED_USE_STATE_THRESHOLD = 5;
5
- const DEEP_NESTING_THRESHOLD = 3;
6
- const DUPLICATE_STORAGE_READ_THRESHOLD = 2;
7
- const SEQUENTIAL_AWAIT_THRESHOLD = 3;
8
- const SECRET_MIN_LENGTH_CHARS = 8;
9
- const AUTH_CHECK_LOOKAHEAD_STATEMENTS = 3;
10
2
  const LAYOUT_PROPERTIES = new Set([
11
3
  "width",
12
4
  "height",
@@ -204,7 +196,6 @@ const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
204
196
  ];
205
197
  const TANSTACK_REDIRECT_FUNCTIONS = new Set(["redirect", "notFound"]);
206
198
  const TANSTACK_SERVER_FN_FILE_PATTERN = /\.functions(\.[jt]sx?)?$/;
207
- const SEQUENTIAL_AWAIT_THRESHOLD_FOR_LOADER = 2;
208
199
  const TANSTACK_QUERY_HOOKS = new Set([
209
200
  "useQuery",
210
201
  "useInfiniteQuery",
@@ -212,7 +203,6 @@ const TANSTACK_QUERY_HOOKS = new Set([
212
203
  "useSuspenseInfiniteQuery"
213
204
  ]);
214
205
  const TANSTACK_MUTATION_HOOKS = new Set(["useMutation"]);
215
- const TANSTACK_QUERY_CLIENT_CLASS = "QueryClient";
216
206
  const STABLE_HOOK_WRAPPERS = new Set([
217
207
  "useState",
218
208
  "useMemo",
@@ -297,10 +287,8 @@ const CHAINABLE_ITERATION_METHODS = new Set([
297
287
  "flatMap"
298
288
  ]);
299
289
  const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
300
- const LARGE_BLUR_THRESHOLD_PX = 10;
301
290
  const BLUR_VALUE_PATTERN = /blur\((\d+(?:\.\d+)?)px\)/;
302
291
  const ANIMATION_CALLBACK_NAMES = new Set(["requestAnimationFrame", "setInterval"]);
303
- const RAW_TEXT_PREVIEW_MAX_CHARS = 30;
304
292
  const REACT_NATIVE_TEXT_COMPONENTS = new Set([
305
293
  "Text",
306
294
  "TextInput",
@@ -369,18 +357,7 @@ const BOUNCE_ANIMATION_NAMES = new Set([
369
357
  "jiggle",
370
358
  "spring"
371
359
  ]);
372
- const Z_INDEX_ABSURD_THRESHOLD = 100;
373
- const INLINE_STYLE_PROPERTY_THRESHOLD = 8;
374
- const SIDE_TAB_BORDER_WIDTH_WITHOUT_RADIUS_PX = 3;
375
- const SIDE_TAB_BORDER_WIDTH_WITH_RADIUS_PX = 1;
376
- const SIDE_TAB_TAILWIND_WIDTH_WITHOUT_RADIUS = 4;
377
- const DARK_GLOW_BLUR_THRESHOLD_PX = 4;
378
- const DARK_BACKGROUND_CHANNEL_MAX = 35;
379
- const COLOR_CHROMA_THRESHOLD = 30;
380
- const TINY_TEXT_THRESHOLD_PX = 12;
381
- const WIDE_TRACKING_THRESHOLD_EM = .05;
382
360
  const LONG_TRANSITION_DURATION_THRESHOLD_MS = 1e3;
383
-
384
361
  //#endregion
385
362
  //#region src/plugin/helpers.ts
386
363
  const walkAst = (node, visitor) => {
@@ -516,14 +493,13 @@ const extractDestructuredPropNames = (params) => {
516
493
  } else if (param.type === "Identifier") propNames.add(param.name);
517
494
  return propNames;
518
495
  };
519
-
520
496
  //#endregion
521
497
  //#region src/plugin/rules/architecture.ts
522
498
  const noGiantComponent = { create: (context) => {
523
499
  const reportOversizedComponent = (nameNode, componentName, bodyNode) => {
524
500
  if (!bodyNode.loc) return;
525
501
  const lineCount = bodyNode.loc.end.line - bodyNode.loc.start.line + 1;
526
- if (lineCount > GIANT_COMPONENT_LINE_THRESHOLD) context.report({
502
+ if (lineCount > 300) context.report({
527
503
  node: nameNode,
528
504
  message: `Component "${componentName}" is ${lineCount} lines — consider breaking it into smaller focused components`
529
505
  });
@@ -577,7 +553,6 @@ const noNestedComponentDefinition = { create: (context) => {
577
553
  }
578
554
  };
579
555
  } };
580
-
581
556
  //#endregion
582
557
  //#region src/plugin/rules/bundle-size.ts
583
558
  const noBarrelImport = { create: (context) => {
@@ -632,7 +607,6 @@ const noUndeferredThirdParty = { create: (context) => ({ JSXOpeningElement(node)
632
607
  message: "Synchronous <script> with src — add defer or async to avoid blocking first paint"
633
608
  });
634
609
  } }) };
635
-
636
610
  //#endregion
637
611
  //#region src/plugin/rules/client.ts
638
612
  const clientPassiveEventListeners = { create: (context) => ({ CallExpression(node) {
@@ -655,7 +629,6 @@ const clientPassiveEventListeners = { create: (context) => ({ CallExpression(nod
655
629
  message: `"${eventName}" listener without { passive: true } — blocks scrolling performance`
656
630
  });
657
631
  } }) };
658
-
659
632
  //#endregion
660
633
  //#region src/plugin/rules/design.ts
661
634
  const isOvershootCubicBezier = (value) => {
@@ -722,7 +695,7 @@ const parseColorToRgb = (value) => {
722
695
  };
723
696
  return null;
724
697
  };
725
- const hasColorChroma = (parsed) => Math.max(parsed.red, parsed.green, parsed.blue) - Math.min(parsed.red, parsed.green, parsed.blue) >= COLOR_CHROMA_THRESHOLD;
698
+ const hasColorChroma = (parsed) => Math.max(parsed.red, parsed.green, parsed.blue) - Math.min(parsed.red, parsed.green, parsed.blue) >= 30;
726
699
  const isNeutralBorderColor = (value) => {
727
700
  const trimmed = value.trim().toLowerCase();
728
701
  if ([
@@ -768,7 +741,7 @@ const parseShadowLayerBlur = (layer) => {
768
741
  const hasColoredGlowShadow = (shadowValue) => {
769
742
  for (const layer of splitShadowLayers(shadowValue)) {
770
743
  const color = extractColorFromShadowLayer(layer);
771
- if (color && hasColorChroma(color) && parseShadowLayerBlur(layer) > DARK_GLOW_BLUR_THRESHOLD_PX) return true;
744
+ if (color && hasColorChroma(color) && parseShadowLayerBlur(layer) > 4) return true;
772
745
  }
773
746
  return false;
774
747
  };
@@ -777,7 +750,7 @@ const isBackgroundDark = (bgValue) => {
777
750
  if (isPureBlackColor(trimmed)) return true;
778
751
  const parsed = parseColorToRgb(trimmed);
779
752
  if (!parsed) return false;
780
- return parsed.red <= DARK_BACKGROUND_CHANNEL_MAX && parsed.green <= DARK_BACKGROUND_CHANNEL_MAX && parsed.blue <= DARK_BACKGROUND_CHANNEL_MAX;
753
+ return parsed.red <= 35 && parsed.green <= 35 && parsed.blue <= 35;
781
754
  };
782
755
  const BORDER_SIDE_KEYS = {
783
756
  borderLeft: "left",
@@ -826,7 +799,7 @@ const noZIndex9999 = { create: (context) => ({
826
799
  for (const property of expression.properties ?? []) {
827
800
  if (getStylePropertyKey(property) !== "zIndex") continue;
828
801
  const zValue = getStylePropertyNumberValue(property);
829
- if (zValue !== null && Math.abs(zValue) >= Z_INDEX_ABSURD_THRESHOLD) context.report({
802
+ if (zValue !== null && Math.abs(zValue) >= 100) context.report({
830
803
  node: property,
831
804
  message: `z-index: ${zValue} is arbitrarily high — use a deliberate z-index scale (1–50). Extreme values signal a stacking context problem, not a fix`
832
805
  });
@@ -843,7 +816,7 @@ const noZIndex9999 = { create: (context) => ({
843
816
  if (getStylePropertyKey(child) !== "zIndex") return;
844
817
  if (child.value?.type === "Literal" && typeof child.value.value === "number") {
845
818
  const zValue = child.value.value;
846
- if (Math.abs(zValue) >= Z_INDEX_ABSURD_THRESHOLD) context.report({
819
+ if (Math.abs(zValue) >= 100) context.report({
847
820
  node: child,
848
821
  message: `z-index: ${zValue} is arbitrarily high — use a deliberate z-index scale (1–50). Extreme values signal a stacking context problem, not a fix`
849
822
  });
@@ -855,7 +828,7 @@ const noInlineExhaustiveStyle = { create: (context) => ({ JSXAttribute(node) {
855
828
  const expression = getInlineStyleExpression(node);
856
829
  if (!expression) return;
857
830
  const propertyCount = expression.properties?.filter((property) => property.type === "Property").length ?? 0;
858
- if (propertyCount >= INLINE_STYLE_PROPERTY_THRESHOLD) context.report({
831
+ if (propertyCount >= 8) context.report({
859
832
  node: expression,
860
833
  message: `${propertyCount} inline style properties — extract to a CSS class, CSS module, or styled component for maintainability and reuse`
861
834
  });
@@ -870,7 +843,7 @@ const noSideTabBorder = { create: (context) => ({
870
843
  const strValue = getStylePropertyStringValue(property);
871
844
  if (numValue !== null && numValue > 0 || strValue !== null && parseFloat(strValue) > 0) hasBorderRadius = true;
872
845
  }
873
- const threshold = hasBorderRadius ? SIDE_TAB_BORDER_WIDTH_WITH_RADIUS_PX : SIDE_TAB_BORDER_WIDTH_WITHOUT_RADIUS_PX;
846
+ const threshold = hasBorderRadius ? 1 : 3;
874
847
  for (const property of expression.properties ?? []) {
875
848
  const key = getStylePropertyKey(property);
876
849
  if (!key) continue;
@@ -911,7 +884,7 @@ const noSideTabBorder = { create: (context) => ({
911
884
  const sideMatch = classStr.match(/\bborder-[lrse]-(\d+)\b/);
912
885
  if (!sideMatch) return;
913
886
  if (/\bborder-(?:(?:gray|slate|zinc|neutral|stone)-\d+|white|black|transparent)\b/.test(classStr)) return;
914
- if (parseInt(sideMatch[1], 10) >= (/\brounded(?:-(?!none\b)\w+)?\b/.test(classStr) && !/\brounded-none\b/.test(classStr) ? SIDE_TAB_BORDER_WIDTH_WITH_RADIUS_PX : SIDE_TAB_TAILWIND_WIDTH_WITHOUT_RADIUS)) context.report({
887
+ if (parseInt(sideMatch[1], 10) >= (/\brounded(?:-(?!none\b)\w+)?\b/.test(classStr) && !/\brounded-none\b/.test(classStr) ? 1 : 4)) context.report({
915
888
  node,
916
889
  message: `Thick one-sided border (${sideMatch[0]}) — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it`
917
890
  });
@@ -1023,9 +996,9 @@ const noTinyText = { create: (context) => ({ JSXAttribute(node) {
1023
996
  const remMatch = strValue.match(/^([\d.]+)rem$/);
1024
997
  if (remMatch) pxValue = parseFloat(remMatch[1]) * 16;
1025
998
  }
1026
- if (pxValue !== null && pxValue > 0 && pxValue < TINY_TEXT_THRESHOLD_PX) context.report({
999
+ if (pxValue !== null && pxValue > 0 && pxValue < 12) context.report({
1027
1000
  node: property,
1028
- message: `Font size ${pxValue}px is too small — body text should be at least ${TINY_TEXT_THRESHOLD_PX}px for readability, 16px is ideal`
1001
+ message: `Font size ${pxValue}px is too small — body text should be at least 12px for readability, 16px is ideal`
1029
1002
  });
1030
1003
  }
1031
1004
  } }) };
@@ -1054,7 +1027,7 @@ const noWideLetterSpacing = { create: (context) => ({ JSXAttribute(node) {
1054
1027
  if (numValue !== null && numValue > 0) letterSpacingEm = numValue / 16;
1055
1028
  }
1056
1029
  }
1057
- if (!isUppercase && letterSpacingProperty && letterSpacingEm !== null && letterSpacingEm > WIDE_TRACKING_THRESHOLD_EM) context.report({
1030
+ if (!isUppercase && letterSpacingProperty && letterSpacingEm !== null && letterSpacingEm > .05) context.report({
1058
1031
  node: letterSpacingProperty,
1059
1032
  message: `Letter spacing ${letterSpacingEm.toFixed(2)}em on body text disrupts natural character groupings. Reserve wide tracking for short uppercase labels only`
1060
1033
  });
@@ -1164,13 +1137,12 @@ const noLongTransitionDuration = { create: (context) => ({ JSXAttribute(node) {
1164
1137
  }
1165
1138
  if (longestDurationMs > 0) durationMs = longestDurationMs;
1166
1139
  }
1167
- if (durationMs !== null && durationMs > LONG_TRANSITION_DURATION_THRESHOLD_MS) context.report({
1140
+ if (durationMs !== null && durationMs > 1e3) context.report({
1168
1141
  node: property,
1169
1142
  message: `${durationMs}ms transition is too slow for UI feedback — keep transitions under ${LONG_TRANSITION_DURATION_THRESHOLD_MS}ms. Use longer durations only for page-load hero animations`
1170
1143
  });
1171
1144
  }
1172
1145
  } }) };
1173
-
1174
1146
  //#endregion
1175
1147
  //#region src/plugin/rules/correctness.ts
1176
1148
  const extractIndexName = (node) => {
@@ -1245,7 +1217,6 @@ const renderingConditionalRender = { create: (context) => ({ LogicalExpression(n
1245
1217
  message: "Conditional rendering with .length can render '0' — use .length > 0 or Boolean(.length)"
1246
1218
  });
1247
1219
  } }) };
1248
-
1249
1220
  //#endregion
1250
1221
  //#region src/plugin/rules/js-performance.ts
1251
1222
  const jsCombineIterations = { create: (context) => ({ CallExpression(node) {
@@ -1328,7 +1299,7 @@ const jsCacheStorage = { create: (context) => {
1328
1299
  const storageKey = String(node.arguments[0].value);
1329
1300
  const readCount = (storageReadCounts.get(storageKey) ?? 0) + 1;
1330
1301
  storageReadCounts.set(storageKey, readCount);
1331
- if (readCount === DUPLICATE_STORAGE_READ_THRESHOLD) {
1302
+ if (readCount === 2) {
1332
1303
  const storageName = node.callee.object.name;
1333
1304
  context.report({
1334
1305
  node,
@@ -1347,7 +1318,7 @@ const jsEarlyExit = { create: (context) => ({ IfStatement(node) {
1347
1318
  nestingDepth++;
1348
1319
  currentBlock = innerStatement.consequent;
1349
1320
  }
1350
- if (nestingDepth >= DEEP_NESTING_THRESHOLD) context.report({
1321
+ if (nestingDepth >= 3) context.report({
1351
1322
  node,
1352
1323
  message: `${nestingDepth + 1} levels of nested if statements — use early returns to flatten`
1353
1324
  });
@@ -1359,7 +1330,7 @@ const asyncParallel = { create: (context) => {
1359
1330
  if (isTestFile) return;
1360
1331
  const consecutiveAwaitStatements = [];
1361
1332
  const flushConsecutiveAwaits = () => {
1362
- if (consecutiveAwaitStatements.length >= SEQUENTIAL_AWAIT_THRESHOLD) reportIfIndependent(consecutiveAwaitStatements, context);
1333
+ if (consecutiveAwaitStatements.length >= 3) reportIfIndependent(consecutiveAwaitStatements, context);
1363
1334
  consecutiveAwaitStatements.length = 0;
1364
1335
  };
1365
1336
  for (const statement of node.body ?? []) if (statement.type === "VariableDeclaration" && statement.declarations?.length === 1 && statement.declarations[0].init?.type === "AwaitExpression" || statement.type === "ExpressionStatement" && statement.expression?.type === "AwaitExpression") consecutiveAwaitStatements.push(statement);
@@ -1400,7 +1371,6 @@ const jsFlatmapFilter = { create: (context) => ({ CallExpression(node) {
1400
1371
  message: ".map().filter(Boolean) iterates twice — use .flatMap() to transform and filter in a single pass"
1401
1372
  });
1402
1373
  } }) };
1403
-
1404
1374
  //#endregion
1405
1375
  //#region src/plugin/rules/nextjs.ts
1406
1376
  const nextjsNoImgElement = { create: (context) => {
@@ -1654,7 +1624,6 @@ const nextjsNoSideEffectInGetHandler = { create: (context) => ({ ExportNamedDecl
1654
1624
  message: `GET handler has side effects (${sideEffect}) — use POST to prevent CSRF and unintended prefetch triggers`
1655
1625
  });
1656
1626
  } }) };
1657
-
1658
1627
  //#endregion
1659
1628
  //#region src/plugin/rules/performance.ts
1660
1629
  const isMemoCall = (node) => {
@@ -1782,7 +1751,7 @@ const noLargeAnimatedBlur = { create: (context) => ({ JSXAttribute(node) {
1782
1751
  const match = BLUR_VALUE_PATTERN.exec(property.value.value);
1783
1752
  if (!match) continue;
1784
1753
  const blurRadius = Number.parseFloat(match[1]);
1785
- if (blurRadius > LARGE_BLUR_THRESHOLD_PX) context.report({
1754
+ if (blurRadius > 10) context.report({
1786
1755
  node: property,
1787
1756
  message: `blur(${blurRadius}px) is expensive — cost escalates with radius and layer size, can exceed GPU memory on mobile`
1788
1757
  });
@@ -1880,7 +1849,6 @@ const renderingScriptDeferAsync = { create: (context) => ({ JSXOpeningElement(no
1880
1849
  message: "<script src> without defer or async — blocks HTML parsing and delays First Contentful Paint. Add defer for DOM-dependent scripts or async for independent ones"
1881
1850
  });
1882
1851
  } }) };
1883
-
1884
1852
  //#endregion
1885
1853
  //#region src/plugin/rules/react-native.ts
1886
1854
  const resolveJsxElementName = (openingElement) => {
@@ -1890,7 +1858,7 @@ const resolveJsxElementName = (openingElement) => {
1890
1858
  if (elementName.type === "JSXMemberExpression") return elementName.property?.name ?? null;
1891
1859
  return null;
1892
1860
  };
1893
- const truncateText = (text) => text.length > RAW_TEXT_PREVIEW_MAX_CHARS ? `${text.slice(0, RAW_TEXT_PREVIEW_MAX_CHARS)}...` : text;
1861
+ const truncateText = (text) => text.length > 30 ? `${text.slice(0, 30)}...` : text;
1894
1862
  const isRawTextContent = (child) => {
1895
1863
  if (child.type === "JSXText") return Boolean(child.value?.trim());
1896
1864
  if (child.type !== "JSXExpressionContainer" || !child.expression) return false;
@@ -2043,7 +2011,6 @@ const rnNoSingleElementStyleArray = { create: (context) => ({ JSXAttribute(node)
2043
2011
  message: `Single-element style array on "${propName}" — use ${propName}={value} instead of ${propName}={[value]} to avoid unnecessary array allocation`
2044
2012
  });
2045
2013
  } }) };
2046
-
2047
2014
  //#endregion
2048
2015
  //#region src/plugin/rules/tanstack-query.ts
2049
2016
  const queryStableQueryClient = { create: (context) => {
@@ -2071,7 +2038,7 @@ const queryStableQueryClient = { create: (context) => {
2071
2038
  NewExpression(node) {
2072
2039
  if (componentDepth <= 0) return;
2073
2040
  if (stableHookDepth > 0) return;
2074
- if (node.callee?.type !== "Identifier" || node.callee.name !== TANSTACK_QUERY_CLIENT_CLASS) return;
2041
+ if (node.callee?.type !== "Identifier" || node.callee.name !== "QueryClient") return;
2075
2042
  context.report({
2076
2043
  node,
2077
2044
  message: "new QueryClient() inside a component — creates a new cache on every render. Move to module scope or wrap in useState(() => new QueryClient())"
@@ -2156,7 +2123,6 @@ const queryNoUseQueryForMutation = { create: (context) => ({ CallExpression(node
2156
2123
  message: `${calleeName}() with a mutating fetch (POST/PUT/DELETE) — use useMutation() instead, which provides onSuccess/onError callbacks and doesn't auto-refetch`
2157
2124
  });
2158
2125
  } }) };
2159
-
2160
2126
  //#endregion
2161
2127
  //#region src/plugin/rules/security.ts
2162
2128
  const noEval = { create: (context) => ({
@@ -2187,7 +2153,7 @@ const noSecretsInClientCode = { create: (context) => ({ VariableDeclarator(node)
2187
2153
  const literalValue = node.init.value;
2188
2154
  const trailingSuffix = variableName.split("_").pop()?.toLowerCase() ?? "";
2189
2155
  const isUiConstant = SECRET_FALSE_POSITIVE_SUFFIXES.has(trailingSuffix);
2190
- if (SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && literalValue.length > SECRET_MIN_LENGTH_CHARS) {
2156
+ if (SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && literalValue.length > 8) {
2191
2157
  context.report({
2192
2158
  node,
2193
2159
  message: `Possible hardcoded secret in "${variableName}" — use environment variables instead`
@@ -2199,7 +2165,6 @@ const noSecretsInClientCode = { create: (context) => ({ VariableDeclarator(node)
2199
2165
  message: "Hardcoded secret detected — use environment variables instead"
2200
2166
  });
2201
2167
  } }) };
2202
-
2203
2168
  //#endregion
2204
2169
  //#region src/plugin/rules/server.ts
2205
2170
  const containsAuthCheck = (statements) => {
@@ -2223,7 +2188,7 @@ const serverAuthActions = { create: (context) => {
2223
2188
  const declaration = node.declaration;
2224
2189
  if (declaration?.type !== "FunctionDeclaration" || !declaration?.async) return;
2225
2190
  if (!(fileHasUseServerDirective || hasUseServerDirective(declaration))) return;
2226
- if (!containsAuthCheck((declaration.body?.body ?? []).slice(0, AUTH_CHECK_LOOKAHEAD_STATEMENTS))) {
2191
+ if (!containsAuthCheck((declaration.body?.body ?? []).slice(0, 3))) {
2227
2192
  const functionName = declaration.id?.name ?? "anonymous";
2228
2193
  context.report({
2229
2194
  node: declaration.id ?? node,
@@ -2254,7 +2219,6 @@ const serverAfterNonblocking = { create: (context) => {
2254
2219
  }
2255
2220
  };
2256
2221
  } };
2257
-
2258
2222
  //#endregion
2259
2223
  //#region src/plugin/rules/tanstack-start.ts
2260
2224
  const getRouteOptionsObject = (node) => {
@@ -2573,7 +2537,7 @@ const tanstackStartLoaderParallelFetch = { create: (context) => ({ CallExpressio
2573
2537
  let sequentialAwaitCount = 0;
2574
2538
  for (const statement of functionBody.body ?? []) {
2575
2539
  if (hasTopLevelAwait(statement)) sequentialAwaitCount++;
2576
- if (sequentialAwaitCount >= SEQUENTIAL_AWAIT_THRESHOLD_FOR_LOADER) {
2540
+ if (sequentialAwaitCount >= 2) {
2577
2541
  context.report({
2578
2542
  node: property,
2579
2543
  message: "Multiple sequential awaits in loader — use Promise.all() to fetch data in parallel and avoid waterfalls"
@@ -2583,7 +2547,6 @@ const tanstackStartLoaderParallelFetch = { create: (context) => ({ CallExpressio
2583
2547
  }
2584
2548
  }
2585
2549
  } }) };
2586
-
2587
2550
  //#endregion
2588
2551
  //#region src/plugin/rules/state-and-effects.ts
2589
2552
  const noDerivedStateEffect = { create: (context) => ({ CallExpression(node) {
@@ -2635,7 +2598,7 @@ const noCascadingSetState = { create: (context) => ({ CallExpression(node) {
2635
2598
  const callback = getEffectCallback(node);
2636
2599
  if (!callback) return;
2637
2600
  const setStateCallCount = countSetStateCalls(callback);
2638
- if (setStateCallCount >= CASCADING_SET_STATE_THRESHOLD) context.report({
2601
+ if (setStateCallCount >= 3) context.report({
2639
2602
  node,
2640
2603
  message: `${setStateCallCount} setState calls in a single useEffect — consider using useReducer or deriving state`
2641
2604
  });
@@ -2685,7 +2648,7 @@ const preferUseReducer = { create: (context) => {
2685
2648
  if (statement.type !== "VariableDeclaration") continue;
2686
2649
  for (const declarator of statement.declarations ?? []) if (isHookCall(declarator.init, "useState")) useStateCount++;
2687
2650
  }
2688
- if (useStateCount >= RELATED_USE_STATE_THRESHOLD) context.report({
2651
+ if (useStateCount >= 5) context.report({
2689
2652
  node: body,
2690
2653
  message: `Component "${componentName}" has ${useStateCount} useState calls — consider useReducer for related state`
2691
2654
  });
@@ -2738,7 +2701,6 @@ const rerenderDependencies = { create: (context) => ({ CallExpression(node) {
2738
2701
  });
2739
2702
  }
2740
2703
  } }) };
2741
-
2742
2704
  //#endregion
2743
2705
  //#region src/plugin/index.ts
2744
2706
  const plugin = {
@@ -2854,7 +2816,7 @@ const plugin = {
2854
2816
  "no-long-transition-duration": noLongTransitionDuration
2855
2817
  }
2856
2818
  };
2857
-
2858
2819
  //#endregion
2859
2820
  export { plugin as default };
2821
+
2860
2822
  //# sourceMappingURL=react-doctor-plugin.js.map