@sarj/eslint-plugin 0.2.1 → 1.0.0

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 (47) hide show
  1. package/README.md +132 -3
  2. package/dist/index.d.ts +12 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +98 -0
  5. package/dist/rules/enforce-file-structure.d.ts +4 -0
  6. package/dist/rules/enforce-file-structure.d.ts.map +1 -0
  7. package/dist/rules/enforce-file-structure.js +90 -0
  8. package/dist/rules/no-enum.d.ts +8 -0
  9. package/dist/rules/no-enum.d.ts.map +1 -0
  10. package/dist/rules/no-enum.js +29 -0
  11. package/dist/rules/no-enum.test.d.ts +2 -0
  12. package/dist/rules/no-enum.test.d.ts.map +1 -0
  13. package/dist/rules/no-enum.test.js +44 -0
  14. package/dist/rules/no-raw-env.d.ts +8 -0
  15. package/dist/rules/no-raw-env.d.ts.map +1 -0
  16. package/dist/rules/no-raw-env.js +33 -0
  17. package/dist/rules/no-raw-env.test.d.ts +2 -0
  18. package/dist/rules/no-raw-env.test.d.ts.map +1 -0
  19. package/dist/rules/no-raw-env.test.js +36 -0
  20. package/dist/rules/prefer-shadcn.d.ts +4 -0
  21. package/dist/rules/prefer-shadcn.d.ts.map +1 -0
  22. package/dist/rules/prefer-shadcn.js +50 -0
  23. package/dist/rules/prefer-shadcn.test.d.ts +2 -0
  24. package/dist/rules/prefer-shadcn.test.d.ts.map +1 -0
  25. package/dist/rules/prefer-shadcn.test.js +58 -0
  26. package/dist/rules/require-assert-never.d.ts +8 -0
  27. package/dist/rules/require-assert-never.d.ts.map +1 -0
  28. package/dist/rules/require-assert-never.js +53 -0
  29. package/dist/rules/require-assert-never.test.d.ts +2 -0
  30. package/dist/rules/require-assert-never.test.d.ts.map +1 -0
  31. package/dist/rules/require-assert-never.test.js +76 -0
  32. package/dist/rules/require-zod-form-validation.d.ts +8 -0
  33. package/dist/rules/require-zod-form-validation.d.ts.map +1 -0
  34. package/dist/rules/require-zod-form-validation.js +59 -0
  35. package/dist/rules/zod-naming-convention.d.ts +8 -0
  36. package/dist/rules/zod-naming-convention.d.ts.map +1 -0
  37. package/dist/rules/zod-naming-convention.js +53 -0
  38. package/dist/rules/zod-naming-convention.test.d.ts +2 -0
  39. package/dist/rules/zod-naming-convention.test.d.ts.map +1 -0
  40. package/dist/rules/zod-naming-convention.test.js +38 -0
  41. package/package.json +33 -11
  42. package/.claude/settings.local.json +0 -9
  43. package/lib/index.js +0 -19
  44. package/lib/rules/enforce-file-structure.js +0 -85
  45. package/lib/rules/require-assert-never.js +0 -46
  46. package/lib/rules/require-zod-form-validation.js +0 -47
  47. package/lib/rules/zod-naming-convention.js +0 -44
