@sarj/eslint-plugin 1.0.1 → 2.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/rules/enforce-file-structure.ts","../src/rules/no-client-side-data-fetching.ts","../src/rules/no-enum.ts","../src/rules/no-raw-env.ts","../src/rules/no-unnecessary-use-client.ts","../src/rules/prefer-schema-for-api-payload.ts","../src/rules/prefer-server-actions.ts","../src/rules/prefer-shadcn.ts","../src/rules/require-assert-never.ts","../src/rules/require-zod-form-validation.ts","../src/rules/zod-naming-convention.ts","../src/index.ts"],"sourcesContent":["import { ESLintUtils, type TSESTree, AST_NODE_TYPES } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"incorrectOrder\" | \"useServerDirective\";\ntype Options = readonly [];\n\n/** Section ordinals — lower numbers must appear before higher numbers. */\nconst SECTION = {\n imports: 0,\n types: 1,\n constants: 2,\n functions: 3,\n exports: 4,\n} as const;\n\nconst SECTION_NAMES = [\n \"imports\",\n \"types\",\n \"constants\",\n \"functions\",\n \"exports\",\n] as const;\n\ntype SectionOrdinal = (typeof SECTION)[keyof typeof SECTION];\n\nconst sectionName = (ordinal: SectionOrdinal): string => {\n const name = SECTION_NAMES[ordinal];\n // SECTION_NAMES is indexed by SectionOrdinal which is a numeric literal union of 0..4 — this is always defined.\n // But noUncheckedIndexedAccess widens this to `string | undefined`, so we fall back defensively.\n return name ?? \"unknown\";\n};\n\nconst isConstantNamed = (declarator: TSESTree.VariableDeclarator): boolean => {\n if (declarator.id.type !== AST_NODE_TYPES.Identifier) return false;\n const name = declarator.id.name;\n // Treat as constant if the binding is ALL_CAPS (allowing underscores/digits).\n return name.length > 0 && name === name.toUpperCase();\n};\n\nconst getStatementSection = (\n statement: TSESTree.ProgramStatement,\n): SectionOrdinal => {\n switch (statement.type) {\n case AST_NODE_TYPES.ImportDeclaration:\n return SECTION.imports;\n case AST_NODE_TYPES.TSTypeAliasDeclaration:\n case AST_NODE_TYPES.TSInterfaceDeclaration:\n case AST_NODE_TYPES.TSEnumDeclaration:\n return SECTION.types;\n case AST_NODE_TYPES.VariableDeclaration: {\n if (statement.kind === \"const\") {\n const firstDeclarator = statement.declarations[0];\n if (firstDeclarator !== undefined && isConstantNamed(firstDeclarator)) {\n return SECTION.constants;\n }\n }\n return SECTION.functions;\n }\n case AST_NODE_TYPES.FunctionDeclaration:\n return SECTION.functions;\n case AST_NODE_TYPES.ExportNamedDeclaration:\n case AST_NODE_TYPES.ExportDefaultDeclaration:\n case AST_NODE_TYPES.ExportAllDeclaration:\n return SECTION.exports;\n default:\n return SECTION.functions;\n }\n};\n\nconst isUseServerDirective = (\n statement: TSESTree.ProgramStatement | undefined,\n): boolean => {\n if (statement === undefined) return false;\n if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;\n const expr = statement.expression;\n if (expr.type !== AST_NODE_TYPES.Literal) return false;\n return expr.value === \"use server\";\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"enforce-file-structure\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Enforce a canonical top-of-file ordering: imports -> types -> constants -> functions -> exports. Server-action files (under `/actions/` or with `action` in the path) must also begin with a `use server` directive.\",\n },\n schema: [],\n messages: {\n incorrectOrder:\n \"File structure violation: {{current}} should come before {{expected}}\",\n useServerDirective:\n \"Server action files must start with 'use server' directive\",\n },\n },\n defaultOptions: [],\n create(context) {\n const filename = context.filename;\n const isServerAction =\n filename.includes(\"/actions/\") || filename.includes(\"action\");\n\n return {\n Program(node: TSESTree.Program): void {\n const body = node.body;\n\n if (isServerAction) {\n const firstNode = body[0];\n if (!isUseServerDirective(firstNode)) {\n context.report({\n node,\n messageId: \"useServerDirective\",\n });\n }\n }\n\n let currentSection: SectionOrdinal = SECTION.imports;\n\n for (const statement of body) {\n // Skip top-of-file string directives ('use server', 'use client',\n // 'use strict', ...) so they don't get classified as a section.\n if (isUseServerDirective(statement)) continue;\n if (\n statement.type === AST_NODE_TYPES.ExpressionStatement &&\n statement.expression.type === AST_NODE_TYPES.Literal &&\n typeof statement.expression.value === \"string\" &&\n statement.expression.value.startsWith(\"use \")\n ) {\n continue;\n }\n\n const statementSection = getStatementSection(statement);\n\n if (statementSection < currentSection) {\n context.report({\n node: statement,\n messageId: \"incorrectOrder\",\n data: {\n current: sectionName(statementSection),\n expected: sectionName(currentSection),\n },\n });\n } else if (statementSection > currentSection) {\n currentSection = statementSection;\n }\n }\n },\n };\n },\n});\n","/**\n * @fileoverview Disallow data fetching inside `useEffect` / `useLayoutEffect`.\n *\n * Anti-pattern:\n *\n * useEffect(() => { fetch('/data').then(setData); }, []);\n *\n * Causes a client-side waterfall (render → effect → fetch → re-render),\n * forfeits server-side caching, and produces layout shift. In Next.js App\n * Router, prefer:\n * - a React Server Component that fetches at render time, or\n * - a Server Action invoked from a form / onClick handler, or\n * - client-side caching libraries like SWR or React Query.\n *\n * Detection covers:\n * - `fetch(...)` (default + when explicit `method: \"GET\"`)\n * - `axios.<method>(...)` for actual HTTP verbs only:\n * get / post / put / delete / patch / request / head / options\n * (i.e. NOT `axios.create` / `axios.defaults`)\n * - `ky.<method>(...)` / `superagent.<method>(...)` (same verbs)\n * - bare `axios(...)` / `ky(...)` calls (when treated as a GET)\n *\n * Analytics / telemetry endpoints (`*track*`, `*log*`, `*ping*`, ...) are\n * intentionally exempt because they aren't render-blocking data fetches.\n *\n * References:\n * - https://nextjs.org/docs/app/building-your-application/data-fetching\n * - https://react.dev/reference/react/useEffect#fetching-data-with-effects\n */\n\nimport {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"noClientFetch\";\ntype Options = readonly [];\n\nconst FETCH_LIBS: ReadonlySet<string> = new Set([\"axios\", \"ky\", \"superagent\"]);\n\n// Real HTTP verbs only. Explicitly excludes `create`, `defaults`,\n// `interceptors`, `isAxiosError`, etc.\nconst HTTP_METHOD_NAMES: ReadonlySet<string> = new Set([\n \"get\",\n \"post\",\n \"put\",\n \"delete\",\n \"patch\",\n \"request\",\n \"head\",\n \"options\",\n]);\n\nconst ANALYTICS_KEYWORDS: readonly string[] = [\n \"analytics\",\n \"telemetry\",\n \"track\",\n \"log\",\n \"ping\",\n \"beacon\",\n \"metrics\",\n \"event\",\n];\n\nfunction isEffectHookCall(node: TSESTree.CallExpression): boolean {\n const callee = node.callee;\n\n // useEffect(...) / useLayoutEffect(...)\n if (callee.type === AST_NODE_TYPES.Identifier) {\n return callee.name === \"useEffect\" || callee.name === \"useLayoutEffect\";\n }\n\n // React.useEffect(...) / React.useLayoutEffect(...)\n if (\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === \"React\" &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ) {\n return (\n callee.property.name === \"useEffect\" ||\n callee.property.name === \"useLayoutEffect\"\n );\n }\n\n return false;\n}\n\n/**\n * Reads the `method` property of an options object passed to `fetch` / `axios`.\n * Returns the uppercased method name, or `null` if not statically determinable.\n */\nfunction readMethodProperty(\n optionsArg: TSESTree.Node | undefined,\n): string | null {\n if (!optionsArg || optionsArg.type !== AST_NODE_TYPES.ObjectExpression) {\n return null;\n }\n for (const prop of optionsArg.properties) {\n if (prop.type !== AST_NODE_TYPES.Property) continue;\n if (prop.computed) continue;\n const key = prop.key;\n const matchesMethodKey =\n (key.type === AST_NODE_TYPES.Identifier && key.name === \"method\") ||\n (key.type === AST_NODE_TYPES.Literal && key.value === \"method\");\n if (!matchesMethodKey) continue;\n if (\n prop.value.type === AST_NODE_TYPES.Literal &&\n typeof prop.value.value === \"string\"\n ) {\n return prop.value.value.toUpperCase();\n }\n return null;\n }\n return null;\n}\n\nfunction isFetchCall(node: TSESTree.CallExpression): boolean {\n const callee = node.callee;\n\n // fetch(url, options?)\n if (\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === \"fetch\"\n ) {\n const method = readMethodProperty(node.arguments[1]);\n if (method !== null && method !== \"GET\") {\n return false;\n }\n return true;\n }\n\n // axios.get(...), ky.post(...), superagent.delete(...), ...\n if (\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n FETCH_LIBS.has(callee.object.name) &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ) {\n // Only flag actual HTTP method calls — NOT `axios.create`, `axios.defaults`,\n // `axios.interceptors`, `axios.isAxiosError`, etc.\n return HTTP_METHOD_NAMES.has(callee.property.name);\n }\n\n // axios(config) / ky(config) — treat as request unless method is explicitly non-GET.\n if (\n callee.type === AST_NODE_TYPES.Identifier &&\n (callee.name === \"axios\" || callee.name === \"ky\")\n ) {\n const firstArg = node.arguments[0];\n const secondArg = node.arguments[1];\n let configArg: TSESTree.Node | undefined;\n if (firstArg?.type === AST_NODE_TYPES.ObjectExpression) {\n configArg = firstArg;\n } else if (secondArg?.type === AST_NODE_TYPES.ObjectExpression) {\n configArg = secondArg;\n }\n const method = readMethodProperty(configArg);\n if (method !== null && method !== \"GET\") {\n return false;\n }\n return true;\n }\n\n return false;\n}\n\nfunction extractUrlString(node: TSESTree.CallExpression): string {\n const firstArg = node.arguments[0];\n if (!firstArg) return \"\";\n\n if (\n firstArg.type === AST_NODE_TYPES.Literal &&\n typeof firstArg.value === \"string\"\n ) {\n return firstArg.value;\n }\n if (firstArg.type === AST_NODE_TYPES.TemplateLiteral) {\n return firstArg.quasis.map((q) => q.value.cooked).join(\"\");\n }\n if (firstArg.type === AST_NODE_TYPES.Identifier) {\n return firstArg.name;\n }\n return \"\";\n}\n\nfunction isAnalyticsCall(node: TSESTree.CallExpression): boolean {\n const url = extractUrlString(node).toLowerCase();\n if (url === \"\") return false;\n return ANALYTICS_KEYWORDS.some((keyword) => url.includes(keyword));\n}\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"no-client-side-data-fetching\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow data fetching inside `useEffect` / `useLayoutEffect`; prefer React Server Components, Server Actions, or a client-side cache (SWR / React Query).\",\n },\n schema: [],\n messages: {\n noClientFetch:\n \"Avoid direct data fetching inside useEffect / useLayoutEffect. This causes waterfalls and layout shifts. Prefer React Server Components, Server Actions, or client-side caching libraries like SWR or React Query.\",\n },\n },\n defaultOptions: [],\n create(context) {\n let effectDepth = 0;\n return {\n CallExpression(node: TSESTree.CallExpression): void {\n if (isEffectHookCall(node)) {\n effectDepth += 1;\n return;\n }\n if (effectDepth === 0) return;\n if (!isFetchCall(node)) return;\n if (isAnalyticsCall(node)) return;\n context.report({ node, messageId: \"noClientFetch\" });\n },\n \"CallExpression:exit\"(node: TSESTree.CallExpression): void {\n if (isEffectHookCall(node)) {\n effectDepth -= 1;\n }\n },\n };\n },\n});\n","/**\n * @fileoverview Disallow TypeScript `enum` declarations. Use string-literal\n * union types (e.g. `type Status = \"active\" | \"inactive\"`) or `as const`\n * objects instead — enums generate runtime code, have unintuitive numeric\n * defaults, and don't tree-shake cleanly.\n *\n * Generated files can opt out either by living under a path matched by\n * `ignoreFiles` (default: `**\\/generated/**`, `**\\/*.gen.ts`, `**\\/*.generated.ts`)\n * or by including a `@generated` marker comment near the top of the file.\n */\n\nimport { ESLintUtils, type TSESTree } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"noEnum\";\ntype Options = readonly [\n {\n ignoreFiles?: readonly string[];\n }?,\n];\n\nconst DEFAULT_IGNORE_PATTERNS: readonly RegExp[] = [\n /[\\\\/]generated[\\\\/]/,\n /\\.gen\\.tsx?$/,\n /\\.generated\\.tsx?$/,\n];\n\nfunction matchesAnyPattern(\n filename: string,\n patterns: readonly string[],\n): boolean {\n for (const pattern of patterns) {\n // Convert minimatch-ish globs to regex: ** -> .*, * -> [^/\\\\]*\n const regexSource = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*\\*/g, \"::DOUBLESTAR::\")\n .replace(/\\*/g, \"[^/\\\\\\\\]*\")\n .replace(/::DOUBLESTAR::/g, \".*\");\n if (new RegExp(`^${regexSource}$`).test(filename)) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasGeneratedMarker(sourceText: string): boolean {\n // Look only in the first 1KB to keep this cheap.\n const head = sourceText.slice(0, 1024);\n return /@generated\\b/.test(head);\n}\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"no-enum\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Disallow TypeScript `enum`; use string-literal unions or `as const` objects instead.\",\n },\n schema: [\n {\n type: \"object\",\n additionalProperties: false,\n properties: {\n ignoreFiles: {\n type: \"array\",\n items: { type: \"string\" },\n },\n },\n },\n ],\n messages: {\n noEnum:\n 'Enums are discouraged. Use a string-literal union (e.g. `type Status = \"active\" | \"inactive\"`) or an `as const` object instead.',\n },\n },\n defaultOptions: [{}],\n create(context, [optionsArg]) {\n const options = optionsArg ?? {};\n const ignoreFiles = options.ignoreFiles ?? [];\n const filename = context.filename;\n const sourceText = context.sourceCode.getText();\n\n const isIgnoredByDefault = DEFAULT_IGNORE_PATTERNS.some((re) =>\n re.test(filename),\n );\n const isIgnoredByOption =\n ignoreFiles.length > 0 && matchesAnyPattern(filename, ignoreFiles);\n const isGenerated = hasGeneratedMarker(sourceText);\n\n if (isIgnoredByDefault || isIgnoredByOption || isGenerated) {\n return {};\n }\n\n return {\n TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void {\n context.report({\n node,\n messageId: \"noEnum\",\n });\n },\n };\n },\n});\n","/**\n * @fileoverview Disallow direct `process.env` access. Force all env reads\n * through a Zod-validated env module so configuration is typed and validated\n * at startup.\n */\n\nimport { ESLintUtils, type TSESTree } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"noRawEnv\";\ntype Options = readonly [];\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"no-raw-env\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow direct `process.env` access; use a Zod-validated env module instead.\",\n },\n schema: [],\n messages: {\n noRawEnv:\n \"Do not read from `process.env` directly. Import the Zod-validated env module instead so values are typed and validated at startup.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n MemberExpression(node: TSESTree.MemberExpression): void {\n if (node.computed) {\n // Skip dynamic accesses like process[\"env\"] — extremely rare and out of scope.\n return;\n }\n if (\n node.object.type === \"Identifier\" &&\n node.object.name === \"process\" &&\n node.property.type === \"Identifier\" &&\n node.property.name === \"env\"\n ) {\n context.report({\n node,\n messageId: \"noRawEnv\",\n });\n }\n },\n };\n },\n});\n","/**\n * @fileoverview Flag `'use client'` files with no hooks or event handlers.\n *\n * If a file is marked `'use client'` but contains no hook calls\n * (`useState`/`useEffect`/etc.), no JSX event handlers (`onClick`,\n * `onChange`, etc.), no browser globals, no client-only imports, and no\n * other client-side indicators (classes, re-exports), the directive is\n * likely unnecessary and the file could be a React Server Component —\n * improving cold-start, bundle size, and SEO.\n *\n * False-positive watch: components that only use client-side context\n * (e.g. theme providers) without hooks or events still need `'use client'`.\n *\n * References:\n * - https://nextjs.org/docs/app/building-your-application/rendering/client-components\n */\n\nimport {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\nimport type { RuleContext, Scope } from \"@typescript-eslint/utils/ts-eslint\";\n\ntype MessageIds = \"unnecessaryUseClient\";\ntype Options = readonly [];\n\nconst HOOK_REGEX = /^use([A-Z]|$)/;\nconst EVENT_PROP_REGEX = /^on[A-Z]/;\nconst ERROR_FILE_REGEX = /\\b(?:global-)?error\\.[jt]sx?$/;\n\nconst BROWSER_GLOBALS: ReadonlySet<string> = new Set([\n \"window\",\n \"document\",\n \"navigator\",\n \"localStorage\",\n \"sessionStorage\",\n \"location\",\n \"history\",\n \"screen\",\n \"requestAnimationFrame\",\n \"cancelAnimationFrame\",\n \"CustomEvent\",\n \"Event\",\n \"MouseEvent\",\n \"KeyboardEvent\",\n \"TouchEvent\",\n]);\n\nconst CLIENT_ONLY_PACKAGES_REGEX =\n /^(?:@radix-ui\\/|framer-motion|react-dom|react-day-picker|@floating-ui\\/|react-select|react-toastify|react-hook-form|recharts|react-dropzone|react-slick|react-swipeable|react-resizable|react-draggable|react-beautiful-dnd|@hello-pangea\\/dnd|react-virtualized|react-window|@tanstack\\/react-table|@tanstack\\/react-query|react-redux|recoil|jotai|zustand|@tippyjs\\/react|react-color|react-datepicker|next-themes|react-helmet|react-helmet-async|styled-components|@emotion\\/)/;\n\ntype Ctx = Readonly<RuleContext<MessageIds, Options>>;\n\nconst isUseClientDirective = (\n node: TSESTree.Statement,\n): node is TSESTree.ExpressionStatement => {\n return (\n node.type === AST_NODE_TYPES.ExpressionStatement &&\n node.expression.type === AST_NODE_TYPES.Literal &&\n node.expression.value === \"use client\"\n );\n};\n\nconst isGlobalReference = (\n node: TSESTree.Identifier,\n context: Ctx,\n): boolean => {\n if (!BROWSER_GLOBALS.has(node.name)) return false;\n\n const parent = node.parent;\n if (parent !== undefined) {\n // `obj.window` — `window` is a property name, not a global reference.\n if (\n parent.type === AST_NODE_TYPES.MemberExpression &&\n parent.property === node &&\n !parent.computed\n ) {\n return false;\n }\n // `{ window: ... }` — property key, not a global reference.\n if (\n parent.type === AST_NODE_TYPES.Property &&\n parent.key === node &&\n !parent.computed\n ) {\n return false;\n }\n // Type annotations / type-only positions are not runtime references.\n if (parent.type.startsWith(\"TS\")) {\n return false;\n }\n }\n\n // If there's a local binding for this name anywhere up the chain, it's not\n // a reference to the browser global.\n let scope: Scope.Scope | null = context.sourceCode.getScope(node);\n while (scope !== null) {\n const variable = scope.set.get(node.name);\n if (variable !== undefined && variable.defs.length > 0) {\n return false;\n }\n scope = scope.upper;\n }\n\n return true;\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"no-unnecessary-use-client\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Flag `'use client'` files with no hooks or event handlers — they could be RSC.\",\n },\n schema: [],\n messages: {\n unnecessaryUseClient:\n \"'use client' directive but no hooks (use*), JSX event handlers (on*), browser globals, or client-only imports found. Consider removing the directive and serving as a React Server Component.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const filename = context.filename;\n if (ERROR_FILE_REGEX.test(filename)) {\n return {};\n }\n\n let directiveNode: TSESTree.ExpressionStatement | null = null;\n let hasClientIndicator = false;\n\n const markIfHookOrContext = (\n callee: TSESTree.CallExpression[\"callee\"],\n ): void => {\n if (callee.type === AST_NODE_TYPES.Identifier) {\n if (HOOK_REGEX.test(callee.name) || callee.name === \"createContext\") {\n hasClientIndicator = true;\n }\n return;\n }\n if (\n callee.type === AST_NODE_TYPES.MemberExpression &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ) {\n const name = callee.property.name;\n if (HOOK_REGEX.test(name) || name === \"createContext\") {\n hasClientIndicator = true;\n }\n }\n };\n\n return {\n Program(node): void {\n for (const stmt of node.body) {\n // Directives must be the first statements; once we see a non-\n // ExpressionStatement, stop scanning.\n if (stmt.type !== AST_NODE_TYPES.ExpressionStatement) break;\n if (isUseClientDirective(stmt)) {\n directiveNode = stmt;\n break;\n }\n }\n },\n CallExpression(node): void {\n markIfHookOrContext(node.callee);\n },\n JSXAttribute(node): void {\n if (\n node.name.type === AST_NODE_TYPES.JSXIdentifier &&\n EVENT_PROP_REGEX.test(node.name.name)\n ) {\n hasClientIndicator = true;\n }\n },\n ImportDeclaration(node): void {\n if (\n typeof node.source.value === \"string\" &&\n CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)\n ) {\n hasClientIndicator = true;\n }\n },\n ExportNamedDeclaration(node): void {\n if (node.source !== null) {\n hasClientIndicator = true;\n }\n },\n ExportAllDeclaration(node): void {\n if (node.source !== null) {\n hasClientIndicator = true;\n }\n },\n ClassDeclaration(): void {\n hasClientIndicator = true;\n },\n ClassExpression(): void {\n hasClientIndicator = true;\n },\n Identifier(node): void {\n if (isGlobalReference(node, context)) {\n hasClientIndicator = true;\n }\n },\n \"Program:exit\"(): void {\n if (directiveNode !== null && !hasClientIndicator) {\n context.report({\n node: directiveNode,\n messageId: \"unnecessaryUseClient\",\n });\n }\n },\n };\n },\n});\n","/**\n * @fileoverview Don't access `response.json()` fields without a Zod parse first.\n *\n * Pattern flagged:\n * const data = await response.json();\n * doSomething(data.foo); // <-- unvalidated property access\n *\n * Encouraged:\n * const data = MySchema.parse(await response.json());\n * doSomething(data.foo); // typed + validated\n *\n * Heuristic:\n * - Track variables initialized to `await someCall.json()` using ESLint's scope manager.\n * - Untrack if reassigned to anything other than another raw `json()` call.\n * - Flag MemberExpression reads and destructuring off tracked variables.\n * - `.parse()` / `.safeParse()` chained directly on the json call are legit\n * and never produce a tracked binding in the first place.\n *\n * References:\n * - https://zod.dev/?id=parse\n * - https://www.totaltypescript.com/parse-don-t-validate\n */\n\nimport {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\nimport type { RuleContext, Scope } from \"@typescript-eslint/utils/ts-eslint\";\n\ntype MessageIds = \"unparsedJsonAccess\";\ntype Options = readonly [];\n\ntype Ctx = Readonly<RuleContext<MessageIds, Options>>;\n\n/**\n * Peel TypeScript wrapper nodes that don't affect the underlying value\n * (`as Foo`, `<Foo>x`, `x!`, `x satisfies Foo`, parentheses, optional chain\n * wrappers). Returns the inner expression we actually care about.\n */\nconst unwrap = (\n node: TSESTree.Node | null | undefined,\n): TSESTree.Node | null => {\n let current: TSESTree.Node | null | undefined = node;\n while (current !== null && current !== undefined) {\n if (\n current.type === AST_NODE_TYPES.TSAsExpression ||\n current.type === AST_NODE_TYPES.TSTypeAssertion ||\n current.type === AST_NODE_TYPES.TSNonNullExpression ||\n current.type === AST_NODE_TYPES.TSSatisfiesExpression\n ) {\n current = current.expression;\n } else if (current.type === AST_NODE_TYPES.ChainExpression) {\n current = current.expression;\n } else {\n break;\n }\n }\n return current ?? null;\n};\n\n/**\n * Returns true if the expression is (optionally awaited) `<x>.json()`.\n */\nconst isJsonCall = (\n node: TSESTree.Node | null | undefined,\n): boolean => {\n let current = unwrap(node);\n if (current === null) return false;\n if (current.type === AST_NODE_TYPES.AwaitExpression) {\n current = unwrap(current.argument);\n }\n if (current === null || current.type !== AST_NODE_TYPES.CallExpression) {\n return false;\n }\n const callee = unwrap(current.callee);\n if (callee === null || callee.type !== AST_NODE_TYPES.MemberExpression) {\n return false;\n }\n const property = unwrap(callee.property);\n return (\n property !== null &&\n property.type === AST_NODE_TYPES.Identifier &&\n property.name === \"json\"\n );\n};\n\nconst findVariable = (\n scope: Scope.Scope | null,\n name: string,\n): Scope.Variable | null => {\n let current: Scope.Scope | null = scope;\n while (current !== null) {\n const variable = current.set.get(name);\n if (variable !== undefined) return variable;\n current = current.upper;\n }\n return null;\n};\n\nconst isUnvalidatedVariableRef = (\n node: TSESTree.Node | null | undefined,\n scope: Scope.Scope,\n tracked: ReadonlySet<Scope.Variable>,\n): boolean => {\n const unwrapped = unwrap(node);\n if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES.Identifier) {\n return false;\n }\n const variable = findVariable(scope, unwrapped.name);\n return variable !== null && tracked.has(variable);\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"prefer-schema-for-api-payload\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Require Zod (or similar) schema validation on `response.json()` before property access.\",\n },\n schema: [],\n messages: {\n unparsedJsonAccess:\n \"Property access on the result of `response.json()` without a schema parse. Pipe through `XSchema.parse(...)` (Zod) before reading fields.\",\n },\n },\n defaultOptions: [],\n create(context: Ctx) {\n const unvalidatedVariables = new Set<Scope.Variable>();\n\n const trackInitializer = (\n declarator: TSESTree.VariableDeclarator,\n ): void => {\n if (!isJsonCall(declarator.init)) return;\n const declaredVars = context.sourceCode.getDeclaredVariables(declarator);\n const variable = declaredVars[0];\n if (variable !== undefined) {\n unvalidatedVariables.add(variable);\n }\n };\n\n return {\n VariableDeclarator(node): void {\n const scope = context.sourceCode.getScope(node);\n\n if (node.id.type === AST_NODE_TYPES.Identifier) {\n trackInitializer(node);\n return;\n }\n\n if (\n node.id.type === AST_NODE_TYPES.ObjectPattern ||\n node.id.type === AST_NODE_TYPES.ArrayPattern\n ) {\n if (isJsonCall(node.init)) {\n context.report({ node: node.id, messageId: \"unparsedJsonAccess\" });\n return;\n }\n if (\n isUnvalidatedVariableRef(node.init, scope, unvalidatedVariables)\n ) {\n context.report({ node: node.id, messageId: \"unparsedJsonAccess\" });\n }\n }\n },\n AssignmentExpression(node): void {\n const scope = context.sourceCode.getScope(node);\n\n if (node.left.type === AST_NODE_TYPES.Identifier) {\n const variable = findVariable(scope, node.left.name);\n if (variable === null) return;\n if (isJsonCall(node.right)) {\n unvalidatedVariables.add(variable);\n } else {\n // Reassigned to a parse call or something else: drop tracking.\n unvalidatedVariables.delete(variable);\n }\n return;\n }\n\n if (\n node.left.type === AST_NODE_TYPES.ObjectPattern ||\n node.left.type === AST_NODE_TYPES.ArrayPattern\n ) {\n if (isJsonCall(node.right)) {\n context.report({\n node: node.left,\n messageId: \"unparsedJsonAccess\",\n });\n return;\n }\n if (\n isUnvalidatedVariableRef(node.right, scope, unvalidatedVariables)\n ) {\n context.report({\n node: node.left,\n messageId: \"unparsedJsonAccess\",\n });\n }\n }\n },\n MemberExpression(node): void {\n const scope = context.sourceCode.getScope(node);\n const obj = unwrap(node.object);\n\n if (isJsonCall(obj)) {\n // Direct `.foo` access on `(await r.json()).foo` is always bad,\n // unless the parent call is a `.parse()`/`.safeParse()` — in which\n // case it's a validation, not an unvalidated read.\n const parent = node.parent;\n if (\n parent.type === AST_NODE_TYPES.CallExpression &&\n parent.callee === node &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n (node.property.name === \"parse\" ||\n node.property.name === \"safeParse\")\n ) {\n return;\n }\n context.report({ node, messageId: \"unparsedJsonAccess\" });\n return;\n }\n\n if (\n obj !== null &&\n obj.type === AST_NODE_TYPES.Identifier &&\n isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)\n ) {\n context.report({ node, messageId: \"unparsedJsonAccess\" });\n const variable = findVariable(scope, obj.name);\n if (variable !== null) {\n unvalidatedVariables.delete(variable);\n }\n }\n },\n };\n },\n});\n","/**\n * @fileoverview Prefer Next.js Server Actions over /api/* mutations.\n *\n * Flags mutations against internal `/api/*` URLs (POST/PUT/DELETE/PATCH) via\n * `fetch`, axios-style helpers, or direct axios/request calls. GET requests\n * and external URLs are ignored. Tests, scripts, and Next.js route handlers\n * are skipped because Server Actions don't apply there.\n *\n * References:\n * - https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations\n */\n\nimport { ESLintUtils, type TSESTree } from \"@typescript-eslint/utils\";\nimport type { RuleContext, Scope } from \"@typescript-eslint/utils/ts-eslint\";\n\ntype MessageIds = \"preferServerAction\";\ntype Options = readonly [];\n\nconst MUTATION_METHODS = new Set([\"POST\", \"PUT\", \"DELETE\", \"PATCH\"]);\nconst AXIOS_MUTATION_METHODS = new Set([\"post\", \"put\", \"delete\", \"patch\"]);\n\nconst SKIP_FILE_REGEX =\n /(?:\\.test\\.[jt]sx?$|\\.spec\\.[jt]sx?$|\\/tests?\\/|\\/__tests__\\/|\\/scripts?\\/|\\/app\\/api\\/.*\\/route\\.[jt]sx?$|\\/pages\\/api\\/)/;\n\ntype Ctx = Readonly<RuleContext<MessageIds, Options>>;\n\nfunction getScope(\n context: Ctx,\n node: TSESTree.Node,\n): Scope.Scope {\n return context.sourceCode.getScope(node);\n}\n\n/**\n * Walk up the scope chain looking for a variable declared with a single\n * initializer; if found, return the initializer expression so we can analyze\n * it. Falls back to the original node when resolution is not possible.\n */\nfunction resolveNode(\n node: TSESTree.Node | null | undefined,\n context: Ctx,\n): TSESTree.Node | null {\n if (!node) return null;\n if (node.type !== \"Identifier\") return node;\n\n let scope: Scope.Scope | null = getScope(context, node);\n while (scope) {\n const variable = scope.set.get(node.name);\n if (variable && variable.defs.length === 1) {\n const def = variable.defs[0];\n if (def && def.type === \"Variable\") {\n const declarator = def.node;\n if (\n declarator.type === \"VariableDeclarator\" &&\n declarator.init\n ) {\n return declarator.init;\n }\n }\n }\n scope = scope.upper;\n }\n return node;\n}\n\nfunction isApiUrl(\n node: TSESTree.Node | null | undefined,\n context: Ctx,\n): boolean {\n const resolved = resolveNode(node, context);\n if (!resolved) return false;\n\n if (resolved.type === \"Literal\" && typeof resolved.value === \"string\") {\n return resolved.value.startsWith(\"/api/\");\n }\n if (resolved.type === \"TemplateLiteral\") {\n const firstQuasi = resolved.quasis[0];\n const cooked = firstQuasi?.value.cooked;\n return typeof cooked === \"string\" && cooked.startsWith(\"/api/\");\n }\n if (resolved.type === \"BinaryExpression\" && resolved.operator === \"+\") {\n return isApiUrl(resolved.left, context);\n }\n return false;\n}\n\nfunction isMutationMethod(\n node: TSESTree.Node | null | undefined,\n context: Ctx,\n): boolean {\n const resolved = resolveNode(node, context);\n if (!resolved) return false;\n\n if (resolved.type === \"Literal\" && typeof resolved.value === \"string\") {\n return MUTATION_METHODS.has(resolved.value.toUpperCase());\n }\n\n if (\n resolved.type === \"TemplateLiteral\" &&\n resolved.expressions.length === 0\n ) {\n const val = resolved.quasis.map((q) => q.value.cooked).join(\"\");\n return MUTATION_METHODS.has(val.toUpperCase());\n }\n\n if (resolved.type === \"ConditionalExpression\") {\n return (\n isMutationMethod(resolved.consequent, context) ||\n isMutationMethod(resolved.alternate, context)\n );\n }\n\n if (resolved.type === \"LogicalExpression\" && resolved.operator === \"||\") {\n return (\n isMutationMethod(resolved.left, context) ||\n isMutationMethod(resolved.right, context)\n );\n }\n\n return false;\n}\n\nfunction getPropertyNode(\n objNode: TSESTree.Node | null | undefined,\n propName: string,\n): TSESTree.Node | null {\n if (!objNode || objNode.type !== \"ObjectExpression\") return null;\n for (const prop of objNode.properties) {\n if (prop.type !== \"Property\") continue;\n let keyName: string | null = null;\n if (prop.key.type === \"Identifier\" && !prop.computed) {\n keyName = prop.key.name;\n } else if (\n prop.key.type === \"Literal\" &&\n typeof prop.key.value === \"string\"\n ) {\n keyName = prop.key.value;\n }\n if (keyName === propName) {\n // Skip destructuring patterns — they're not valid as config values.\n if (\n prop.value.type === \"AssignmentPattern\" ||\n prop.value.type === \"ArrayPattern\" ||\n prop.value.type === \"ObjectPattern\"\n ) {\n return null;\n }\n return prop.value;\n }\n }\n return null;\n}\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"prefer-server-actions\",\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Next.js Server Actions over /api/* mutations.\",\n },\n schema: [],\n messages: {\n preferServerAction:\n \"Mutation against /api/* — prefer a Next.js Server Action for type-safety and to avoid the JSON round-trip.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const filename = context.filename;\n if (SKIP_FILE_REGEX.test(filename)) {\n return {};\n }\n\n return {\n CallExpression(node) {\n let isMutation = false;\n\n // 1. Standard fetch('/api/orders', { method: 'POST' })\n if (\n node.callee.type === \"Identifier\" &&\n node.callee.name === \"fetch\"\n ) {\n const urlArg = node.arguments[0];\n if (urlArg && urlArg.type !== \"SpreadElement\" && isApiUrl(urlArg, context)) {\n const initArg = node.arguments[1];\n if (initArg && initArg.type !== \"SpreadElement\") {\n const resolvedInit = resolveNode(initArg, context);\n const methodNode = getPropertyNode(resolvedInit, \"method\");\n if (methodNode && isMutationMethod(methodNode, context)) {\n isMutation = true;\n }\n }\n }\n }\n // 2. Custom wrappers or Axios: api.post('/api/orders') or axios.put('/api/orders')\n else if (\n node.callee.type === \"MemberExpression\" &&\n node.callee.property.type === \"Identifier\" &&\n !node.callee.computed\n ) {\n const methodName = node.callee.property.name.toLowerCase();\n if (AXIOS_MUTATION_METHODS.has(methodName)) {\n const urlArg = node.arguments[0];\n if (urlArg && urlArg.type !== \"SpreadElement\" && isApiUrl(urlArg, context)) {\n isMutation = true;\n }\n }\n }\n // 3. Direct axios/request call: axios({ method: 'post', url: '/api/orders' })\n else if (\n node.callee.type === \"Identifier\" &&\n (node.callee.name === \"axios\" || node.callee.name === \"request\")\n ) {\n const firstArg = node.arguments[0];\n if (firstArg && firstArg.type !== \"SpreadElement\") {\n const configArg = resolveNode(firstArg, context);\n if (configArg && configArg.type === \"ObjectExpression\") {\n const urlNode = getPropertyNode(configArg, \"url\");\n const methodNode = getPropertyNode(configArg, \"method\");\n if (\n urlNode &&\n isApiUrl(urlNode, context) &&\n methodNode &&\n isMutationMethod(methodNode, context)\n ) {\n isMutation = true;\n }\n }\n }\n }\n\n if (isMutation) {\n context.report({ node, messageId: \"preferServerAction\" });\n }\n },\n };\n },\n});\n","/**\n * @fileoverview Prefer shadcn/ui form primitives over their native HTML\n * counterparts. Limited to form/dialog elements — `<button>` and `<table>`\n * have been removed from the forbid list because they produced 100% false\n * positives during bulbul validation (icon buttons, layout tables).\n */\n\nimport { ESLintUtils, type TSESTree } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"preferShadcn\";\ntype Options = readonly [];\n\nconst REPLACEMENTS: Readonly<Record<string, string>> = {\n input: \"Input\",\n select: \"Select\",\n textarea: \"Textarea\",\n dialog: \"Dialog\",\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"prefer-shadcn\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Prefer shadcn/ui form primitives over native `<input>`, `<select>`, `<textarea>`, and `<dialog>` elements.\",\n },\n schema: [],\n messages: {\n preferShadcn:\n \"Use the shadcn <{{replacement}}> component from @/components/ui/{{lowercase}} instead of native <{{element}}>.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n JSXOpeningElement(node: TSESTree.JSXOpeningElement): void {\n // Only lowercase JSXIdentifier names represent native HTML elements.\n // Member expressions (`Foo.Bar`) and namespaces (`svg:path`) are skipped.\n if (node.name.type !== \"JSXIdentifier\") {\n return;\n }\n\n const elementName = node.name.name;\n const replacement = REPLACEMENTS[elementName];\n\n if (replacement === undefined) {\n return;\n }\n\n context.report({\n node,\n messageId: \"preferShadcn\",\n data: {\n element: elementName,\n replacement,\n lowercase: elementName,\n },\n });\n },\n };\n },\n});\n","import { ESLintUtils, type TSESTree, AST_NODE_TYPES } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"missingAssertNever\";\ntype Options = readonly [];\n\nconst isAssertNeverCall = (expression: TSESTree.Expression): boolean => {\n if (expression.type !== AST_NODE_TYPES.CallExpression) return false;\n const callee = expression.callee;\n return (\n callee.type === AST_NODE_TYPES.Identifier && callee.name === \"assertNever\"\n );\n};\n\nconst statementContainsAssertNever = (\n statement: TSESTree.Statement,\n): boolean => {\n if (statement.type === AST_NODE_TYPES.ExpressionStatement) {\n return isAssertNeverCall(statement.expression);\n }\n if (statement.type === AST_NODE_TYPES.ThrowStatement) {\n return isAssertNeverCall(statement.argument);\n }\n // Recurse into block-scoped default bodies like `default: { ... }`\n if (statement.type === AST_NODE_TYPES.BlockStatement) {\n return statement.body.some(statementContainsAssertNever);\n }\n return false;\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"require-assert-never\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Require switch statements to end with `assertNever(_)` in their default case so that discriminated unions are exhaustively checked at compile time.\",\n },\n schema: [],\n messages: {\n missingAssertNever:\n \"Switch statement default case must call assertNever() for exhaustive type checking\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n SwitchStatement(node: TSESTree.SwitchStatement): void {\n const defaultCase = node.cases.find(\n (caseNode): caseNode is TSESTree.SwitchCase => caseNode.test === null,\n );\n if (!defaultCase) return;\n\n const hasAssertNever = defaultCase.consequent.some(\n statementContainsAssertNever,\n );\n if (hasAssertNever) return;\n\n context.report({\n node: defaultCase,\n messageId: \"missingAssertNever\",\n });\n },\n };\n },\n});\n","import { ESLintUtils, type TSESTree, AST_NODE_TYPES } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"missingZodValidation\";\ntype Options = readonly [];\n\nconst isFormDataGetCall = (node: TSESTree.CallExpression): boolean => {\n const callee = node.callee;\n if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;\n if (\n callee.property.type !== AST_NODE_TYPES.Identifier ||\n callee.property.name !== \"get\"\n ) {\n return false;\n }\n // Match `formData.get(...)` specifically — same convention as the original rule.\n return (\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === \"formData\"\n );\n};\n\nconst isParseCallExpression = (node: TSESTree.Node): boolean => {\n if (node.type !== AST_NODE_TYPES.CallExpression) return false;\n const callee = node.callee;\n if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;\n return (\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === \"parse\"\n );\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"require-zod-form-validation\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Require Zod validation (`Schema.parse(...)`) when reading values out of a `FormData` object.\",\n },\n schema: [],\n messages: {\n missingZodValidation:\n \"FormData parsing must use Zod schema validation (e.g., Schema.parse())\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node: TSESTree.CallExpression): void {\n if (!isFormDataGetCall(node)) return;\n\n // Walk up the parent chain to find a surrounding `.parse(...)` call.\n // `.parent` is `null` at the Program root, so we must guard for both\n // null and undefined.\n let parent: TSESTree.Node | null | undefined = node.parent;\n while (parent !== null && parent !== undefined) {\n if (isParseCallExpression(parent)) return;\n parent = parent.parent;\n }\n\n context.report({\n node,\n messageId: \"missingZodValidation\",\n });\n },\n };\n },\n});\n","import { ESLintUtils, type TSESTree, AST_NODE_TYPES } from \"@typescript-eslint/utils\";\n\ntype MessageIds = \"zPrefix\";\ntype Options = readonly [];\n\n/**\n * Walks down a (possibly chained) callee like `z.object().extend().refine()` and\n * returns `true` if the chain originates from a bare `z` identifier — i.e. the\n * outermost MemberExpression on the chain has `z` as its receiver.\n */\nconst calleeChainStartsWithZ = (node: TSESTree.Node): boolean => {\n let current: TSESTree.Node = node;\n\n while (current.type === AST_NODE_TYPES.MemberExpression) {\n const receiver: TSESTree.Node = current.object;\n if (receiver.type === AST_NODE_TYPES.Identifier && receiver.name === \"z\") {\n return true;\n }\n if (receiver.type === AST_NODE_TYPES.CallExpression) {\n current = receiver.callee;\n continue;\n }\n return false;\n }\n\n return false;\n};\n\nexport default ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`,\n)<Options, MessageIds>({\n name: \"zod-naming-convention\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Enforce Zod schemas to be named with a Z prefix (e.g. `ZUser = z.object({...})`).\",\n },\n schema: [],\n messages: {\n zPrefix: \"Zod schema names should start with Z\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n VariableDeclarator(node: TSESTree.VariableDeclarator): void {\n const init = node.init;\n if (init === null || init === undefined) return;\n if (init.type !== AST_NODE_TYPES.CallExpression) return;\n\n const callee = init.callee;\n if (callee.type !== AST_NODE_TYPES.MemberExpression) return;\n\n if (!calleeChainStartsWithZ(callee)) return;\n\n if (node.id.type !== AST_NODE_TYPES.Identifier) return;\n const variableName = node.id.name;\n if (variableName.startsWith(\"Z\")) return;\n\n context.report({\n node: node.id,\n messageId: \"zPrefix\",\n });\n },\n };\n },\n});\n","import enforceFileStructure from \"./rules/enforce-file-structure.js\";\nimport noClientSideDataFetching from \"./rules/no-client-side-data-fetching.js\";\nimport noEnum from \"./rules/no-enum.js\";\nimport noRawEnv from \"./rules/no-raw-env.js\";\nimport noUnnecessaryUseClient from \"./rules/no-unnecessary-use-client.js\";\nimport preferSchemaForApiPayload from \"./rules/prefer-schema-for-api-payload.js\";\nimport preferServerActions from \"./rules/prefer-server-actions.js\";\nimport preferShadcn from \"./rules/prefer-shadcn.js\";\nimport requireAssertNever from \"./rules/require-assert-never.js\";\nimport requireZodFormValidation from \"./rules/require-zod-form-validation.js\";\nimport zodNamingConvention from \"./rules/zod-naming-convention.js\";\n\nconst rules = {\n \"enforce-file-structure\": enforceFileStructure,\n \"no-client-side-data-fetching\": noClientSideDataFetching,\n \"no-enum\": noEnum,\n \"no-raw-env\": noRawEnv,\n \"no-unnecessary-use-client\": noUnnecessaryUseClient,\n \"prefer-schema-for-api-payload\": preferSchemaForApiPayload,\n \"prefer-server-actions\": preferServerActions,\n \"prefer-shadcn\": preferShadcn,\n \"require-assert-never\": requireAssertNever,\n \"require-zod-form-validation\": requireZodFormValidation,\n \"zod-naming-convention\": zodNamingConvention,\n};\n\nconst plugin = {\n meta: {\n name: \"@sarj/eslint-plugin\",\n version: \"2.0.0\",\n },\n rules,\n configs: {\n recommended: {\n plugins: [\"@sarj\"],\n rules: {\n \"@sarj/zod-naming-convention\": \"warn\",\n \"@sarj/require-assert-never\": \"error\",\n \"@sarj/require-zod-form-validation\": \"error\",\n \"@sarj/enforce-file-structure\": \"warn\",\n \"@sarj/no-client-side-data-fetching\": \"warn\",\n \"@sarj/prefer-server-actions\": \"warn\",\n \"@sarj/no-unnecessary-use-client\": \"warn\",\n \"@sarj/prefer-schema-for-api-payload\": \"warn\",\n },\n },\n strict: {\n plugins: [\"@sarj\"],\n rules: {\n \"@sarj/zod-naming-convention\": \"error\",\n \"@sarj/require-assert-never\": \"error\",\n \"@sarj/require-zod-form-validation\": \"error\",\n \"@sarj/enforce-file-structure\": \"error\",\n \"@sarj/no-raw-env\": \"error\",\n \"@sarj/prefer-shadcn\": \"error\",\n \"@sarj/no-enum\": \"error\",\n \"@sarj/no-client-side-data-fetching\": \"error\",\n \"@sarj/prefer-server-actions\": \"error\",\n \"@sarj/no-unnecessary-use-client\": \"error\",\n \"@sarj/prefer-schema-for-api-payload\": \"error\",\n },\n },\n },\n};\n\nexport default plugin;\nexport { rules };\n"],"mappings":"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","names":["AST_NODE_TYPES","ESLintUtils","ESLintUtils","ESLintUtils","AST_NODE_TYPES","ESLintUtils","AST_NODE_TYPES","ESLintUtils","ESLintUtils","ESLintUtils","ESLintUtils","AST_NODE_TYPES","ESLintUtils","AST_NODE_TYPES","ESLintUtils","AST_NODE_TYPES"]}
package/package.json CHANGED
@@ -1,52 +1,69 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "1.0.1",
4
- "description": "Custom ESLint rules collection for Sarj projects",
3
+ "version": "2.0.1",
4
+ "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
5
5
  "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
