oxlint-plugin-react-doctor 0.6.0-dev.6885698 → 0.6.1-dev.4e91677

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.
Files changed (2) hide show
  1. package/dist/index.js +89 -96
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1573,7 +1573,6 @@ const LAYOUT_FILE_NAMES = [
1573
1573
  ];
1574
1574
  const METADATA_EXPORT_NAMES = ["metadata", "generateMetadata"];
1575
1575
  const INTERNAL_PAGE_PATH_PATTERN = /\/(?:(?:\((?:dashboard|admin|settings|account|internal|manage|console|portal|auth|onboarding|app|ee|protected)\))|(?:dashboard|admin|settings|account|internal|manage|console|portal))\//i;
1576
- const PAGES_DIRECTORY_PATTERN = /\/pages\//;
1577
1576
  const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
1578
1577
  "redirect",
1579
1578
  "permanentRedirect",
@@ -1591,7 +1590,6 @@ const CACHE_REVALIDATION_FUNCTION_NAMES = new Set([
1591
1590
  ]);
1592
1591
  const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
1593
1592
  const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
1594
- const APP_DIRECTORY_PATTERN = /\/app\//;
1595
1593
  const ROUTE_HANDLER_FILE_PATTERN = new RegExp(`/route\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
1596
1594
  const CRON_ROUTE_PATTERN = /\/(?:cron|jobs\/cron)(?:\/|$)/i;
1597
1595
  const MUTATING_ROUTE_SEGMENTS = new Set([
@@ -1628,7 +1626,7 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
1628
1626
  };
1629
1627
  //#endregion
1630
1628
  //#region src/plugin/utils/normalize-filename.ts
1631
- const normalizeFilename$1 = (filename) => filename.replaceAll("\\", "/");
1629
+ const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
1632
1630
  //#endregion
1633
1631
  //#region src/plugin/utils/is-generated-image-render-context.ts
1634
1632
  const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
@@ -1636,7 +1634,7 @@ const SATORI_MODULE = "satori";
1636
1634
  const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
1637
1635
  const isGeneratedImageRenderFilename = (rawFilename) => {
1638
1636
  if (!rawFilename) return false;
1639
- return isNextjsMetadataImageRouteFilename(normalizeFilename$1(rawFilename));
1637
+ return isNextjsMetadataImageRouteFilename(normalizeFilename(rawFilename));
1640
1638
  };
1641
1639
  const isImageResponseCallee = (contextNode, callee) => {
1642
1640
  if (isNodeOfType(callee, "Identifier")) return IMAGE_RESPONSE_MODULES.some((moduleSource) => getImportedNameFromModule(contextNode, callee.name, moduleSource) === "ImageResponse");
@@ -4598,7 +4596,7 @@ const asyncParallel = defineRule({
4598
4596
  severity: "warn",
4599
4597
  recommendation: "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` so independent calls run at the same time",
4600
4598
  create: (context) => {
4601
- const filename = normalizeFilename$1(context.filename ?? "");
4599
+ const filename = normalizeFilename(context.filename ?? "");
4602
4600
  const isBrowserTestFile = BROWSER_TEST_FILE_PATTERN.test(filename);
4603
4601
  let hasTestLibraryImport = false;
4604
4602
  const shouldSkipFile = () => isBrowserTestFile || hasTestLibraryImport;
@@ -8182,7 +8180,7 @@ const expoNoNonInlinedEnv = defineRule({
8182
8180
  severity: "warn",
8183
8181
  recommendation: "Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",
8184
8182
  create: (context) => {
8185
- const filename = normalizeFilename$1(context.filename ?? "");
8183
+ const filename = normalizeFilename(context.filename ?? "");
8186
8184
  if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$3;
8187
8185
  return {
8188
8186
  MemberExpression(node) {
@@ -11617,7 +11615,7 @@ const jsxFilenameExtension = defineRule({
11617
11615
  create: (context) => {
11618
11616
  const settings = resolveSettings$33(context.settings);
11619
11617
  const allowedExtensions = normalizeExtensions(settings.extensions);
11620
- const filename = normalizeFilename$1(context.filename ?? "fixture.tsx");
11618
+ const filename = normalizeFilename(context.filename ?? "fixture.tsx");
11621
11619
  const extensionOnly = path.extname(filename).slice(1);
11622
11620
  const fileHasAllowedExtension = allowedExtensions.has(extensionOnly);
11623
11621
  let didReportMismatch = false;
@@ -15257,6 +15255,52 @@ const nextjsAsyncClientComponent = defineRule({
15257
15255
  }
15258
15256
  });
15259
15257
  //#endregion
15258
+ //#region src/plugin/utils/get-project-relative-filename.ts
15259
+ const getProjectRelativeFilename = (filename, rootDirectory) => {
15260
+ const normalizedFilename = normalizeFilename(filename);
15261
+ if (!rootDirectory) return normalizedFilename;
15262
+ const rootDirectoryPrefix = `${normalizeFilename(rootDirectory).replace(/\/+$/, "")}/`;
15263
+ if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
15264
+ return normalizedFilename.slice(rootDirectoryPrefix.length);
15265
+ };
15266
+ //#endregion
15267
+ //#region src/plugin/utils/get-react-doctor-setting.ts
15268
+ const readReactDoctorSettingsBag = (settings) => {
15269
+ const reactDoctorSettings = settings?.["react-doctor"];
15270
+ if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return null;
15271
+ return reactDoctorSettings;
15272
+ };
15273
+ const readOwnPropertyValue = (bag, settingName) => Object.getOwnPropertyDescriptor(bag, settingName)?.value;
15274
+ const getReactDoctorStringSetting = (settings, settingName) => {
15275
+ const bag = readReactDoctorSettingsBag(settings);
15276
+ if (!bag) return void 0;
15277
+ const settingValue = readOwnPropertyValue(bag, settingName);
15278
+ return typeof settingValue === "string" ? settingValue : void 0;
15279
+ };
15280
+ const getReactDoctorNumberSetting = (settings, settingName) => {
15281
+ const bag = readReactDoctorSettingsBag(settings);
15282
+ if (!bag) return void 0;
15283
+ const settingValue = readOwnPropertyValue(bag, settingName);
15284
+ return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
15285
+ };
15286
+ const getReactDoctorStringArraySetting = (settings, settingName) => {
15287
+ const bag = readReactDoctorSettingsBag(settings);
15288
+ if (!bag) return [];
15289
+ const settingValue = readOwnPropertyValue(bag, settingName);
15290
+ if (!Array.isArray(settingValue)) return [];
15291
+ return settingValue.filter((entry) => typeof entry === "string" && entry.length > 0);
15292
+ };
15293
+ //#endregion
15294
+ //#region src/plugin/utils/is-in-project-directory.ts
15295
+ const isInProjectDirectory = (context, directoryPath) => {
15296
+ const filename = normalizeFilename(context.filename ?? "");
15297
+ if (filename.length === 0) return false;
15298
+ const directorySegment = `/${directoryPath}/`;
15299
+ const relativeFilename = getProjectRelativeFilename(filename, getReactDoctorStringSetting(context.settings, "rootDirectory"));
15300
+ if (relativeFilename !== filename) return relativeFilename.startsWith(`${directoryPath}/`) || relativeFilename.includes(directorySegment);
15301
+ return filename.indexOf(directorySegment, 1) !== -1;
15302
+ };
15303
+ //#endregion
15260
15304
  //#region src/plugin/rules/nextjs/nextjs-error-boundary-missing-use-client.ts
15261
15305
  const nextjsErrorBoundaryMissingUseClient = defineRule({
15262
15306
  id: "nextjs-error-boundary-missing-use-client",
@@ -15266,8 +15310,8 @@ const nextjsErrorBoundaryMissingUseClient = defineRule({
15266
15310
  severity: "error",
15267
15311
  recommendation: "Add `'use client'` at the top of this file. Error boundaries must be Client Components to catch and render fallback UI",
15268
15312
  create: (context) => ({ Program(programNode) {
15269
- const filename = normalizeFilename$1(context.filename ?? "");
15270
- if (!APP_DIRECTORY_PATTERN.test(filename)) return;
15313
+ const filename = normalizeFilename(context.filename ?? "");
15314
+ if (!isInProjectDirectory(context, "app")) return;
15271
15315
  if (!ERROR_BOUNDARY_FILE_PATTERN.test(filename)) return;
15272
15316
  if (hasDirective(programNode, "use client")) return;
15273
15317
  context.report({
@@ -15298,8 +15342,8 @@ const nextjsGlobalErrorMissingHtmlBody = defineRule({
15298
15342
  severity: "error",
15299
15343
  recommendation: "Wrap your error UI in `<html><body>...</body></html>`. The root layout is unmounted when global-error renders",
15300
15344
  create: (context) => ({ Program(programNode) {
15301
- const filename = normalizeFilename$1(context.filename ?? "");
15302
- if (!APP_DIRECTORY_PATTERN.test(filename)) return;
15345
+ const filename = normalizeFilename(context.filename ?? "");
15346
+ if (!isInProjectDirectory(context, "app")) return;
15303
15347
  if (!GLOBAL_ERROR_FILE_PATTERN.test(filename)) return;
15304
15348
  const foundTags = fileContainsJsxElements(programNode, REQUIRED_HTML_TAGS);
15305
15349
  const missingTags = REQUIRED_HTML_TAGS.filter((tagName) => !foundTags.has(tagName)).map((tagName) => `<${tagName}>`);
@@ -15430,7 +15474,7 @@ const nextjsMissingMetadata = defineRule({
15430
15474
  severity: "warn",
15431
15475
  recommendation: "Add metadata or `generateMetadata()` so search engines and social previews get a title and description.",
15432
15476
  create: (context) => ({ Program(programNode) {
15433
- const filename = normalizeFilename$1(context.filename ?? "");
15477
+ const filename = normalizeFilename(context.filename ?? "");
15434
15478
  if (!PAGE_FILE_PATTERN.test(filename)) return;
15435
15479
  if (INTERNAL_PAGE_PATH_PATTERN.test(filename)) return;
15436
15480
  if (programNode.body?.some((statement) => {
@@ -15563,8 +15607,8 @@ const nextjsNoClientFetchForServerData = defineRule({
15563
15607
  if (!fileHasUseClient || !isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
15564
15608
  const callback = getEffectCallback(node);
15565
15609
  if (!callback || !containsFetchCall(callback, { stopAtFunctionBoundary: true })) return;
15566
- const filename = normalizeFilename$1(context.filename ?? "");
15567
- if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) || PAGES_DIRECTORY_PATTERN.test(filename)) context.report({
15610
+ const filename = normalizeFilename(context.filename ?? "");
15611
+ if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) || isInProjectDirectory(context, "pages")) context.report({
15568
15612
  node,
15569
15613
  message: "useEffect + fetch in a page/layout makes your users wait through an extra round trip & loading spinner."
15570
15614
  });
@@ -15663,8 +15707,8 @@ const nextjsNoDefaultExportInRouteHandler = defineRule({
15663
15707
  let programNode = null;
15664
15708
  return {
15665
15709
  Program(node) {
15666
- const filename = normalizeFilename$1(context.filename ?? "");
15667
- isAppRouteHandler = APP_DIRECTORY_PATTERN.test(filename) && ROUTE_HANDLER_FILE_PATTERN.test(filename);
15710
+ const filename = normalizeFilename(context.filename ?? "");
15711
+ isAppRouteHandler = isInProjectDirectory(context, "app") && ROUTE_HANDLER_FILE_PATTERN.test(filename);
15668
15712
  programNode = node;
15669
15713
  },
15670
15714
  ExportDefaultDeclaration(node) {
@@ -15700,7 +15744,7 @@ const nextjsNoEdgeOgRuntime = defineRule({
15700
15744
  let isOgImageFile = false;
15701
15745
  return {
15702
15746
  Program() {
15703
- const filename = normalizeFilename$1(context.filename ?? "");
15747
+ const filename = normalizeFilename(context.filename ?? "");
15704
15748
  isOgImageFile = OG_IMAGE_FILE_PATTERN.test(filename);
15705
15749
  },
15706
15750
  ExportNamedDeclaration(node) {
@@ -15776,8 +15820,7 @@ const nextjsNoHeadImport = defineRule({
15776
15820
  recommendation: "Use the Metadata API because `next/head` is ignored in the App Router and meta tags will not render.",
15777
15821
  create: (context) => ({ ImportDeclaration(node) {
15778
15822
  if (node.source?.value !== "next/head") return;
15779
- const filename = normalizeFilename$1(context.filename ?? "");
15780
- if (!APP_DIRECTORY_PATTERN.test(filename)) return;
15823
+ if (!isInProjectDirectory(context, "app")) return;
15781
15824
  context.report({
15782
15825
  node,
15783
15826
  message: "next/head silently does nothing in the App Router, so your meta tags never render."
@@ -16235,7 +16278,7 @@ const nextjsNoSideEffectInGetHandler = defineRule({
16235
16278
  resolveBinding = buildProgramBindingLookup(node);
16236
16279
  },
16237
16280
  ExportNamedDeclaration(node) {
16238
- const filename = normalizeFilename$1(context.filename ?? "");
16281
+ const filename = normalizeFilename(context.filename ?? "");
16239
16282
  if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
16240
16283
  if (CRON_ROUTE_PATTERN.test(filename)) return;
16241
16284
  if (!isExportedGetHandler(node)) return;
@@ -17014,7 +17057,7 @@ const nextjsNoUseSearchParamsWithoutSuspense = defineRule({
17014
17057
  let suspenseLocalNames = /* @__PURE__ */ new Set();
17015
17058
  return {
17016
17059
  Program(programNode) {
17017
- const filename = normalizeFilename$1(context.filename ?? "");
17060
+ const filename = normalizeFilename(context.filename ?? "");
17018
17061
  isPageOrLayoutFile = PAGE_OR_LAYOUT_FILE_PATTERN.test(filename);
17019
17062
  if (!isPageOrLayoutFile) return;
17020
17063
  hasAncestorLayoutSuspense = hasAncestorSuspenseLayout(context.filename ?? "");
@@ -18666,40 +18709,13 @@ const classifyPackagePlatform = (filename) => {
18666
18709
  return result;
18667
18710
  };
18668
18711
  //#endregion
18669
- //#region src/plugin/utils/get-react-doctor-setting.ts
18670
- const readReactDoctorSettingsBag = (settings) => {
18671
- const reactDoctorSettings = settings?.["react-doctor"];
18672
- if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return null;
18673
- return reactDoctorSettings;
18674
- };
18675
- const readOwnPropertyValue = (bag, settingName) => Object.getOwnPropertyDescriptor(bag, settingName)?.value;
18676
- const getReactDoctorStringSetting = (settings, settingName) => {
18677
- const bag = readReactDoctorSettingsBag(settings);
18678
- if (!bag) return void 0;
18679
- const settingValue = readOwnPropertyValue(bag, settingName);
18680
- return typeof settingValue === "string" ? settingValue : void 0;
18681
- };
18682
- const getReactDoctorNumberSetting = (settings, settingName) => {
18683
- const bag = readReactDoctorSettingsBag(settings);
18684
- if (!bag) return void 0;
18685
- const settingValue = readOwnPropertyValue(bag, settingName);
18686
- return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
18687
- };
18688
- const getReactDoctorStringArraySetting = (settings, settingName) => {
18689
- const bag = readReactDoctorSettingsBag(settings);
18690
- if (!bag) return [];
18691
- const settingValue = readOwnPropertyValue(bag, settingName);
18692
- if (!Array.isArray(settingValue)) return [];
18693
- return settingValue.filter((entry) => typeof entry === "string" && entry.length > 0);
18694
- };
18695
- //#endregion
18696
18712
  //#region src/plugin/utils/is-react-native-file.ts
18697
18713
  const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
18698
18714
  const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
18699
18715
  const classifyReactNativeFileTarget = (context) => {
18700
18716
  const rawFilename = context.filename;
18701
18717
  if (!rawFilename) return "unknown";
18702
- const filename = normalizeFilename$1(rawFilename);
18718
+ const filename = normalizeFilename(rawFilename);
18703
18719
  if (NATIVE_FILE_EXTENSION_PATTERN.test(filename)) return "react-native";
18704
18720
  if (WEB_FILE_EXTENSION_PATTERN.test(filename)) return "web";
18705
18721
  const packagePlatform = classifyPackagePlatform(filename);
@@ -18756,7 +18772,7 @@ const noBarrelImport = defineRule({
18756
18772
  if (didReportForFile) return;
18757
18773
  const source = node.source?.value;
18758
18774
  if (typeof source !== "string" || !source.startsWith(".")) return;
18759
- const filename = normalizeFilename$1(context.filename ?? "");
18775
+ const filename = normalizeFilename(context.filename ?? "");
18760
18776
  if (!filename) return;
18761
18777
  const importRequests = getRuntimeImportRequests(node);
18762
18778
  if (importRequests.length === 0) return;
@@ -26227,15 +26243,6 @@ const CLIENT_APP_DIRECTORY_FRAMEWORKS = new Set([
26227
26243
  "vite"
26228
26244
  ]);
26229
26245
  const isInsideDirectory = (pathSegments, directoryNames) => pathSegments.some((pathSegment) => directoryNames.has(pathSegment));
26230
- const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
26231
- const normalizeDirectory = (directory) => normalizeFilename(directory).replace(/\/+$/, "");
26232
- const getProjectRelativeFilename = (filename, rootDirectory) => {
26233
- const normalizedFilename = normalizeFilename(filename);
26234
- if (!rootDirectory) return normalizedFilename;
26235
- const rootDirectoryPrefix = `${normalizeDirectory(rootDirectory)}/`;
26236
- if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
26237
- return normalizedFilename.slice(rootDirectoryPrefix.length);
26238
- };
26239
26246
  const getClassifiablePathSegments = (pathSegments) => {
26240
26247
  const srcIndex = pathSegments.lastIndexOf("src");
26241
26248
  if (srcIndex === -1) return pathSegments;
@@ -26297,7 +26304,6 @@ const tokenizeIdentifierWords = (identifierName) => {
26297
26304
  const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
26298
26305
  //#endregion
26299
26306
  //#region src/plugin/constants/tanstack.ts
26300
- const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
26301
26307
  const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
26302
26308
  const TANSTACK_ROUTE_PROPERTY_ORDER = [
26303
26309
  "params",
@@ -26409,7 +26415,7 @@ const noSecretsInClientCode = defineRule({
26409
26415
  severity: "warn",
26410
26416
  recommendation: "Move secrets to server-only code. Anything in client env variables gets shipped to the browser, so it can't hold secrets.",
26411
26417
  create: (context) => {
26412
- const filename = normalizeFilename$1(context.filename ?? "");
26418
+ const filename = normalizeFilename(context.filename ?? "");
26413
26419
  const framework = getReactDoctorStringSetting(context.settings, "framework");
26414
26420
  const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
26415
26421
  let shouldUseVariableNameHeuristic = classifySecretFileExposure(filename, {
@@ -29575,7 +29581,7 @@ const onlyExportComponents = defineRule({
29575
29581
  allowConstantExport: settings.allowConstantExport
29576
29582
  };
29577
29583
  return { Program(node) {
29578
- if (!isFileNameAllowed(normalizeFilename$1(context.filename ?? ""), settings.checkJS)) return;
29584
+ if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
29579
29585
  const allNodes = collectAllNodes(node);
29580
29586
  const exports = [];
29581
29587
  let hasReactExport = false;
@@ -32090,7 +32096,7 @@ const renderingSvgPrecision = defineRule({
32090
32096
  recommendation: "Round path, points, and transform decimals to 1 or 2 digits. The extra precision adds bytes with no visible difference.",
32091
32097
  create: (context) => {
32092
32098
  const filename = context.filename;
32093
- const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename$1(filename) : void 0);
32099
+ const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename(filename) : void 0);
32094
32100
  return { JSXAttribute(node) {
32095
32101
  if (isAutoGenerated) return;
32096
32102
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -33240,7 +33246,7 @@ const rnDetoxMissingAwait = defineRule({
33240
33246
  severity: "warn",
33241
33247
  recommendation: "Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",
33242
33248
  create: (context) => {
33243
- const filename = normalizeFilename$1(context.filename ?? "");
33249
+ const filename = normalizeFilename(context.filename ?? "");
33244
33250
  if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$2;
33245
33251
  return { ExpressionStatement(node) {
33246
33252
  const expression = node.expression;
@@ -34970,7 +34976,7 @@ const rnPreferContentInsetAdjustment = defineRetiredRule({
34970
34976
  //#region src/plugin/utils/is-expo-managed-file.ts
34971
34977
  const isExpoManagedFileActive = (context) => {
34972
34978
  const rawFilename = context.filename;
34973
- const filename = rawFilename ? normalizeFilename$1(rawFilename) : void 0;
34979
+ const filename = rawFilename ? normalizeFilename(rawFilename) : void 0;
34974
34980
  if (filename) {
34975
34981
  const packagePlatform = classifyPackagePlatform(filename);
34976
34982
  if (packagePlatform === "expo") return true;
@@ -39584,8 +39590,8 @@ const serverFetchWithoutRevalidate = defineRule({
39584
39590
  let isServerSideFile = false;
39585
39591
  return {
39586
39592
  Program(node) {
39587
- const filename = normalizeFilename$1(context.filename ?? "");
39588
- if (!APP_ROUTER_FILE_PATTERN.test(filename)) {
39593
+ const filename = normalizeFilename(context.filename ?? "");
39594
+ if (!isInProjectDirectory(context, "app") || !APP_ROUTER_FILE_PATTERN.test(filename)) {
39589
39595
  isServerSideFile = false;
39590
39596
  return;
39591
39597
  }
@@ -39670,7 +39676,6 @@ const collectRequestTaintedNames = (handlerBody, handlerParamNames) => {
39670
39676
  });
39671
39677
  return taintedNames;
39672
39678
  };
39673
- const PAGES_ROUTER_API_PATH_PATTERN = /\/pages\/api\//;
39674
39679
  const inspectHandlerBody = (context, handlerBody, handlerLabel, handlerParamNames) => {
39675
39680
  const requestTaintedNames = collectRequestTaintedNames(handlerBody, handlerParamNames);
39676
39681
  walkAst(handlerBody, (child) => {
@@ -39711,8 +39716,7 @@ const serverHoistStaticIo = defineRule({
39711
39716
  inspectHandlerBody(context, declaration.body, `${handlerName} route handler`, collectHandlerParamNames(declaration.params ?? []));
39712
39717
  },
39713
39718
  ExportDefaultDeclaration(node) {
39714
- const filename = normalizeFilename$1(context.filename ?? "");
39715
- if (!PAGES_ROUTER_API_PATH_PATTERN.test(filename)) return;
39719
+ if (!isInProjectDirectory(context, "pages/api")) return;
39716
39720
  const declaration = node.declaration;
39717
39721
  if (!isFunctionLike$1(declaration)) return;
39718
39722
  if (!declaration.async) return;
@@ -40766,7 +40770,7 @@ const tanstackStartMissingHeadContent = defineRule({
40766
40770
  collectVariableAlias(node);
40767
40771
  },
40768
40772
  JSXOpeningElement(node) {
40769
- const filename = normalizeFilename$1(context.filename ?? "");
40773
+ const filename = normalizeFilename(context.filename ?? "");
40770
40774
  if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
40771
40775
  if (isNodeOfType(node.name, "JSXIdentifier")) {
40772
40776
  if (node.name.name === DOCUMENT_HEAD_ELEMENT_NAME) hasDocumentHeadElement = true;
@@ -40781,7 +40785,7 @@ const tanstackStartMissingHeadContent = defineRule({
40781
40785
  if (isInsideDocumentHeadElement(node) && isCustomJsxElementName(node.name)) hasCustomHeadChildElement = true;
40782
40786
  },
40783
40787
  "Program:exit"(programNode) {
40784
- const filename = normalizeFilename$1(context.filename ?? "");
40788
+ const filename = normalizeFilename(context.filename ?? "");
40785
40789
  if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
40786
40790
  if (hasDocumentHeadElement && !hasHeadContentElement && !hasCustomHeadChildElement) context.report({
40787
40791
  node: programNode,
@@ -40807,8 +40811,7 @@ const tanstackStartNoAnchorElement = defineRule({
40807
40811
  severity: "warn",
40808
40812
  recommendation: "Use `Link` from `@tanstack/react-router` so internal navigation keeps client state, preloading, and typed routes.",
40809
40813
  create: (context) => ({ JSXOpeningElement(node) {
40810
- const filename = normalizeFilename$1(context.filename ?? "");
40811
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40814
+ if (!isInProjectDirectory(context, "routes")) return;
40812
40815
  if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
40813
40816
  const attributes = node.attributes ?? [];
40814
40817
  const hrefAttribute = findJsxAttribute(attributes, "href");
@@ -40891,6 +40894,7 @@ const tanstackStartNoNavigateInRender = defineRule({
40891
40894
  severity: "warn",
40892
40895
  recommendation: "Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader`. navigate() during render causes hydration issues.",
40893
40896
  create: (context) => {
40897
+ const isRouteFile = isInProjectDirectory(context, "routes");
40894
40898
  let deferredCallbackDepth = 0;
40895
40899
  let eventHandlerDepth = 0;
40896
40900
  const isDeferredHookCall = (node) => isHookCall$1(node, EFFECT_HOOK_NAMES$1) || isHookCall$1(node, "useCallback") || isHookCall$1(node, "useMemo");
@@ -40954,8 +40958,7 @@ const tanstackStartNoNavigateInRender = defineRule({
40954
40958
  };
40955
40959
  return {
40956
40960
  CallExpression(node) {
40957
- const filename = normalizeFilename$1(context.filename ?? "");
40958
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40961
+ if (!isRouteFile) return;
40959
40962
  if (isDeferredHookCall(node)) deferredCallbackDepth++;
40960
40963
  if (deferredCallbackDepth > 0 || eventHandlerDepth > 0) return;
40961
40964
  if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "navigate" && (node.arguments?.length ?? 0) > 0) {
@@ -40967,48 +40970,39 @@ const tanstackStartNoNavigateInRender = defineRule({
40967
40970
  }
40968
40971
  },
40969
40972
  "CallExpression:exit"(node) {
40970
- const filename = normalizeFilename$1(context.filename ?? "");
40971
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40973
+ if (!isRouteFile) return;
40972
40974
  if (isDeferredHookCall(node)) deferredCallbackDepth = Math.max(0, deferredCallbackDepth - 1);
40973
40975
  },
40974
40976
  JSXAttribute(node) {
40975
- const filename = normalizeFilename$1(context.filename ?? "");
40976
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40977
+ if (!isRouteFile) return;
40977
40978
  if (isEventHandlerAttribute(node)) eventHandlerDepth++;
40978
40979
  },
40979
40980
  "JSXAttribute:exit"(node) {
40980
- const filename = normalizeFilename$1(context.filename ?? "");
40981
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40981
+ if (!isRouteFile) return;
40982
40982
  if (isEventHandlerAttribute(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
40983
40983
  },
40984
40984
  Property(node) {
40985
- const filename = normalizeFilename$1(context.filename ?? "");
40986
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40985
+ if (!isRouteFile) return;
40987
40986
  if (isEventHandlerProperty(node)) eventHandlerDepth++;
40988
40987
  },
40989
40988
  "Property:exit"(node) {
40990
- const filename = normalizeFilename$1(context.filename ?? "");
40991
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40989
+ if (!isRouteFile) return;
40992
40990
  if (isEventHandlerProperty(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
40993
40991
  },
40994
40992
  VariableDeclarator(node) {
40995
- const filename = normalizeFilename$1(context.filename ?? "");
40996
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40993
+ if (!isRouteFile) return;
40997
40994
  if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth++;
40998
40995
  },
40999
40996
  "VariableDeclarator:exit"(node) {
41000
- const filename = normalizeFilename$1(context.filename ?? "");
41001
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
40997
+ if (!isRouteFile) return;
41002
40998
  if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
41003
40999
  },
41004
41000
  FunctionDeclaration(node) {
41005
- const filename = normalizeFilename$1(context.filename ?? "");
41006
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
41001
+ if (!isRouteFile) return;
41007
41002
  if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth++;
41008
41003
  },
41009
41004
  "FunctionDeclaration:exit"(node) {
41010
- const filename = normalizeFilename$1(context.filename ?? "");
41011
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
41005
+ if (!isRouteFile) return;
41012
41006
  if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
41013
41007
  }
41014
41008
  };
@@ -41093,8 +41087,7 @@ const tanstackStartNoUseEffectFetch = defineRule({
41093
41087
  severity: "warn",
41094
41088
  recommendation: "Fetch data in the route `loader` instead. The router loads it before rendering and avoids waterfalls.",
41095
41089
  create: (context) => ({ CallExpression(node) {
41096
- const filename = normalizeFilename$1(context.filename ?? "");
41097
- if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
41090
+ if (!isInProjectDirectory(context, "routes")) return;
41098
41091
  if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
41099
41092
  const callback = node.arguments?.[0];
41100
41093
  if (!callback) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.6.0-dev.6885698",
3
+ "version": "0.6.1-dev.4e91677",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",