@@ -0,0 +1,8 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,53 @@
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;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=require-assert-never.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-assert-never.test.d.ts","sourceRoot":"","sources":["../../src/rules/require-assert-never.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { RuleTester } from "eslint";
2
+ import { describe, it } from "vitest";
3
+ import { requireAssertNever } from "./require-assert-never.js";
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ ecmaVersion: 2022,
7
+ sourceType: "module",
8
+ },
9
+ });
10
+ describe("require-assert-never", () => {
11
+ it("should pass valid and fail invalid cases", () => {
12
+ ruleTester.run("require-assert-never", requireAssertNever, {
13
+ valid: [
14
+ // Switch with assertNever in default case
15
+ {
16
+ code: `
17
+ switch (status) {
18
+ case "active":
19
+ return 1;
20
+ default:
21
+ assertNever(status);
22
+ }
23
+ `,
24
+ },
25
+ // Switch with throw assertNever
26
+ {
27
+ code: `
28
+ switch (type) {
29
+ case "a":
30
+ break;
31
+ default:
32
+ throw assertNever(type);
33
+ }
34
+ `,
35
+ },
36
+ // Switch without default case is OK
37
+ {
38
+ code: `
39
+ switch (value) {
40
+ case 1:
41
+ return "one";
42
+ case 2:
43
+ return "two";
44
+ }
45
+ `,
46
+ },
47
+ ],
48
+ invalid: [
49
+ // Default case without assertNever
50
+ {
51
+ code: `
52
+ switch (status) {
53
+ case "active":
54
+ return 1;
55
+ default:
56
+ return 0;
57
+ }
58
+ `,
59
+ errors: [{ messageId: "missingAssertNever" }],
60
+ },
61
+ // Empty default case
62
+ {
63
+ code: `
64
+ switch (type) {
65
+ case "a":
66
+ break;
67
+ default:
68
+ break;
69
+ }
70
+ `,
71
+ errors: [{ messageId: "missingAssertNever" }],
72
+ },
73
+ ],
74
+ });
75
+ });
76
+ });
@@ -0,0 +1,8 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview Require Zod validation when parsing FormData
3
+ * @description Ensures form data is validated with Zod schemas
4
+ */
5
+ export const requireZodFormValidation = {
6
+ meta: {
7
+ type: "problem",
8
+ docs: {
9
+ description: "Require Zod validation when parsing FormData",
10
+ recommended: true,
11
+ },
12
+ schema: [],
13
+ messages: {
14
+ missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())",
15
+ },
16
+ },
17
+ create(context) {
18
+ return {
19
+ CallExpression(node) {
20
+ const callExpr = node;
21
+ // Check for formData.get() calls
22
+ if (callExpr.callee.type !== "MemberExpression") {
23
+ return;
24
+ }
25
+ const callee = callExpr.callee;
26
+ if (callee.property.type !== "Identifier" ||
27
+ callee.property.name !== "get" ||
28
+ callee.object.type !== "Identifier" ||
29
+ callee.object.name !== "formData") {
30
+ return;
31
+ }
32
+ // Check if this is inside a Zod .parse() call
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ let parent = node.parent;
35
+ let hasZodValidation = false;
36
+ // Traverse up to find if we're inside a .parse() call
37
+ while (parent) {
38
+ if (parent.type === "CallExpression" &&
39
+ parent.callee.type === "MemberExpression") {
40
+ const parentCallee = parent.callee;
41
+ if (parentCallee.property.type === "Identifier" &&
42
+ parentCallee.property.name === "parse") {
43
+ hasZodValidation = true;
44
+ break;
45
+ }
46
+ }
47
+ parent = parent.parent;
48
+ }
49
+ if (!hasZodValidation) {
50
+ context.report({
51
+ node: node,
52
+ messageId: "missingZodValidation",
53
+ });
54
+ }
55
+ },
56
+ };
57
+ },
58
+ };
59
+ export default requireZodFormValidation;
@@ -0,0 +1,8 @@
1
+ import type { Rule } from "eslint";
2
+ /**
3
+ * @fileoverview Enforce Zod schemas to be named with a Z prefix
4
+ * @description Ensures consistent naming for Zod schemas (e.g., ZUserSchema instead of userSchema)
5
+ */
6
+ export declare const zodNamingConvention: Rule.RuleModule;
7
+ export default zodNamingConvention;
8
+ //# sourceMappingURL=zod-naming-convention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-naming-convention.d.ts","sourceRoot":"","sources":["../../src/rules/zod-naming-convention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGnC;;;GAGG;AAEH,eAAO,MAAM,mBAAmB,EAAE,IAAI,CAAC,UAqDtC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @fileoverview Enforce Zod schemas to be named with a Z prefix
3
+ * @description Ensures consistent naming for Zod schemas (e.g., ZUserSchema instead of userSchema)
4
+ */
5
+ export const zodNamingConvention = {
6
+ meta: {
7
+ type: "suggestion",
8
+ docs: {
9
+ description: "Enforce Zod schemas to be named with a Z prefix",
10
+ recommended: false,
11
+ },
12
+ schema: [],
13
+ messages: {
14
+ zodPrefix: "Zod schema names should start with 'Z' (e.g., Z{{suggestedName}})",
15
+ },
16
+ },
17
+ create(context) {
18
+ return {
19
+ VariableDeclarator(node) {
20
+ const declarator = node;
21
+ if (!declarator.init || declarator.init.type !== "CallExpression") {
22
+ return;
23
+ }
24
+ const callExpr = declarator.init;
25
+ if (!callExpr.callee || callExpr.callee.type !== "MemberExpression") {
26
+ return;
27
+ }
28
+ const callee = callExpr.callee;
29
+ const isDirectZodCall = callee.object.type === "Identifier" &&
30
+ callee.object.name === "z";
31
+ const isChainedZodCall = callee.object.type === "CallExpression" &&
32
+ callee.object.callee?.type === "MemberExpression" &&
33
+ callee.object.callee.object?.type === "Identifier" &&
34
+ callee.object.callee.object.name === "z";
35
+ if (isDirectZodCall || isChainedZodCall) {
36
+ if (declarator.id.type !== "Identifier") {
37
+ return;
38
+ }
39
+ const variableName = declarator.id.name;
40
+ if (!variableName.startsWith("Z")) {
41
+ const suggestedName = variableName.charAt(0).toUpperCase() + variableName.slice(1);
42
+ context.report({
43
+ node: declarator.id,
44
+ messageId: "zodPrefix",
45
+ data: { suggestedName },
46
+ });
47
+ }
48
+ }
49
+ },
50
+ };
51
+ },
52
+ };
53
+ export default zodNamingConvention;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=zod-naming-convention.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-naming-convention.test.d.ts","sourceRoot":"","sources":["../../src/rules/zod-naming-convention.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { RuleTester } from "eslint";
2
+ import { describe, it } from "vitest";
3
+ import { zodNamingConvention } from "./zod-naming-convention.js";
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ ecmaVersion: 2022,
7
+ sourceType: "module",
8
+ },
9
+ });
10
+ describe("zod-naming-convention", () => {
11
+ it("should pass valid and fail invalid cases", () => {
12
+ ruleTester.run("zod-naming-convention", zodNamingConvention, {
13
+ valid: [
14
+ // Correct naming with Z prefix
15
+ { code: 'const ZUserSchema = z.object({ name: z.string() });' },
16
+ { code: 'const ZConfig = z.object({ port: z.number() });' },
17
+ { code: 'const ZResponse = z.object({}).extend({});' },
18
+ // Non-Zod variables are fine
19
+ { code: 'const userSchema = createSchema();' },
20
+ { code: 'const config = { name: "test" };' },
21
+ ],
22
+ invalid: [
23
+ {
24
+ code: 'const userSchema = z.object({ name: z.string() });',
25
+ errors: [{ messageId: "zodPrefix" }],
26
+ },
27
+ {
28
+ code: 'const schema = z.string();',
29
+ errors: [{ messageId: "zodPrefix" }],
30
+ },
31
+ {
32
+ code: 'const config = z.object({}).extend({ port: z.number() });',
33
+ errors: [{ messageId: "zodPrefix" }],
34
+ },
35
+ ],
36
+ });
37
+ });
38
+ });
package/package.json CHANGED
@@ -1,30 +1,52 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "0.2.1",
4
- "description": "Custom ESLint rules collection",
5
- "main": "lib/index.js",
3
+ "version": "1.0.0",
4
+ "description": "Custom ESLint rules collection for Sarj projects",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
6
17
  "publishConfig": {
7
18
  "access": "public"
8
19
  },
