oxlint-plugin-react-doctor 0.5.0 → 0.5.1-dev.b819e5f

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 +100 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -432,6 +432,7 @@ const SETTER_PATTERN = /^set[A-Z]/;
432
432
  const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
433
433
  const UPPERCASE_PATTERN = /^[A-Z]/;
434
434
  const REACT_HANDLER_PROP_PATTERN = /^on[A-Z]/;
435
+ const HANDLER_FUNCTION_NAME_PATTERN = /^(?:on|handle)[A-Z]/;
435
436
  const EFFECT_HOOK_NAMES$1 = new Set(["useEffect", "useLayoutEffect"]);
436
437
  const HOOKS_WITH_DEPS = new Set([
437
438
  "useEffect",
@@ -24499,6 +24500,54 @@ const UTILITY_FILE_BASENAMES = new Set([
24499
24500
  "defaults.tsx",
24500
24501
  "defaults.jsx"
24501
24502
  ]);
24503
+ const ROUTE_FACTORY_CALLEE_NAMES = new Set([
24504
+ ...TANSTACK_ROUTE_CREATION_FUNCTIONS,
24505
+ "createLazyFileRoute",
24506
+ "createLazyRoute",
24507
+ "createAPIFileRoute",
24508
+ "createServerFileRoute",
24509
+ "createServerRootRoute",
24510
+ "createServerRoute",
24511
+ "createBrowserRouter",
24512
+ "createHashRouter",
24513
+ "createMemoryRouter",
24514
+ "createStaticRouter",
24515
+ "createRouter"
24516
+ ]);
24517
+ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
24518
+ "loader",
24519
+ "clientLoader",
24520
+ "action",
24521
+ "clientAction",
24522
+ "headers",
24523
+ "meta",
24524
+ "links",
24525
+ "handle",
24526
+ "shouldRevalidate",
24527
+ "middleware",
24528
+ "unstable_middleware",
24529
+ "getServerSideProps",
24530
+ "getStaticProps",
24531
+ "getStaticPaths",
24532
+ "getInitialProps",
24533
+ "reportWebVitals",
24534
+ "metadata",
24535
+ "generateMetadata",
24536
+ "generateStaticParams",
24537
+ "generateImageMetadata",
24538
+ "generateSitemaps",
24539
+ "viewport",
24540
+ "generateViewport",
24541
+ "revalidate",
24542
+ "dynamic",
24543
+ "dynamicParams",
24544
+ "fetchCache",
24545
+ "runtime",
24546
+ "preferredRegion",
24547
+ "maxDuration",
24548
+ "experimental_ppr",
24549
+ "unstable_settings"
24550
+ ]);
24502
24551
  //#endregion
24503
24552
  //#region src/plugin/rules/react-builtins/only-export-components.ts
24504
24553
  const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh can't safely preserve component state.";
@@ -24542,6 +24591,18 @@ const isReactCreateContext = (initializer) => {
24542
24591
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "createContext") return true;
24543
24592
  return false;
24544
24593
  };
24594
+ const isRouteFactoryName = (name) => ROUTE_FACTORY_CALLEE_NAMES.has(name);
24595
+ const isRouteFactoryCall = (expression) => {
24596
+ let currentCall = expression;
24597
+ while (isNodeOfType(currentCall, "CallExpression")) {
24598
+ const callee = currentCall.callee;
24599
+ if (isNodeOfType(callee, "Identifier") && isRouteFactoryName(callee.name)) return true;
24600
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && isRouteFactoryName(callee.property.name)) return true;
24601
+ if (!isNodeOfType(callee, "CallExpression")) return false;
24602
+ currentCall = callee;
24603
+ }
24604
+ return false;
24605
+ };
24545
24606
  const isReactHocName = (name, state) => state.customHocs.has(name);
