react-doctor 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,21 +160,26 @@ When a suppression isn't working, `--explain <file:line>` reports what the scann
160
160
 
161
161
  ### Config keys
162
162
 
163
- | Key | Type | Default |
164
- | ------------------------- | -------------------------------- | -------- |
165
- | `ignore.rules` | `string[]` | `[]` |
166
- | `ignore.files` | `string[]` | `[]` |
167
- | `ignore.overrides` | `{ files, rules? }[]` | `[]` |
168
- | `lint` | `boolean` | `true` |
169
- | `deadCode` | `boolean` | `true` |
170
- | `verbose` | `boolean` | `false` |
171
- | `diff` | `boolean \| string` | |
172
- | `failOn` | `"error" \| "warning" \| "none"` | `"none"` |
173
- | `customRulesOnly` | `boolean` | `false` |
174
- | `share` | `boolean` | `true` |
175
- | `textComponents` | `string[]` | `[]` |
176
- | `respectInlineDisables` | `boolean` | `true` |
177
- | `adoptExistingLintConfig` | `boolean` | `true` |
163
+ | Key | Type | Default |
164
+ | -------------------------- | -------------------------------- | -------- |
165
+ | `ignore.rules` | `string[]` | `[]` |
166
+ | `ignore.files` | `string[]` | `[]` |
167
+ | `ignore.overrides` | `{ files, rules? }[]` | `[]` |
168
+ | `lint` | `boolean` | `true` |
169
+ | `deadCode` | `boolean` | `true` |
170
+ | `verbose` | `boolean` | `false` |
171
+ | `diff` | `boolean \| string` | |
172
+ | `failOn` | `"error" \| "warning" \| "none"` | `"none"` |
173
+ | `customRulesOnly` | `boolean` | `false` |
174
+ | `share` | `boolean` | `true` |
175
+ | `textComponents` | `string[]` | `[]` |
176
+ | `rawTextWrapperComponents` | `string[]` | `[]` |
177
+ | `respectInlineDisables` | `boolean` | `true` |
178
+ | `adoptExistingLintConfig` | `boolean` | `true` |
179
+
180
+ `textComponents` is the broad escape hatch for `rn-no-raw-text` — list components that themselves behave like React Native's `<Text>` (custom `Typography`, `NativeTabs.Trigger.Label`, etc.) and the rule will treat them as text containers regardless of what their children look like.
181
+
182
+ `rawTextWrapperComponents` is the narrower option for components that are not text elements but safely route string-only children through an internal `<Text>` (e.g. `heroui-native`'s `Button`, which stringifies its children and renders them through a `ButtonLabel`). Listed wrappers suppress `rn-no-raw-text` only when their children are entirely stringifiable. A wrapper with mixed children — e.g. `<Button>Save<Icon /></Button>` — still reports because the wrapper can't safely route raw text alongside a sibling JSX element.
178
183
 
179
184
  ## Node.js API
180
185
 
package/dist/cli.js CHANGED
@@ -212,7 +212,10 @@ const finalize = (method, originalText, displayText) => {
212
212
  return;
213
213
  }
214
214
  sharedInstance.stop();
215
- ora(displayText).start()[method](displayText);
215
+ ora({
216
+ text: displayText,
217
+ indent: 2
218
+ }).start()[method](displayText);
216
219
  const [remainingText] = pendingTexts;
217
220
  if (remainingText) sharedInstance.text = remainingText;
218
221
  sharedInstance.start();
@@ -221,7 +224,10 @@ const spinner = (text) => ({ start() {
221
224
  if (isSilent) return noopHandle;
222
225
  activeCount++;
223
226
  pendingTexts.add(text);
224
- if (!sharedInstance) sharedInstance = ora({ text }).start();
227
+ if (!sharedInstance) sharedInstance = ora({
228
+ text,
229
+ indent: 2
230
+ }).start();
225
231
  else sharedInstance.text = text;
226
232
  const handle = {
227
233
  succeed: (displayText) => {
@@ -906,6 +912,8 @@ const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
906
912
  //#endregion
907
913
  //#region src/utils/filter-diagnostics.ts
908
914
  const OPENING_TAG_PATTERN = /<([A-Z][\w.]*)/;
915
+ const JSX_CHILD_OPEN_PATTERN = /<[A-Za-z]/;
916
+ const escapeRegExpSpecials = (rawText) => rawText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
909
917
  const resolveCandidateReadPath = (rootDirectory, filePath) => {
910
918
  const normalizedFile = filePath.replace(/\\/g, "/");
911
919
  if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile) || /^[a-zA-Z]:\\/.test(filePath)) return filePath;
@@ -931,21 +939,93 @@ const isInsideTextComponent = (lines, diagnosticLine, textComponentNames) => {
931
939
  }
932
940
  return false;
933
941
  };
942
+ const findOpenerAtOrAbove = (lines, upperBoundLineIndex) => {
943
+ for (let lineIndex = upperBoundLineIndex; lineIndex >= 0; lineIndex--) {
944
+ const match = lines[lineIndex].match(OPENING_TAG_PATTERN);
945
+ if (!match) continue;
946
+ const fullName = match[1];
947
+ return {
948
+ fullName,
949
+ leafName: fullName.includes(".") ? fullName.split(".").at(-1) ?? fullName : fullName,
950
+ lineIndex
951
+ };
952
+ }
953
+ return null;
954
+ };
955
+ const resolveJsxRange = (lines, opener) => {
956
+ const closingPattern = new RegExp(`</(?:${escapeRegExpSpecials(opener.fullName)}|${escapeRegExpSpecials(opener.leafName)})\\s*>`);
957
+ let closerLineIndex = -1;
958
+ let closerColumn = -1;
959
+ for (let lineIndex = opener.lineIndex; lineIndex < lines.length; lineIndex++) {
960
+ const match = closingPattern.exec(lines[lineIndex]);
961
+ if (!match) continue;
962
+ closerLineIndex = lineIndex;
963
+ closerColumn = match.index;
964
+ break;
965
+ }
966
+ if (closerLineIndex < 0) return null;
967
+ const openerLine = lines[opener.lineIndex];
968
+ const tagStartIndex = openerLine.indexOf(`<${opener.fullName}`);
969
+ if (tagStartIndex < 0) return null;
970
+ const openerEndIndex = openerLine.indexOf(">", tagStartIndex);
971
+ let bodyText;
972
+ if (opener.lineIndex === closerLineIndex) {
973
+ if (openerEndIndex < 0 || openerEndIndex >= closerColumn) return null;
974
+ bodyText = openerLine.slice(openerEndIndex + 1, closerColumn);
975
+ } else {
976
+ const segments = [];
977
+ if (openerEndIndex >= 0) segments.push(openerLine.slice(openerEndIndex + 1));
978
+ for (let lineIndex = opener.lineIndex + 1; lineIndex < closerLineIndex; lineIndex++) segments.push(lines[lineIndex]);
979
+ segments.push(lines[closerLineIndex].slice(0, closerColumn));
980
+ bodyText = segments.join("\n");
981
+ }
982
+ return {
983
+ closerLineIndex,
984
+ closerColumn,
985
+ bodyText
986
+ };
987
+ };
988
+ const isInsideStringOnlyWrapper = (lines, diagnosticLine, diagnosticColumn, wrapperNames) => {
989
+ const diagnosticLineIndex = diagnosticLine - 1;
990
+ const diagnosticColumnIndex = Math.max(0, diagnosticColumn - 1);
991
+ let upperBoundLineIndex = diagnosticLineIndex;
992
+ while (upperBoundLineIndex >= 0) {
993
+ const opener = findOpenerAtOrAbove(lines, upperBoundLineIndex);
994
+ if (!opener) return false;
995
+ const range = resolveJsxRange(lines, opener);
996
+ if (range === null) {
997
+ upperBoundLineIndex = opener.lineIndex - 1;
998
+ continue;
999
+ }
1000
+ if (range.closerLineIndex < diagnosticLineIndex || range.closerLineIndex === diagnosticLineIndex && range.closerColumn <= diagnosticColumnIndex) {
1001
+ upperBoundLineIndex = opener.lineIndex - 1;
1002
+ continue;
1003
+ }
1004
+ if (!wrapperNames.has(opener.fullName) && !wrapperNames.has(opener.leafName)) return false;
1005
+ return !JSX_CHILD_OPEN_PATTERN.test(range.bodyText);
1006
+ }
1007
+ return false;
1008
+ };
934
1009
  const filterIgnoredDiagnostics = (diagnostics, config, rootDirectory, readFileLinesSync) => {
935
1010
  const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules.filter((rule) => typeof rule === "string") : []);
936
1011
  const ignoredFilePatterns = compileIgnoredFilePatterns(config);
937
1012
  const compiledOverrides = compileIgnoreOverrides(config);
938
1013
  const textComponentNames = new Set(Array.isArray(config.textComponents) ? config.textComponents.filter((name) => typeof name === "string") : []);
939
1014
  const hasTextComponents = textComponentNames.size > 0;
1015
+ const rawTextWrapperComponentNames = new Set(Array.isArray(config.rawTextWrapperComponents) ? config.rawTextWrapperComponents.filter((name) => typeof name === "string") : []);
1016
+ const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
940
1017
  const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);
941
1018
  return diagnostics.filter((diagnostic) => {
942
1019
  const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
943
1020
  if (ignoredRules.has(ruleIdentifier)) return false;
944
1021
  if (isFileIgnoredByPatterns(diagnostic.filePath, rootDirectory, ignoredFilePatterns)) return false;
945
1022
  if (isDiagnosticIgnoredByOverrides(diagnostic, rootDirectory, compiledOverrides)) return false;
946
- if (hasTextComponents && diagnostic.rule === "rn-no-raw-text" && diagnostic.line > 0) {
1023
+ if ((hasTextComponents || hasRawTextWrappers) && diagnostic.rule === "rn-no-raw-text" && diagnostic.line > 0) {
947
1024
  const lines = getFileLines(diagnostic.filePath);
948
- if (lines && isInsideTextComponent(lines, diagnostic.line, textComponentNames)) return false;
1025
+ if (lines) {
1026
+ if (hasTextComponents && isInsideTextComponent(lines, diagnostic.line, textComponentNames)) return false;
1027
+ if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return false;
1028
+ }
949
1029
  }
950
1030
  return true;
951
1031
  });
@@ -2177,22 +2257,22 @@ const TANSTACK_START_RULES = {
2177
2257
  "react-doctor/tanstack-start-loader-parallel-fetch": "warn"
2178
2258
  };
2179
2259
  const REACT_COMPILER_RULES = {
2180
- "react-hooks-js/set-state-in-render": "warn",
2181
- "react-hooks-js/immutability": "warn",
2182
- "react-hooks-js/refs": "warn",
2183
- "react-hooks-js/purity": "warn",
2184
- "react-hooks-js/hooks": "warn",
2185
- "react-hooks-js/set-state-in-effect": "warn",
2186
- "react-hooks-js/globals": "warn",
2187
- "react-hooks-js/error-boundaries": "warn",
2188
- "react-hooks-js/preserve-manual-memoization": "warn",
2189
- "react-hooks-js/unsupported-syntax": "warn",
2190
- "react-hooks-js/component-hook-factories": "warn",
2191
- "react-hooks-js/static-components": "warn",
2192
- "react-hooks-js/use-memo": "warn",
2193
- "react-hooks-js/void-use-memo": "warn",
2194
- "react-hooks-js/incompatible-library": "warn",
2195
- "react-hooks-js/todo": "warn"
2260
+ "react-hooks-js/set-state-in-render": "error",
2261
+ "react-hooks-js/immutability": "error",
2262
+ "react-hooks-js/refs": "error",
2263
+ "react-hooks-js/purity": "error",
2264
+ "react-hooks-js/hooks": "error",
2265
+ "react-hooks-js/set-state-in-effect": "error",
2266
+ "react-hooks-js/globals": "error",
2267
+ "react-hooks-js/error-boundaries": "error",
2268
+ "react-hooks-js/preserve-manual-memoization": "error",
2269
+ "react-hooks-js/unsupported-syntax": "error",
2270
+ "react-hooks-js/component-hook-factories": "error",
2271
+ "react-hooks-js/static-components": "error",
2272
+ "react-hooks-js/use-memo": "error",
2273
+ "react-hooks-js/void-use-memo": "error",
2274
+ "react-hooks-js/incompatible-library": "error",
2275
+ "react-hooks-js/todo": "error"
2196
2276
  };
2197
2277
  const readPluginRuleNames = (pluginSpecifier) => {
2198
2278
  try {
@@ -3251,18 +3331,13 @@ const printDiagnostics = (diagnostics, isVerbose, rootDirectory) => {
3251
3331
  const visibleRuleGroups = isVerbose ? sortedRuleGroups : sortedRuleGroups.slice(0, 5);
3252
3332
  const hiddenRuleGroups = isVerbose ? [] : sortedRuleGroups.slice(5);
3253
3333
  const ruleNameColumnWidth = computeRuleNameColumnWidth(visibleRuleGroups.map(([ruleKey]) => ruleKey));
3254
- visibleRuleGroups.forEach(([ruleKey, ruleDiagnostics], visibleIndex) => {
3334
+ visibleRuleGroups.forEach(([ruleKey, ruleDiagnostics]) => {
3255
3335
  if (isVerbose) {
3256
3336
  printVerboseRuleGroup(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
3257
3337
  return;
3258
3338
  }
3259
- if (visibleIndex < 1) {
3260
- printDetailedRuleGroup(ruleKey, ruleDiagnostics, rootDirectory, ruleNameColumnWidth);
3261
- return;
3262
- }
3263
- printCompactRuleGroupLine(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
3339
+ printDetailedRuleGroup(ruleKey, ruleDiagnostics, rootDirectory, ruleNameColumnWidth);
3264
3340
  });
3265
- if (visibleRuleGroups.length > 1 && !isVerbose) logger.break();
3266
3341
  if (hiddenRuleGroups.length > 0) printHiddenDiagnosticsSummary(hiddenRuleGroups);
3267
3342
  };
3268
3343
  const printHiddenDiagnosticsSummary = (hiddenRuleGroups) => {
@@ -3608,6 +3683,7 @@ const runScan = async (directory, options, userConfig, startTime) => {
3608
3683
  else printNoScoreHeader(noScoreMessage);
3609
3684
  return buildResult();
3610
3685
  }
3686
+ logger.break();
3611
3687
  printDiagnostics(diagnostics, options.verbose, directory);
3612
3688
  const displayedSourceFileCount = isDiffMode ? includePaths.length : lintSourceFileCount;
3613
3689
  const shouldShowShareLink = !options.offline && options.share;
@@ -4026,7 +4102,7 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
4026
4102
  };
4027
4103
  //#endregion
4028
4104
  //#region src/cli.ts
4029
- const VERSION = "0.1.1";
4105
+ const VERSION = "0.1.3";
4030
4106
  const VALID_FAIL_ON_LEVELS = new Set([
4031
4107
  "error",
4032
4108
  "warning",
@@ -6946,7 +6946,7 @@ const ALL_RULES_AT_RECOMMENDED_SEVERITY = {
6946
6946
  const eslintPlugin = {
6947
6947
  meta: {
6948
6948
  name: PLUGIN_NAMESPACE,
6949
- version: "0.1.1"
6949
+ version: "0.1.3"
6950
6950
  },
6951
6951
  rules: eslintShapedRules,
6952
6952
  configs: {
package/dist/index.d.ts CHANGED
@@ -76,6 +76,23 @@ interface ReactDoctorConfig {
76
76
  customRulesOnly?: boolean;
77
77
  share?: boolean;
78
78
  textComponents?: string[];
79
+ /**
80
+ * Names of components that safely route string-only children through a
81
+ * React Native `<Text>` internally (e.g. `heroui-native`'s `Button`,
82
+ * which stringifies its children and renders them through a
83
+ * `ButtonLabel` → `Text`). For listed components, `rn-no-raw-text`
84
+ * is suppressed ONLY when the wrapper's children are entirely
85
+ * stringifiable (no nested JSX elements). A wrapper with mixed
86
+ * children — e.g. `<Button>Save<Icon /></Button>` — still reports,
87
+ * because the wrapper can't safely route raw text alongside a
88
+ * sibling JSX element.
89
+ *
90
+ * Use this instead of `textComponents` when the component is not
91
+ * itself a text element but is known to wrap its string children
92
+ * in one. `textComponents` is the broader escape hatch and
93
+ * suppresses regardless of sibling content.
94
+ */
95
+ rawTextWrapperComponents?: string[];
79
96
  /**
80
97
  * Whether to respect inline `// eslint-disable*`, `// oxlint-disable*`,
81
98
  * and `// react-doctor-disable*` comments in source files. Default: `true`.
package/dist/index.js CHANGED
@@ -1349,6 +1349,8 @@ const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
1349
1349
  //#endregion
1350
1350
  //#region src/utils/filter-diagnostics.ts
1351
1351
  const OPENING_TAG_PATTERN = /<([A-Z][\w.]*)/;
1352
+ const JSX_CHILD_OPEN_PATTERN = /<[A-Za-z]/;
1353
+ const escapeRegExpSpecials = (rawText) => rawText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1352
1354
  const resolveCandidateReadPath = (rootDirectory, filePath) => {
1353
1355
  const normalizedFile = filePath.replace(/\\/g, "/");
1354
1356
  if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile) || /^[a-zA-Z]:\\/.test(filePath)) return filePath;
@@ -1374,21 +1376,93 @@ const isInsideTextComponent = (lines, diagnosticLine, textComponentNames) => {
1374
1376
  }
1375
1377
  return false;
1376
1378
  };
1379
+ const findOpenerAtOrAbove = (lines, upperBoundLineIndex) => {
1380
+ for (let lineIndex = upperBoundLineIndex; lineIndex >= 0; lineIndex--) {
1381
+ const match = lines[lineIndex].match(OPENING_TAG_PATTERN);
1382
+ if (!match) continue;
1383
+ const fullName = match[1];
1384
+ return {
1385
+ fullName,
1386
+ leafName: fullName.includes(".") ? fullName.split(".").at(-1) ?? fullName : fullName,
1387
+ lineIndex
1388
+ };
1389
+ }
1390
+ return null;
1391
+ };
1392
+ const resolveJsxRange = (lines, opener) => {
1393
+ const closingPattern = new RegExp(`</(?:${escapeRegExpSpecials(opener.fullName)}|${escapeRegExpSpecials(opener.leafName)})\\s*>`);
1394
+ let closerLineIndex = -1;
1395
+ let closerColumn = -1;
1396
+ for (let lineIndex = opener.lineIndex; lineIndex < lines.length; lineIndex++) {
1397
+ const match = closingPattern.exec(lines[lineIndex]);
1398
+ if (!match) continue;
1399
+ closerLineIndex = lineIndex;
1400
+ closerColumn = match.index;
1401
+ break;
1402
+ }
1403
+ if (closerLineIndex < 0) return null;
1404
+ const openerLine = lines[opener.lineIndex];
1405
+ const tagStartIndex = openerLine.indexOf(`<${opener.fullName}`);
1406
+ if (tagStartIndex < 0) return null;
1407
+ const openerEndIndex = openerLine.indexOf(">", tagStartIndex);
1408
+ let bodyText;
1409
+ if (opener.lineIndex === closerLineIndex) {
1410
+ if (openerEndIndex < 0 || openerEndIndex >= closerColumn) return null;
1411
+ bodyText = openerLine.slice(openerEndIndex + 1, closerColumn);
1412
+ } else {
1413
+ const segments = [];
1414
+ if (openerEndIndex >= 0) segments.push(openerLine.slice(openerEndIndex + 1));
1415
+ for (let lineIndex = opener.lineIndex + 1; lineIndex < closerLineIndex; lineIndex++) segments.push(lines[lineIndex]);
1416
+ segments.push(lines[closerLineIndex].slice(0, closerColumn));
1417
+ bodyText = segments.join("\n");
1418
+ }
1419
+ return {
1420
+ closerLineIndex,
1421
+ closerColumn,
1422
+ bodyText
1423
+ };
1424
+ };
1425
+ const isInsideStringOnlyWrapper = (lines, diagnosticLine, diagnosticColumn, wrapperNames) => {
1426
+ const diagnosticLineIndex = diagnosticLine - 1;
1427
+ const diagnosticColumnIndex = Math.max(0, diagnosticColumn - 1);
1428
+ let upperBoundLineIndex = diagnosticLineIndex;
1429
+ while (upperBoundLineIndex >= 0) {
1430
+ const opener = findOpenerAtOrAbove(lines, upperBoundLineIndex);
1431
+ if (!opener) return false;
1432
+ const range = resolveJsxRange(lines, opener);
1433
+ if (range === null) {
1434
+ upperBoundLineIndex = opener.lineIndex - 1;
1435
+ continue;
1436
+ }
1437
+ if (range.closerLineIndex < diagnosticLineIndex || range.closerLineIndex === diagnosticLineIndex && range.closerColumn <= diagnosticColumnIndex) {
1438
+ upperBoundLineIndex = opener.lineIndex - 1;
1439
+ continue;
1440
+ }
1441
+ if (!wrapperNames.has(opener.fullName) && !wrapperNames.has(opener.leafName)) return false;
1442
+ return !JSX_CHILD_OPEN_PATTERN.test(range.bodyText);
1443
+ }
1444
+ return false;
1445
+ };
1377
1446
  const filterIgnoredDiagnostics = (diagnostics, config, rootDirectory, readFileLinesSync) => {
1378
1447
  const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules.filter((rule) => typeof rule === "string") : []);
1379
1448
  const ignoredFilePatterns = compileIgnoredFilePatterns(config);
1380
1449
  const compiledOverrides = compileIgnoreOverrides(config);
1381
1450
  const textComponentNames = new Set(Array.isArray(config.textComponents) ? config.textComponents.filter((name) => typeof name === "string") : []);
1382
1451
  const hasTextComponents = textComponentNames.size > 0;
1452
+ const rawTextWrapperComponentNames = new Set(Array.isArray(config.rawTextWrapperComponents) ? config.rawTextWrapperComponents.filter((name) => typeof name === "string") : []);
1453
+ const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
1383
1454
  const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);
1384
1455
  return diagnostics.filter((diagnostic) => {
1385
1456
  const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
1386
1457
  if (ignoredRules.has(ruleIdentifier)) return false;
1387
1458
  if (isFileIgnoredByPatterns(diagnostic.filePath, rootDirectory, ignoredFilePatterns)) return false;
1388
1459
  if (isDiagnosticIgnoredByOverrides(diagnostic, rootDirectory, compiledOverrides)) return false;
1389
- if (hasTextComponents && diagnostic.rule === "rn-no-raw-text" && diagnostic.line > 0) {
1460
+ if ((hasTextComponents || hasRawTextWrappers) && diagnostic.rule === "rn-no-raw-text" && diagnostic.line > 0) {
1390
1461
  const lines = getFileLines(diagnostic.filePath);
1391
- if (lines && isInsideTextComponent(lines, diagnostic.line, textComponentNames)) return false;
1462
+ if (lines) {
1463
+ if (hasTextComponents && isInsideTextComponent(lines, diagnostic.line, textComponentNames)) return false;
1464
+ if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return false;
1465
+ }
1392
1466
  }
1393
1467
  return true;
1394
1468
  });
@@ -1850,22 +1924,22 @@ const TANSTACK_START_RULES = {
1850
1924
  "react-doctor/tanstack-start-loader-parallel-fetch": "warn"
1851
1925
  };
1852
1926
  const REACT_COMPILER_RULES = {
1853
- "react-hooks-js/set-state-in-render": "warn",
1854
- "react-hooks-js/immutability": "warn",
1855
- "react-hooks-js/refs": "warn",
1856
- "react-hooks-js/purity": "warn",
1857
- "react-hooks-js/hooks": "warn",
1858
- "react-hooks-js/set-state-in-effect": "warn",
1859
- "react-hooks-js/globals": "warn",
1860
- "react-hooks-js/error-boundaries": "warn",
1861
- "react-hooks-js/preserve-manual-memoization": "warn",
1862
- "react-hooks-js/unsupported-syntax": "warn",
1863
- "react-hooks-js/component-hook-factories": "warn",
1864
- "react-hooks-js/static-components": "warn",
1865
- "react-hooks-js/use-memo": "warn",
1866
- "react-hooks-js/void-use-memo": "warn",
1867
- "react-hooks-js/incompatible-library": "warn",
1868
- "react-hooks-js/todo": "warn"
1927
+ "react-hooks-js/set-state-in-render": "error",
1928
+ "react-hooks-js/immutability": "error",
1929
+ "react-hooks-js/refs": "error",
1930
+ "react-hooks-js/purity": "error",
1931
+ "react-hooks-js/hooks": "error",
1932
+ "react-hooks-js/set-state-in-effect": "error",
1933
+ "react-hooks-js/globals": "error",
1934
+ "react-hooks-js/error-boundaries": "error",
1935
+ "react-hooks-js/preserve-manual-memoization": "error",
1936
+ "react-hooks-js/unsupported-syntax": "error",
1937
+ "react-hooks-js/component-hook-factories": "error",
1938
+ "react-hooks-js/static-components": "error",
1939
+ "react-hooks-js/use-memo": "error",
1940
+ "react-hooks-js/void-use-memo": "error",
1941
+ "react-hooks-js/incompatible-library": "error",
1942
+ "react-hooks-js/todo": "error"
1869
1943
  };
1870
1944
  const readPluginRuleNames = (pluginSpecifier) => {
1871
1945
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",