9
20
  "scripts": {
10
- "test": "mocha tests/**/*.test.js"
21
+ "build": "tsc",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "lint": "eslint src",
25
+ "prepublishOnly": "yarn build"
11
26
  },
12
27
  "keywords": [
13
28
  "eslint",
14
29
  "eslintplugin",
15
30
  "eslint-plugin",
16
- "zod"
31
+ "zod",
32
+ "typescript",
33
+ "flat-config"
17
34
  ],
18
- "author": "Your Name",
35
+ "author": "Sarj",
19
36
  "license": "MIT",
20
37
  "devDependencies": {
21
- "eslint": "^8.0.0",
22
- "mocha": "^10.0.0"
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"
23
44
  },
24
45
  "peerDependencies": {
25
- "eslint": ">=6.0.0"
46
+ "eslint": ">=9.0.0"
26
47
  },
27
48
  "engines": {
28
- "node": ">=14.0.0"
29
- }
49
+ "node": ">=18.0.0"
50
+ },
51
+ "packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8"
30
52
  }
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npx eslint:*)",
5
- "Bash(rm:*)"
6
- ],
7
- "deny": []
8
- }
9
- }
package/lib/index.js DELETED
@@ -1,19 +0,0 @@
1
- module.exports = {
2
- rules: {
3
- "zod-naming-convention": require("./rules/zod-naming-convention"),
4
- "require-assert-never": require("./rules/require-assert-never"),
5
- "require-zod-form-validation": require("./rules/require-zod-form-validation"),
6
- "enforce-file-structure": require("./rules/enforce-file-structure"),
7
- },
8
- configs: {
9
- recommended: {
10
- plugins: ["sarj-eslint"],
11
- rules: {
12
- "sarj-eslint/zod-naming-convention": "warn",
13
- "sarj-eslint/require-assert-never": "error",
14
- "sarj-eslint/require-zod-form-validation": "error",
15
- "sarj-eslint/enforce-file-structure": "warn",
16
- },
17
- },
18
- },
19
- };
@@ -1,85 +0,0 @@
1
- module.exports = {
2
- meta: {
3
- type: "suggestion",
4
- docs: {
5
- description: "Enforce consistent file structure: imports → types → constants → functions → exports",
6
- category: "Stylistic Issues",
7
- recommended: true,
8
- },
9
- schema: [],
10
- messages: {
11
- incorrectOrder: "File structure violation: {{current}} should come after {{expected}}",
12
- useServerDirective: "Server action files must start with 'use server' directive"
13
- }
14
- },
15
- create(context) {
16
- const filename = context.getFilename();
17
- const isServerAction = filename.includes('/actions/') || filename.includes('action');
18
-
19
- return {
20
- Program(node) {
21
- const body = node.body;
22
- let currentSection = 0; // 0: imports, 1: types, 2: constants, 3: functions, 4: exports
23
- const sections = ['imports', 'types', 'constants', 'functions', 'exports'];
24
-
25
- // Check for "use server" directive in server action files
26
- if (isServerAction) {
27
- const firstNode = body[0];
28
- if (!firstNode ||
29
- firstNode.type !== 'ExpressionStatement' ||
30
- firstNode.expression.type !== 'Literal' ||
31
- firstNode.expression.value !== 'use server') {
32
- context.report({
33
- node: node,
34
- messageId: "useServerDirective"
35
- });
36
- }
37
- }
38
-
39
- body.forEach(statement => {
40
- let statementSection = getStatementSection(statement);
41
-
42
- if (statementSection < currentSection) {
43
- context.report({
44
- node: statement,
45
- messageId: "incorrectOrder",
46
- data: {
47
- current: sections[statementSection],
48
- expected: sections[currentSection]
49
- }
50
- });
51
- } else {
52
- currentSection = Math.max(currentSection, statementSection);
53
- }
54
- });
55
- }
56
- };
57
-
58
- function getStatementSection(statement) {
59
- switch (statement.type) {
60
- case 'ImportDeclaration':
61
- return 0; // imports
62
- case 'TSTypeAliasDeclaration':
63
- case 'TSInterfaceDeclaration':
64
- case 'TSEnumDeclaration':
65
- return 1; // types
66
- case 'VariableDeclaration':
67
- // Check if it's a constant (const with UPPER_CASE or simple values)
68
- if (statement.kind === 'const') {
69
- const declarator = statement.declarations[0];
70
- if (declarator && declarator.id.name === declarator.id.name.toUpperCase()) {
71
- return 2; // constants
72
- }
73
- }
74
- return 3; // functions (const fn = ...)
75
- case 'FunctionDeclaration':
76
- return 3; // functions
77
- case 'ExportNamedDeclaration':
78
- case 'ExportDefaultDeclaration':
79
- return 4; // exports
80
- default:
81
- return 3; // default to functions
82
- }
83
- }
84
- }
85
- };
@@ -1,46 +0,0 @@
1
- module.exports = {
2
- meta: {
3
- type: "problem",
4
- docs: {
5
- description: "Require assertNever in switch statement default cases for exhaustive checking",
6
- category: "Best Practices",
7
- recommended: true,
8
- },
9
- schema: [],
10
- messages: {
11
- missingAssertNever: "Switch statement default case must call assertNever() for exhaustive type checking"
12
- }
13
- },
14
- create(context) {
15
- return {
16
- SwitchStatement(node) {
17
- const defaultCase = node.cases.find(caseNode => caseNode.test === null);
18
-
19
- if (defaultCase) {
20
- const hasAssertNever = defaultCase.consequent.some(statement => {
21
- // Check for ExpressionStatement containing CallExpression
22
- if (statement.type === 'ExpressionStatement' &&
23
- statement.expression.type === 'CallExpression') {
24
- const callee = statement.expression.callee;
25
- return callee.name === 'assertNever';
26
- }
27
- // Check for ThrowStatement with assertNever call
28
- if (statement.type === 'ThrowStatement' &&
29
- statement.argument.type === 'CallExpression') {
30
- const callee = statement.argument.callee;
31
- return callee.name === 'assertNever';
32
- }
33
- return false;
34
- });
35
-
36
- if (!hasAssertNever) {
37
- context.report({
38
- node: defaultCase,
39
- messageId: "missingAssertNever"
40
- });
41
- }
42
- }
43
- }
44
- };
45
- }
46
- };
@@ -1,47 +0,0 @@
1
- module.exports = {
2
- meta: {
3
- type: "problem",
4
- docs: {
5
- description: "Require Zod validation when parsing FormData",
6
- category: "Best Practices",
7
- recommended: true,
8
- },
9
- schema: [],
10
- messages: {
11
- missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())"
12
- }
13
- },
14
- create(context) {
15
- return {
16
- CallExpression(node) {
17
- // Check for formData.get() calls
18
- if (node.callee.type === 'MemberExpression' &&
19
- node.callee.property.name === 'get' &&
20
- node.callee.object.name === 'formData') {
21
-
22
- // Check if this is inside a Zod .parse() call
23
- let parent = node.parent;
24
- let hasZodValidation = false;
25
-
26
- // Traverse up to find if we're inside a .parse() call
27
- while (parent) {
28
- if (parent.type === 'CallExpression' &&
29
- parent.callee.type === 'MemberExpression' &&
30
- parent.callee.property.name === 'parse') {
31
- hasZodValidation = true;
32
- break;
33
- }
34
- parent = parent.parent;
35
- }
36
-
37
- if (!hasZodValidation) {
38
- context.report({
39
- node,
40
- messageId: "missingZodValidation"
41
- });
42
- }
43
- }
44
- }
45
- };
46
- }
47
- };
@@ -1,44 +0,0 @@
1
- module.exports = {
2
- meta: {
3
- type: "suggestion",
4
- docs: {
5
- description: "Enforce Zod schemas to be named with a Z prefix",
6
- category: "Stylistic Issues",
7
- recommended: false
8
- },
9
- fixable: null,
10
- schema: []
11
- },
12
- create(context) {
13
- return {
14
- VariableDeclarator(node) {
15
- // Check if the right side involves z.object() or other Zod schema creation
16
- if (
17
- node.init &&
18
- node.init.type === "CallExpression" &&
19
- node.init.callee &&
20
- (
21
- // Check direct calls like z.object()
22
- (node.init.callee.type === "MemberExpression" &&
23
- node.init.callee.object &&
24
- node.init.callee.object.name === "z") ||
25
- // Check chained calls like z.object().extend()
26
- (node.init.callee.type === "MemberExpression" &&
27
- node.init.callee.object &&
28
- node.init.callee.object.callee &&
29
- node.init.callee.object.callee.object &&
30
- node.init.callee.object.callee.object.name === "z")
31
- )
32
- ) {
33
- const variableName = node.id.name;
34
- if (!variableName.startsWith("Z")) {
35
- context.report({
36
- node: node.id,
37
- message: "Zod schema names should start with Z",
38
- });
39
- }
40
- }
41
- },
42
- };
43
- },
44
- };