react-doctor 0.0.34 → 0.0.36

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/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/types.d.ts
2
2
  type FailOnLevel = "error" | "warning" | "none";
3
- type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "expo" | "react-native" | "unknown";
3
+ type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "expo" | "react-native" | "tanstack-start" | "unknown";
4
4
  interface ProjectInfo {
5
5
  rootDirectory: string;
6
6
  projectName: string;
@@ -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,WAAA;AAAA,KAEA,SAAA;AAAA,UAUK,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,UAmBe,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;EACA,MAAA,GAAS,WAAA;EACT,eAAA;EACA,KAAA;EACA,cAAA;AAAA;;;cC7FW,WAAA,GAAe,SAAA,UAAmB,kBAAA,cAA8B,QAAA;AAAA,cAiBhE,iBAAA,GAAqB,SAAA;;;UClFjB,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,WAAA;AAAA,KAEA,SAAA;AAAA,UAWK,WAAA;EACf,aAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA,EAAW,SAAA;EACX,aAAA;EACA,gBAAA;EACA,eAAA;AAAA;AAAA,UAiCe,UAAA;EACf,QAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;AAAA;AAAA,UA4Be,WAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAmBe,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;EACA,MAAA,GAAS,WAAA;EACT,eAAA;EACA,KAAA;EACA,cAAA;AAAA;;;cC9FW,WAAA,GAAe,SAAA,UAAmB,kBAAA,cAA8B,QAAA;AAAA,cAiBhE,iBAAA,GAAqB,SAAA;;;UClFjB,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
@@ -382,9 +382,11 @@ const EXPO_APP_CONFIG_FILENAMES = [
382
382
  "app.config.js",
383
383
  "app.config.ts"
384
384
  ];
385
- const REACT_COMPILER_CONFIG_PATTERN = /react-compiler|reactCompiler/;
385
+ const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
386
+ const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*true\b/;
386
387
  const FRAMEWORK_PACKAGES = {
387
388
  next: "nextjs",
389
+ "@tanstack/react-start": "tanstack-start",
388
390
  vite: "vite",
389
391
  "react-scripts": "cra",
390
392
  "@remix-run/react": "remix",
@@ -630,12 +632,12 @@ const hasCompilerPackage = (packageJson) => {
630
632
  const allDependencies = collectAllDependencies(packageJson);
631
633
  return Object.keys(allDependencies).some((packageName) => REACT_COMPILER_PACKAGES.has(packageName));
632
634
  };
633
- const fileContainsPattern = (filePath, pattern) => {
635
+ const hasCompilerInConfigFile = (filePath) => {
634
636
  if (!isFile(filePath)) return false;
635
637
  const content = fs.readFileSync(filePath, "utf-8");
636
- return pattern.test(content);
638
+ return REACT_COMPILER_ENABLED_FLAG_PATTERN.test(content) || REACT_COMPILER_PACKAGE_REFERENCE_PATTERN.test(content);
637
639
  };
638
- const hasCompilerInConfigFiles = (directory, filenames) => filenames.some((filename) => fileContainsPattern(path.join(directory, filename), REACT_COMPILER_CONFIG_PATTERN));
640
+ const hasCompilerInConfigFiles = (directory, filenames) => filenames.some((filename) => hasCompilerInConfigFile(path.join(directory, filename)));
639
641
  const detectReactCompiler = (directory, packageJson) => {
640
642
  if (hasCompilerPackage(packageJson)) return true;
641
643
  if (hasCompilerInConfigFiles(directory, NEXT_CONFIG_FILENAMES)) return true;
@@ -917,6 +919,22 @@ const REACT_NATIVE_RULES = {
917
919
  "react-doctor/rn-prefer-reanimated": "warn",
918
920
  "react-doctor/rn-no-single-element-style-array": "warn"
919
921
  };
922
+ const TANSTACK_START_RULES = {
923
+ "react-doctor/tanstack-start-route-property-order": "error",
924
+ "react-doctor/tanstack-start-no-direct-fetch-in-loader": "warn",
925
+ "react-doctor/tanstack-start-server-fn-validate-input": "warn",
926
+ "react-doctor/tanstack-start-no-useeffect-fetch": "warn",
927
+ "react-doctor/tanstack-start-missing-head-content": "warn",
928
+ "react-doctor/tanstack-start-no-anchor-element": "warn",
929
+ "react-doctor/tanstack-start-server-fn-method-order": "error",
930
+ "react-doctor/tanstack-start-no-navigate-in-render": "warn",
931
+ "react-doctor/tanstack-start-no-dynamic-server-fn-import": "error",
932
+ "react-doctor/tanstack-start-no-use-server-in-handler": "error",
933
+ "react-doctor/tanstack-start-no-secrets-in-loader": "error",
934
+ "react-doctor/tanstack-start-get-mutation": "warn",
935
+ "react-doctor/tanstack-start-redirect-in-try-catch": "warn",
936
+ "react-doctor/tanstack-start-loader-parallel-fetch": "warn"
937
+ };
920
938
  const REACT_COMPILER_RULES = {
921
939
  "react-hooks-js/set-state-in-render": "error",
922
940
  "react-hooks-js/immutability": "error",
@@ -1006,12 +1024,14 @@ const createOxlintConfig = ({ pluginPath, framework, hasReactCompiler, customRul
1006
1024
  "react-doctor/rendering-animate-svg-wrapper": "warn",
1007
1025
  "react-doctor/no-inline-prop-on-memo-component": "warn",
1008
1026
  "react-doctor/rendering-hydration-no-flicker": "warn",
1027
+ "react-doctor/rendering-script-defer-async": "warn",
1009
1028
  "react-doctor/no-transition-all": "warn",
1010
1029
  "react-doctor/no-global-css-variable-animation": "error",
1011
1030
  "react-doctor/no-large-animated-blur": "warn",
1012
1031
  "react-doctor/no-scale-from-zero": "warn",
1013
1032
  "react-doctor/no-permanent-will-change": "warn",
1014
1033
  "react-doctor/no-secrets-in-client-code": "error",
1034
+ "react-doctor/js-flatmap-filter": "warn",
1015
1035
  "react-doctor/no-barrel-import": "warn",
1016
1036
  "react-doctor/no-full-lodash-import": "warn",
1017
1037
  "react-doctor/no-moment": "warn",
@@ -1024,9 +1044,31 @@ const createOxlintConfig = ({ pluginPath, framework, hasReactCompiler, customRul
1024
1044
  "react-doctor/server-auth-actions": "error",
1025
1045
  "react-doctor/server-after-nonblocking": "warn",
1026
1046
  "react-doctor/client-passive-event-listeners": "warn",
1047
+ "react-doctor/query-stable-query-client": "error",
1048
+ "react-doctor/query-no-rest-destructuring": "warn",
1049
+ "react-doctor/query-no-void-query-fn": "warn",
1050
+ "react-doctor/query-no-query-in-effect": "warn",
1051
+ "react-doctor/query-mutation-missing-invalidation": "warn",
1052
+ "react-doctor/query-no-usequery-for-mutation": "warn",
1053
+ "react-doctor/no-inline-bounce-easing": "warn",
1054
+ "react-doctor/no-z-index-9999": "warn",
1055
+ "react-doctor/no-inline-exhaustive-style": "warn",
1056
+ "react-doctor/no-side-tab-border": "warn",
1057
+ "react-doctor/no-pure-black-background": "warn",
1058
+ "react-doctor/no-gradient-text": "warn",
1059
+ "react-doctor/no-dark-mode-glow": "warn",
1060
+ "react-doctor/no-justified-text": "warn",
1061
+ "react-doctor/no-tiny-text": "warn",
1062
+ "react-doctor/no-wide-letter-spacing": "warn",
1063
+ "react-doctor/no-gray-on-colored-background": "warn",
1064
+ "react-doctor/no-layout-transition-inline": "warn",
1065
+ "react-doctor/no-disabled-zoom": "error",
1066
+ "react-doctor/no-outline-none": "warn",
1067
+ "react-doctor/no-long-transition-duration": "warn",
1027
1068
  "react-doctor/async-parallel": "warn",
1028
1069
  ...framework === "nextjs" ? NEXTJS_RULES : {},
1029
- ...framework === "expo" || framework === "react-native" ? REACT_NATIVE_RULES : {}
1070
+ ...framework === "expo" || framework === "react-native" ? REACT_NATIVE_RULES : {},
1071
+ ...framework === "tanstack-start" ? TANSTACK_START_RULES : {}
1030
1072
  }
1031
1073
  });
1032
1074
 
@@ -1103,6 +1145,7 @@ const RULE_CATEGORY_MAP = {
1103
1145
  "react-doctor/rendering-animate-svg-wrapper": "Performance",
1104
1146
  "react-doctor/rendering-usetransition-loading": "Performance",
1105
1147
  "react-doctor/rendering-hydration-no-flicker": "Performance",
1148
+ "react-doctor/rendering-script-defer-async": "Performance",
1106
1149
  "react-doctor/no-transition-all": "Performance",
1107
1150
  "react-doctor/no-global-css-variable-animation": "Performance",
1108
1151
  "react-doctor/no-large-animated-blur": "Performance",
@@ -1137,6 +1180,28 @@ const RULE_CATEGORY_MAP = {
1137
1180
  "react-doctor/server-auth-actions": "Server",
1138
1181
  "react-doctor/server-after-nonblocking": "Server",
1139
1182
  "react-doctor/client-passive-event-listeners": "Performance",
1183
+ "react-doctor/query-stable-query-client": "TanStack Query",
1184
+ "react-doctor/query-no-rest-destructuring": "TanStack Query",
1185
+ "react-doctor/query-no-void-query-fn": "TanStack Query",
1186
+ "react-doctor/query-no-query-in-effect": "TanStack Query",
1187
+ "react-doctor/query-mutation-missing-invalidation": "TanStack Query",
1188
+ "react-doctor/query-no-usequery-for-mutation": "TanStack Query",
1189
+ "react-doctor/no-inline-bounce-easing": "Performance",
1190
+ "react-doctor/no-z-index-9999": "Architecture",
1191
+ "react-doctor/no-inline-exhaustive-style": "Architecture",
1192
+ "react-doctor/no-side-tab-border": "Architecture",
1193
+ "react-doctor/no-pure-black-background": "Architecture",
1194
+ "react-doctor/no-gradient-text": "Architecture",
1195
+ "react-doctor/no-dark-mode-glow": "Architecture",
1196
+ "react-doctor/no-justified-text": "Accessibility",
1197
+ "react-doctor/no-tiny-text": "Accessibility",
1198
+ "react-doctor/no-wide-letter-spacing": "Architecture",
1199
+ "react-doctor/no-gray-on-colored-background": "Accessibility",
1200
+ "react-doctor/no-layout-transition-inline": "Performance",
1201
+ "react-doctor/no-disabled-zoom": "Accessibility",
1202
+ "react-doctor/no-outline-none": "Accessibility",
1203
+ "react-doctor/no-long-transition-duration": "Performance",
1204
+ "react-doctor/js-flatmap-filter": "Performance",
1140
1205
  "react-doctor/async-parallel": "Performance",
1141
1206
  "react-doctor/rn-no-raw-text": "React Native",
1142
1207
  "react-doctor/rn-no-deprecated-modules": "React Native",
@@ -1145,7 +1210,21 @@ const RULE_CATEGORY_MAP = {
1145
1210
  "react-doctor/rn-no-inline-flatlist-renderitem": "React Native",
1146
1211
  "react-doctor/rn-no-legacy-shadow-styles": "React Native",
1147
1212
  "react-doctor/rn-prefer-reanimated": "React Native",
1148
- "react-doctor/rn-no-single-element-style-array": "React Native"
1213
+ "react-doctor/rn-no-single-element-style-array": "React Native",
1214
+ "react-doctor/tanstack-start-route-property-order": "TanStack Start",
1215
+ "react-doctor/tanstack-start-no-direct-fetch-in-loader": "TanStack Start",
1216
+ "react-doctor/tanstack-start-server-fn-validate-input": "TanStack Start",
1217
+ "react-doctor/tanstack-start-no-useeffect-fetch": "TanStack Start",
1218
+ "react-doctor/tanstack-start-missing-head-content": "TanStack Start",
1219
+ "react-doctor/tanstack-start-no-anchor-element": "TanStack Start",
1220
+ "react-doctor/tanstack-start-server-fn-method-order": "TanStack Start",
1221
+ "react-doctor/tanstack-start-no-navigate-in-render": "TanStack Start",
1222
+ "react-doctor/tanstack-start-no-dynamic-server-fn-import": "TanStack Start",
1223
+ "react-doctor/tanstack-start-no-use-server-in-handler": "TanStack Start",
1224
+ "react-doctor/tanstack-start-no-secrets-in-loader": "Security",
1225
+ "react-doctor/tanstack-start-get-mutation": "Security",
1226
+ "react-doctor/tanstack-start-redirect-in-try-catch": "TanStack Start",
1227
+ "react-doctor/tanstack-start-loader-parallel-fetch": "Performance"
1149
1228
  };
1150
1229
  const RULE_HELP_MAP = {
1151
1230
  "no-derived-state-effect": "For derived state, compute inline: `const x = fn(dep)`. For state resets on prop change, use a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
@@ -1167,6 +1246,7 @@ const RULE_HELP_MAP = {
1167
1246
  "rendering-animate-svg-wrapper": "Wrap the SVG: `<motion.div animate={...}><svg>...</svg></motion.div>`",
1168
1247
  "rendering-usetransition-loading": "Replace with `const [isPending, startTransition] = useTransition()` — avoids a re-render for the loading state",
1169
1248
  "rendering-hydration-no-flicker": "Use `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` or add `suppressHydrationWarning` to the element",
1249
+ "rendering-script-defer-async": "Add `defer` for DOM-dependent scripts or `async` for independent ones (analytics). In Next.js, use `<Script strategy=\"afterInteractive\" />` instead",
1170
1250
  "no-transition-all": "List specific properties: `transition: \"opacity 200ms, transform 200ms\"` — or in Tailwind use `transition-colors`, `transition-opacity`, or `transition-transform`",
1171
1251
  "no-global-css-variable-animation": "Set the variable on the nearest element instead of a parent, or use `@property` with `inherits: false` to prevent cascade. Better yet, use targeted `element.style.transform` updates",
1172
1252
  "no-large-animated-blur": "Keep blur radius under 10px, or apply blur to a smaller element. Large blurs multiply GPU memory usage with layer size",
@@ -1179,6 +1259,21 @@ const RULE_HELP_MAP = {
1179
1259
  "prefer-dynamic-import": "Use `const Component = dynamic(() => import('library'), { ssr: false })` from next/dynamic or React.lazy()",
1180
1260
  "use-lazy-motion": "Use `import { LazyMotion, m } from \"framer-motion\"` with `domAnimation` features — saves ~30kb",
1181
1261
  "no-undeferred-third-party": "Use `next/script` with `strategy=\"lazyOnload\"` or add the `defer` attribute",
1262
+ "no-inline-bounce-easing": "Use `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo) for natural deceleration — objects in the real world don't bounce",
1263
+ "no-z-index-9999": "Define a z-index scale in your design tokens (e.g. dropdown: 10, modal: 20, toast: 30). Create a new stacking context with `isolation: isolate` instead of escalating values",
1264
+ "no-inline-exhaustive-style": "Move styles to a CSS class, CSS module, Tailwind utilities, or a styled component — inline objects with many properties hurt readability and create new references every render",
1265
+ "no-side-tab-border": "Use a subtler accent (box-shadow inset, background gradient, or border-bottom) instead of a thick one-sided border",
1266
+ "no-pure-black-background": "Tint the background slightly toward your brand hue — e.g. `#0a0a0f` or Tailwind's `bg-gray-950`. Pure black looks harsh on modern displays",
1267
+ "no-gradient-text": "Use solid text colors for readability. If you need emphasis, use font weight, size, or a distinct color instead of gradients",
1268
+ "no-dark-mode-glow": "Use a subtle `box-shadow` with neutral colors for depth, or `border` with low opacity. Colored glows on dark backgrounds are the default AI-generated aesthetic",
1269
+ "no-justified-text": "Use `text-align: left` for body text, or add `hyphens: auto` and `overflow-wrap: break-word` if you must justify",
1270
+ "no-tiny-text": "Use at least 12px for body content, 16px is ideal. Small text is hard to read, especially on high-DPI mobile screens",
1271
+ "no-wide-letter-spacing": "Reserve wide tracking (letter-spacing > 0.05em) for short uppercase labels, navigation items, and buttons — not body text",
1272
+ "no-gray-on-colored-background": "Use a darker shade of the background color for text, or white/near-white for contrast. Gray text on colored backgrounds looks washed out",
1273
+ "no-layout-transition-inline": "Use `transform` and `opacity` for transitions — they run on the compositor thread. For height animations, use `grid-template-rows: 0fr → 1fr`",
1274
+ "no-disabled-zoom": "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If your layout breaks at 200% zoom, fix the layout — don't punish users with disabilities",
1275
+ "no-outline-none": "Use `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px }` to show focus only for keyboard users while hiding it for mouse clicks",
1276
+ "no-long-transition-duration": "Keep UI transitions under 1s — 100-150ms for instant feedback, 200-300ms for state changes, 300-500ms for layout changes. Use longer durations only for page-load hero animations",
1182
1277
  "no-array-index-as-key": "Use a stable unique identifier: `key={item.id}` or `key={item.slug}` — index keys break on reorder/filter",
1183
1278
  "rendering-conditional-render": "Change to `{items.length > 0 && <List />}` or use a ternary: `{items.length ? <List /> : null}`",
1184
1279
  "no-prevent-default": "Use `<form action={serverAction}>` (works without JS) or `<button>` instead of `<a>` with preventDefault",
@@ -1188,7 +1283,7 @@ const RULE_HELP_MAP = {
1188
1283
  "nextjs-no-use-search-params-without-suspense": "Wrap the component using useSearchParams: `<Suspense fallback={<Skeleton />}><SearchComponent /></Suspense>`",
1189
1284
  "nextjs-no-client-fetch-for-server-data": "Remove 'use client' and fetch directly in the Server Component — no API round-trip, secrets stay on server",
1190
1285
  "nextjs-missing-metadata": "Add `export const metadata = { title: '...', description: '...' }` or `export async function generateMetadata()`",
1191
- "nextjs-no-client-side-redirect": "Use `redirect('/path')` from 'next/navigation' directly (works in both server and client components), or handle in middleware",
1286
+ "nextjs-no-client-side-redirect": "Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)",
1192
1287
  "nextjs-no-redirect-in-try-catch": "Move the redirect/notFound call outside the try block, or add `unstable_rethrow(error)` in the catch",
1193
1288
  "nextjs-image-missing-sizes": "Add sizes for responsive behavior: `sizes=\"(max-width: 768px) 100vw, 50vw\"` matching your layout breakpoints",
1194
1289
  "nextjs-no-native-script": "`import Script from \"next/script\"` — use `strategy=\"afterInteractive\"` for analytics or `\"lazyOnload\"` for widgets",
@@ -1201,6 +1296,13 @@ const RULE_HELP_MAP = {
1201
1296
  "server-auth-actions": "Add `const session = await auth()` at the top and throw/redirect if unauthorized before any data access",
1202
1297
  "server-after-nonblocking": "`import { after } from 'next/server'` then wrap: `after(() => analytics.track(...))` — response isn't blocked",
1203
1298
  "client-passive-event-listeners": "Add `{ passive: true }` as the third argument: `addEventListener('scroll', handler, { passive: true })`",
1299
+ "query-stable-query-client": "Move `new QueryClient()` to module scope or wrap in `useState(() => new QueryClient())` — recreating it on every render resets the entire cache",
1300
+ "query-no-rest-destructuring": "Destructure only the fields you need: `const { data, isLoading } = useQuery(...)` — rest destructuring subscribes to all fields and causes extra re-renders",
1301
+ "query-no-void-query-fn": "queryFn must return a value for the cache. Use the `enabled` option to conditionally disable the query instead of returning undefined",
1302
+ "query-no-query-in-effect": "React Query manages refetching automatically via queryKey dependencies and the `enabled` option — manual refetch() in useEffect is usually unnecessary",
1303
+ "query-mutation-missing-invalidation": "Add `onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] })` so cached data stays in sync after the mutation",
1304
+ "query-no-usequery-for-mutation": "Use `useMutation()` for POST/PUT/DELETE — it provides onSuccess/onError callbacks, doesn't auto-refetch, and correctly models write operations",
1305
+ "js-flatmap-filter": "Use `.flatMap(item => condition ? [value] : [])` — transforms and filters in a single pass instead of creating an intermediate array",
1204
1306
  "async-parallel": "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to run independent operations concurrently",
1205
1307
  "rn-no-raw-text": "Wrap text in a `<Text>` component: `<Text>{value}</Text>` — raw strings outside `<Text>` crash on React Native",
1206
1308
  "rn-no-deprecated-modules": "Import from the community package instead — deprecated modules were removed from the react-native core",
@@ -1209,7 +1311,21 @@ const RULE_HELP_MAP = {
1209
1311
  "rn-no-inline-flatlist-renderitem": "Extract renderItem to a named function or wrap in useCallback to avoid re-creating on every render",
1210
1312
  "rn-no-legacy-shadow-styles": "Use `boxShadow` for cross-platform shadows on the new architecture instead of platform-specific shadow properties",
1211
1313
  "rn-prefer-reanimated": "Use `import Animated from 'react-native-reanimated'` — animations run on the UI thread instead of the JS thread",
1212
- "rn-no-single-element-style-array": "Use `style={value}` instead of `style={[value]}` — single-element arrays add unnecessary allocation"
1314
+ "rn-no-single-element-style-array": "Use `style={value}` instead of `style={[value]}` — single-element arrays add unnecessary allocation",
1315
+ "tanstack-start-route-property-order": "Follow the order: params/validateSearch → loaderDeps → context → beforeLoad → loader → head. See https://tanstack.com/router/latest/docs/eslint/create-route-property-order",
1316
+ "tanstack-start-no-direct-fetch-in-loader": "Use `createServerFn()` from @tanstack/react-start — provides type-safe RPC, input validation, and proper server/client code splitting",
1317
+ "tanstack-start-server-fn-validate-input": "Add `.inputValidator(schema)` before `.handler()` — data crosses a network boundary and must be validated at runtime",
1318
+ "tanstack-start-no-useeffect-fetch": "Fetch data in the route `loader` instead — the router coordinates loading before rendering to avoid waterfalls",
1319
+ "tanstack-start-missing-head-content": "Add `<HeadContent />` inside `<head>` in your __root route — without it, route `head()` meta tags are silently dropped",
1320
+ "tanstack-start-no-anchor-element": "`import { Link } from '@tanstack/react-router'` — enables type-safe routes, preloading via `preload=\"intent\"`, and client-side navigation",
1321
+ "tanstack-start-server-fn-method-order": "Chain methods in order: .middleware() → .inputValidator() → .client() → .server() → .handler() — types depend on this sequence",
1322
+ "tanstack-start-no-navigate-in-render": "Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader` instead — navigate() during render causes hydration issues",
1323
+ "tanstack-start-no-dynamic-server-fn-import": "Use `import { myFn } from '~/utils/my.functions'` — the bundler replaces server code with RPC stubs only for static imports",
1324
+ "tanstack-start-no-use-server-in-handler": "TanStack Start handles server boundaries automatically via the Vite plugin — \"use server\" inside createServerFn causes compilation errors",
1325
+ "tanstack-start-no-secrets-in-loader": "Loaders are isomorphic (run on both server and client). Wrap secret access in `createServerFn()` so it stays server-only",
1326
+ "tanstack-start-get-mutation": "Use `createServerFn({ method: 'POST' })` for data modifications — GET requests can be triggered by prefetching and are vulnerable to CSRF",
1327
+ "tanstack-start-redirect-in-try-catch": "TanStack Router's `redirect()` and `notFound()` throw special errors caught by the router. Move them outside the try block or re-throw in the catch",
1328
+ "tanstack-start-loader-parallel-fetch": "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to avoid request waterfalls in route loaders"
1213
1329
  };
1214
1330
  const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
1215
1331
  const REACT_COMPILER_MESSAGE = "React Compiler can't optimize this code";