24546
24607
  const isHocCallee = (callee, state) => {
24547
24608
  if (isNodeOfType(callee, "Identifier")) return isReactHocName(callee.name, state);
@@ -24575,10 +24636,12 @@ const isReactComponentInitializer = (expression, state) => {
24575
24636
  const classifyExport = (name, reportNode, isFunction, initializer, state) => {
24576
24637
  if (initializer) {
24577
24638
  const expression = skipTsExpression(initializer);
24639
+ if (isRouteFactoryCall(expression)) return { kind: "react-component" };
24578
24640
  if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state) && expression.arguments.length > 0 && isReactComponentName(name)) return { kind: "react-component" };
24579
24641
  if (isNodeOfType(expression, "ConditionalExpression") && isReactComponentName(name) && isReactComponentInitializer(expression.consequent, state) && isReactComponentInitializer(expression.alternate, state)) return { kind: "react-component" };
24580
24642
  }
24581
24643
  if (state.allowExportNames.has(name)) return { kind: "allowed" };
24644
+ if (ROUTE_MODULE_ALLOWED_EXPORT_NAMES.has(name)) return { kind: "allowed" };
24582
24645
  if (/^use[A-Z]/.test(name)) return { kind: "allowed" };
24583
24646
  if (state.allowConstantExport && initializer) {
24584
24647
  const expression = skipTsExpression(initializer);
@@ -24724,6 +24787,10 @@ const onlyExportComponents = defineRule({
24724
24787
  continue;
24725
24788
  }
24726
24789
  if (isNodeOfType(stripped, "CallExpression")) {
24790
+ if (isRouteFactoryCall(stripped)) {
24791
+ hasReactExport = true;
24792
+ continue;
24793
+ }
24727
24794
  const isHoc = isHocCallee(stripped.callee, state);
24728
24795
  const firstArg = stripped.arguments[0];
24729
24796
  const firstArgIsValid = Boolean(firstArg) && (() => {
@@ -34290,6 +34357,9 @@ const tanstackStartNoNavigateInRender = defineRule({
34290
34357
  let eventHandlerDepth = 0;
34291
34358
  const isDeferredHookCall = (node) => isHookCall$1(node, EFFECT_HOOK_NAMES$1) || isHookCall$1(node, "useCallback") || isHookCall$1(node, "useMemo");
34292
34359
  const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && typeof node.name.name === "string" && node.name.name.startsWith("on") && UPPERCASE_PATTERN.test(node.name.name.charAt(2));
34360
+ const isEventHandlerProperty = (node) => isNodeOfType(node, "Property") && isFunctionLike$2(node.value) && (isNodeOfType(node.key, "Identifier") && typeof node.key.name === "string" && REACT_HANDLER_PROP_PATTERN.test(node.key.name) || isNodeOfType(node.key, "Literal") && typeof node.key.value === "string" && REACT_HANDLER_PROP_PATTERN.test(node.key.value));
34361
+ const isHandlerNamedVariableDeclarator = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && typeof node.id.name === "string" && HANDLER_FUNCTION_NAME_PATTERN.test(node.id.name) && isFunctionLike$2(node.init);
34362
+ const isHandlerNamedFunctionDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && isNodeOfType(node.id, "Identifier") && typeof node.id.name === "string" && HANDLER_FUNCTION_NAME_PATTERN.test(node.id.name);
34293
34363
  return {
34294
34364
  CallExpression(node) {
34295
34365
  const filename = normalizeFilename$1(context.filename ?? "");
@@ -34315,6 +34385,36 @@ const tanstackStartNoNavigateInRender = defineRule({
34315
34385
  const filename = normalizeFilename$1(context.filename ?? "");
34316
34386
  if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34317
34387
  if (isEventHandlerAttribute(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
34388
+ },
34389
+ Property(node) {
34390
+ const filename = normalizeFilename$1(context.filename ?? "");
34391
+ if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34392
+ if (isEventHandlerProperty(node)) eventHandlerDepth++;
34393
+ },
34394
+ "Property:exit"(node) {
34395
+ const filename = normalizeFilename$1(context.filename ?? "");
34396
+ if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34397
+ if (isEventHandlerProperty(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
34398
+ },
34399
+ VariableDeclarator(node) {
34400
+ const filename = normalizeFilename$1(context.filename ?? "");
34401
+ if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34402
+ if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth++;
34403
+ },
34404
+ "VariableDeclarator:exit"(node) {
34405
+ const filename = normalizeFilename$1(context.filename ?? "");
34406
+ if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34407
+ if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
34408
+ },
34409
+ FunctionDeclaration(node) {
34410
+ const filename = normalizeFilename$1(context.filename ?? "");
34411
+ if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34412
+ if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth++;
34413
+ },
34414
+ "FunctionDeclaration:exit"(node) {
34415
+ const filename = normalizeFilename$1(context.filename ?? "");
34416
+ if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
34417
+ if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
34318
34418
  }
34319
34419
  };
34320
34420
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.5.0",
3
+ "version": "0.5.1-dev.b819e5f",
4
4
  "description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",