11
+ "types": "./dist/index.d.ts",
10
12
  "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
13
+ "require": "./dist/index.cjs"
12
14
  }
13
15
  },
14
16
  "files": [
15
- "dist"
17
+ "dist",
18
+ "README.md"
16
19
  ],
17
20
  "publishConfig": {
18
- "access": "public"
21
+ "access": "public",
22
+ "provenance": true
23
+ },
24
+ "sideEffects": false,
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/sarj-ai/standards.git",
28
+ "directory": "packages/typescript"
29
+ },
30
+ "homepage": "https://github.com/sarj-ai/standards/tree/main/packages/typescript",
31
+ "bugs": {
32
+ "url": "https://github.com/sarj-ai/standards/issues"
19
33
  },
20
34
  "scripts": {
21
- "build": "tsc -p tsconfig.build.json",
35
+ "build": "tsup",
22
36
  "test": "vitest run",
23
- "test:watch": "vitest",
24
- "lint": "eslint src",
25
- "prepublishOnly": "yarn build"
37
+ "typecheck": "tsc --noEmit"
26
38
  },
27
39
  "keywords": [
28
40
  "eslint",
29
41
  "eslintplugin",
30
42
  "eslint-plugin",
31
- "zod",
32
- "typescript",
33
- "flat-config"
43
+ "sarj",
44
+ "react",
45
+ "nextjs",
46
+ "zod"
34
47
  ],
35
- "author": "Sarj",
48
+ "author": "sarj-ai",
36
49
  "license": "MIT",
50
+ "dependencies": {
51
+ "@typescript-eslint/utils": "^8.60.0"
52
+ },
37
53
  "devDependencies": {
38
- "@types/eslint": "^9.6.0",
39
- "@types/node": "^22.0.0",
40
- "@typescript-eslint/parser": "^8.0.0",
41
- "eslint": "^9.0.0",
42
- "typescript": "^5.7.0",
43
- "vitest": "^2.0.0"
54
+ "@types/node": "latest",
55
+ "@typescript-eslint/parser": "latest",
56
+ "@typescript-eslint/rule-tester": "latest",
57
+ "@typescript-eslint/utils": "latest",
58
+ "eslint": "latest",
59
+ "tsup": "latest",
60
+ "typescript": "latest",
61
+ "vitest": "latest"
44
62
  },
45
63
  "peerDependencies": {
46
- "eslint": ">=9.0.0"
64
+ "eslint": ">=10.0.0"
47
65
  },
48
66
  "engines": {
49
- "node": ">=18.0.0"
50
- },
51
- "packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8"
52
- }
67
+ "node": ">=24.0.0"
68
+ }
69
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAUnD;;;;;GAKG;AAEH,QAAA,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAQ1C,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,MAAM,CAAC,MAMpB,CAAC;AAGF,QAAA,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAsE5C,CAAC;AAEF,OAAO,EAAE,MAAM,IAAI,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -1,4 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const enforceFileStructure: Rule.RuleModule;
3
- export default enforceFileStructure;
4
- //# sourceMappingURL=enforce-file-structure.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"enforce-file-structure.d.ts","sourceRoot":"","sources":["../../src/rules/enforce-file-structure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAgDnC,eAAO,MAAM,oBAAoB,EAAE,IAAI,CAAC,UA8DvC,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -1,90 +0,0 @@
1
- /**
2
- * @fileoverview Enforce consistent file structure
3
- * @description Enforces: imports -> types -> constants -> functions -> exports
4
- */
5
- const SECTIONS = ["imports", "types", "constants", "functions", "exports"];
6
- function getStatementSection(statement) {
7
- const type = statement.type;
8
- switch (type) {
9
- case "ImportDeclaration":
10
- return 0; // imports
11
- case "TSTypeAliasDeclaration":
12
- case "TSInterfaceDeclaration":
13
- case "TSEnumDeclaration":
14
- return 1; // types
15
- case "VariableDeclaration": {
16
- // Check if it's a constant (const with UPPER_CASE or simple values)
17
- const varDecl = statement;
18
- if (varDecl.kind === "const") {
19
- const declarator = varDecl.declarations[0];
20
- if (declarator?.id.type === "Identifier" &&
21
- declarator.id.name === declarator.id.name.toUpperCase()) {
22
- return 2; // constants
23
- }
24
- }
25
- return 3; // functions (const fn = ...)
26
- }
27
- case "FunctionDeclaration":
28
- return 3; // functions
29
- case "ExportNamedDeclaration":
30
- case "ExportDefaultDeclaration":
31
- return 4; // exports
32
- default:
33
- return 3; // default to functions
34
- }
35
- }
36
- export const enforceFileStructure = {
37
- meta: {
38
- type: "suggestion",
39
- docs: {
40
- description: "Enforce consistent file structure: imports -> types -> constants -> functions -> exports",
41
- recommended: true,
42
- },
43
- schema: [],
44
- messages: {
45
- incorrectOrder: "File structure violation: {{current}} should come after {{expected}}",
46
- useServerDirective: "Server action files must start with 'use server' directive",
47
- },
48
- },
49
- create(context) {
50
- const filename = context.filename;
51
- const isServerAction = filename.includes("/actions/") || filename.includes("action");
52
- return {
53
- Program(node) {
54
- const program = node;
55
- const body = program.body;
56
- let currentSection = 0;
57
- // Check for "use server" directive in server action files
58
- if (isServerAction) {
59
- const firstNode = body[0];
60
- if (!firstNode ||
61
- firstNode.type !== "ExpressionStatement" ||
62
- firstNode.expression.type !== "Literal" ||
63
- firstNode.expression.value !== "use server") {
64
- context.report({
65
- node,
66
- messageId: "useServerDirective",
67
- });
68
- }
69
- }
70
- for (const statement of body) {
71
- const statementSection = getStatementSection(statement);
72
- if (statementSection < currentSection) {
73
- context.report({
74
- node: statement,
75
- messageId: "incorrectOrder",
76
- data: {
77
- current: SECTIONS[statementSection],
78
- expected: SECTIONS[currentSection],
79
- },
80
- });
81
- }
82
- else {
83
- currentSection = Math.max(currentSection, statementSection);
84
- }
85
- }
86
- },
87
- };
88
- },
89
- };
90
- export default enforceFileStructure;
@@ -1,8 +0,0 @@
1
- import type { Rule } from "eslint";
2
- /**
3
- * @fileoverview Disallow TypeScript enums, prefer union types
4
- * @description Enums add runtime code and have unintuitive behavior. Use union types instead.
5
- */
6
- export declare const noEnum: Rule.RuleModule;
7
- export default noEnum;
8
- //# sourceMappingURL=no-enum.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"no-enum.d.ts","sourceRoot":"","sources":["../../src/rules/no-enum.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC;;;GAGG;AAEH,eAAO,MAAM,MAAM,EAAE,IAAI,CAAC,UAyBzB,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -1,29 +0,0 @@
1
- /**
2
- * @fileoverview Disallow TypeScript enums, prefer union types
3
- * @description Enums add runtime code and have unintuitive behavior. Use union types instead.
4
- */
5
- export const noEnum = {
6
- meta: {
7
- type: "suggestion",
8
- docs: {
9
- description: "Disallow TypeScript enums, prefer union types or const objects",
10
- recommended: true,
11
- },
12
- schema: [],
13
- messages: {
14
- noEnum: 'Enums are discouraged. Use union types (\'type Status = "active" | "inactive"\') or const objects instead.',
15
- },
16
- },
17
- create(context) {
18
- return {
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- TSEnumDeclaration(node) {
21
- context.report({
22
- node,
23
- messageId: "noEnum",
24
- });
25
- },
26
- };
27
- },
28
- };
29
- export default noEnum;
@@ -1,8 +0,0 @@
1
- import type { Rule } from "eslint";
2
- /**
3
- * @fileoverview Disallow direct process.env access
4
- * @description Use Zod-validated env schema instead of process.env
5
- */
6
- export declare const noRawEnv: Rule.RuleModule;
7
- export default noRawEnv;
8
- //# sourceMappingURL=no-raw-env.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"no-raw-env.d.ts","sourceRoot":"","sources":["../../src/rules/no-raw-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGnC;;;GAGG;AAEH,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,UA8B3B,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -1,33 +0,0 @@
1
- /**
2
- * @fileoverview Disallow direct process.env access
3
- * @description Use Zod-validated env schema instead of process.env
4
- */
5
- export const noRawEnv = {
6
- meta: {
7
- type: "problem",
8
- docs: {
9
- description: "Disallow direct process.env access",
10
- recommended: true,
11
- },
12
- schema: [],
13
- messages: {
14
- noRawEnv: "Use Zod-validated env schema instead of process.env directly",
15
- },
16
- },
17
- create(context) {
18
- return {
19
- MemberExpression(node) {
20
- if (node.object.type === "Identifier" &&
21
- node.object.name === "process" &&
22
- node.property.type === "Identifier" &&
23
- node.property.name === "env") {
24
- context.report({
25
- node,
26
- messageId: "noRawEnv",
27
- });
28
- }
29
- },
30
- };
31
- },
32
- };
33
- export default noRawEnv;
@@ -1,4 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const preferShadcn: Rule.RuleModule;
3
- export default preferShadcn;
4
- //# sourceMappingURL=prefer-shadcn.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prefer-shadcn.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-shadcn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAgBnC,eAAO,MAAM,YAAY,EAAE,IAAI,CAAC,UAwC/B,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -1,50 +0,0 @@
1
- /**
2
- * @fileoverview Prefer shadcn/ui components over native HTML elements
3
- * @description Use shadcn components for consistent design system
4
- */
5
- const REPLACEMENTS = {
6
- button: "Button",
7
- input: "Input",
8
- select: "Select",
9
- textarea: "Textarea",
10
- table: "Table",
11
- dialog: "Dialog",
12
- };
13
- export const preferShadcn = {
14
- meta: {
15
- type: "suggestion",
16
- docs: {
17
- description: "Prefer shadcn/ui components over native HTML elements",
18
- recommended: true,
19
- },
20
- schema: [],
21
- messages: {
22
- preferShadcn: "Use shadcn <{{replacement}}> component from @/components/ui/{{lowercase}} instead of native <{{element}}>",
23
- },
24
- },
25
- create(context) {
26
- return {
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- JSXOpeningElement(node) {
29
- // Only check lowercase element names (native HTML)
30
- if (!node.name || node.name.type !== "JSXIdentifier") {
31
- return;
32
- }
33
- const elementName = node.name.name;
34
- const replacement = REPLACEMENTS[elementName];
35
- if (replacement) {
36
- context.report({
37
- node,
38
- messageId: "preferShadcn",
39
- data: {
40
- element: elementName,
41
- replacement,
42
- lowercase: elementName,
43
- },
44
- });
45
- }
46
- },
47
- };
48
- },
49
- };
50
- export default preferShadcn;
@@ -1,8 +0,0 @@
1
- import type { Rule } from "eslint";
2
- /**
3
- * @fileoverview Require assertNever in switch statement default cases
4
- * @description Ensures exhaustive type checking in switch statements
5
- */
6
- export declare const requireAssertNever: Rule.RuleModule;
7
- export default requireAssertNever;
8
- //# sourceMappingURL=require-assert-never.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"require-assert-never.d.ts","sourceRoot":"","sources":["../../src/rules/require-assert-never.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGnC;;;GAGG;AAEH,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,UAwDrC,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1,53 +0,0 @@
1
- /**
2
- * @fileoverview Require assertNever in switch statement default cases
3
- * @description Ensures exhaustive type checking in switch statements
4
- */
5
- export const requireAssertNever = {
6
- meta: {
7
- type: "problem",
8
- docs: {
9
- description: "Require assertNever in switch statement default cases for exhaustive type checking",
10
- recommended: true,
11
- },
12
- schema: [],
13
- messages: {
14
- missingAssertNever: "Switch statement default case must call assertNever() for exhaustive type checking",
15
- },
16
- },
17
- create(context) {
18
- return {
19
- SwitchStatement(node) {
20
- const defaultCase = node.cases.find((caseNode) => caseNode.test === null);
21
- if (!defaultCase) {
22
- return;
23
- }
24
- const hasAssertNever = defaultCase.consequent.some((statement) => {
25
- // Check for ExpressionStatement containing CallExpression
26
- if (statement.type === "ExpressionStatement") {
27
- const exprStmt = statement;
28
- if (exprStmt.expression.type === "CallExpression") {
29
- const callee = exprStmt.expression.callee;
30
- return callee.type === "Identifier" && callee.name === "assertNever";
31
- }
32
- }
33
- // Check for ThrowStatement with assertNever call
34
- if (statement.type === "ThrowStatement") {
35
- const throwStmt = statement;
36
- if (throwStmt.argument?.type === "CallExpression") {
37
- const callee = throwStmt.argument.callee;
38
- return callee.type === "Identifier" && callee.name === "assertNever";
39
- }
40
- }
41
- return false;
42
- });
43
- if (!hasAssertNever) {
44
- context.report({
45
- node: defaultCase,
46
- messageId: "missingAssertNever",
47
- });
48
- }
49
- },
50
- };
51
- },
52
- };
53
- export default requireAssertNever;
@@ -1,8 +0,0 @@
1
- import type { Rule } from "eslint";
2
- /**
3
- * @fileoverview Require Zod validation when parsing FormData
4
- * @description Ensures form data is validated with Zod schemas
5
- */
6
- export declare const requireZodFormValidation: Rule.RuleModule;
7
- export default requireZodFormValidation;
8
- //# sourceMappingURL=require-zod-form-validation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"require-zod-form-validation.d.ts","sourceRoot":"","sources":["../../src/rules/require-zod-form-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGnC;;;GAGG;AAEH,eAAO,MAAM,wBAAwB,EAAE,IAAI,CAAC,UAkE3C,CAAC;AAEF,eAAe,wBAAwB,CAAC"}