eslint-plugin-zod 1.3.0 → 3.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.
- package/LICENSE +19 -22
- package/README.md +84 -56
- package/dist/index.cjs +71 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +68 -0
- package/dist/meta.cjs +13 -0
- package/dist/meta.d.cts +3 -0
- package/dist/meta.d.ts +3 -0
- package/dist/meta.js +8 -0
- package/dist/rules/array-style.cjs +99 -0
- package/dist/rules/array-style.d.cts +10 -0
- package/dist/rules/array-style.d.ts +10 -0
- package/dist/rules/array-style.js +96 -0
- package/dist/rules/consistent-import-source.cjs +73 -0
- package/dist/rules/consistent-import-source.d.cts +10 -0
- package/dist/rules/consistent-import-source.d.ts +10 -0
- package/dist/rules/consistent-import-source.js +70 -0
- package/dist/rules/consistent-object-schema-type.cjs +83 -0
- package/dist/rules/consistent-object-schema-type.d.cts +11 -0
- package/dist/rules/consistent-object-schema-type.d.ts +11 -0
- package/dist/rules/consistent-object-schema-type.js +80 -0
- package/dist/rules/no-any-schema.cjs +66 -0
- package/dist/rules/no-any-schema.d.cts +4 -0
- package/dist/rules/no-any-schema.d.ts +4 -0
- package/dist/rules/no-any-schema.js +63 -0
- package/dist/rules/no-empty-custom-schema.cjs +40 -0
- package/dist/rules/no-empty-custom-schema.d.cts +4 -0
- package/dist/rules/no-empty-custom-schema.d.ts +4 -0
- package/dist/rules/no-empty-custom-schema.js +37 -0
- package/dist/rules/no-number-schema-with-int.cjs +78 -0
- package/dist/rules/no-number-schema-with-int.d.cts +4 -0
- package/dist/rules/no-number-schema-with-int.d.ts +4 -0
- package/dist/rules/no-number-schema-with-int.js +75 -0
- package/dist/rules/no-optional-and-default-together.cjs +80 -0
- package/dist/rules/no-optional-and-default-together.d.cts +11 -0
- package/dist/rules/no-optional-and-default-together.d.ts +11 -0
- package/dist/rules/no-optional-and-default-together.js +77 -0
- package/dist/rules/no-throw-in-refine.cjs +84 -0
- package/dist/rules/no-throw-in-refine.d.cts +4 -0
- package/dist/rules/no-throw-in-refine.d.ts +4 -0
- package/dist/rules/no-throw-in-refine.js +81 -0
- package/dist/rules/no-unknown-schema.cjs +35 -0
- package/dist/rules/no-unknown-schema.d.cts +4 -0
- package/dist/rules/no-unknown-schema.d.ts +4 -0
- package/dist/rules/no-unknown-schema.js +32 -0
- package/dist/rules/prefer-enum-over-literal-union.cjs +72 -0
- package/dist/rules/prefer-enum-over-literal-union.d.cts +4 -0
- package/dist/rules/prefer-enum-over-literal-union.d.ts +4 -0
- package/dist/rules/prefer-enum-over-literal-union.js +69 -0
- package/dist/rules/prefer-meta-last.cjs +71 -0
- package/dist/rules/prefer-meta-last.d.cts +4 -0
- package/dist/rules/prefer-meta-last.d.ts +4 -0
- package/dist/rules/prefer-meta-last.js +68 -0
- package/dist/rules/prefer-meta.cjs +48 -0
- package/dist/rules/prefer-meta.d.cts +4 -0
- package/dist/rules/prefer-meta.d.ts +4 -0
- package/dist/rules/prefer-meta.js +45 -0
- package/dist/rules/prefer-namespace-import.cjs +145 -0
- package/dist/rules/prefer-namespace-import.d.cts +4 -0
- package/dist/rules/prefer-namespace-import.d.ts +4 -0
- package/dist/rules/prefer-namespace-import.js +142 -0
- package/dist/rules/require-brand-type-parameter.cjs +60 -0
- package/dist/rules/require-brand-type-parameter.d.cts +4 -0
- package/dist/rules/require-brand-type-parameter.d.ts +4 -0
- package/dist/rules/require-brand-type-parameter.js +57 -0
- package/dist/rules/require-error-message.cjs +101 -0
- package/dist/rules/require-error-message.d.cts +4 -0
- package/dist/rules/require-error-message.d.ts +4 -0
- package/dist/rules/require-error-message.js +98 -0
- package/dist/rules/require-schema-suffix.cjs +73 -0
- package/dist/rules/require-schema-suffix.d.cts +8 -0
- package/dist/rules/require-schema-suffix.d.ts +8 -0
- package/dist/rules/require-schema-suffix.js +70 -0
- package/dist/rules/schema-error-property-style.cjs +106 -0
- package/dist/rules/schema-error-property-style.d.cts +9 -0
- package/dist/rules/schema-error-property-style.d.ts +9 -0
- package/dist/rules/schema-error-property-style.js +100 -0
- package/dist/utils/detect-zod-schema-root-node.cjs +104 -0
- package/dist/utils/detect-zod-schema-root-node.d.cts +10 -0
- package/dist/utils/detect-zod-schema-root-node.d.ts +10 -0
- package/dist/utils/detect-zod-schema-root-node.js +101 -0
- package/dist/utils/get-outermost-call.cjs +14 -0
- package/dist/utils/get-outermost-call.d.cts +2 -0
- package/dist/utils/get-outermost-call.d.ts +2 -0
- package/dist/utils/get-outermost-call.js +11 -0
- package/dist/utils/is-zod-expression.cjs +14 -0
- package/dist/utils/is-zod-expression.d.cts +2 -0
- package/dist/utils/is-zod-expression.d.ts +2 -0
- package/dist/utils/is-zod-expression.js +11 -0
- package/dist/utils/is-zod-import-source.cjs +6 -0
- package/dist/utils/is-zod-import-source.d.cts +1 -0
- package/dist/utils/is-zod-import-source.d.ts +1 -0
- package/dist/utils/is-zod-import-source.js +3 -0
- package/dist/utils/track-zod-schema-imports.cjs +61 -0
- package/dist/utils/track-zod-schema-imports.d.cts +13 -0
- package/dist/utils/track-zod-schema-imports.d.ts +13 -0
- package/dist/utils/track-zod-schema-imports.js +58 -0
- package/package.json +82 -38
- package/dist/src/index.d.ts +0 -31
- package/dist/src/index.js +0 -14
- package/dist/src/rules/requireStrict.d.ts +0 -24
- package/dist/src/rules/requireStrict.js +0 -69
- package/tsconfig.json +0 -28
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getRuleURL } from "../meta.js";
|
|
3
|
+
import { trackZodSchemaImports } from "../utils/track-zod-schema-imports.js";
|
|
4
|
+
export const preferEnumOverLiteralUnion = ESLintUtils.RuleCreator(getRuleURL)({
|
|
5
|
+
name: 'prefer-enum-over-literal-union',
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
fixable: 'code',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Prefer `z.enum()` over `z.union()` when all members are string literals.',
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
useEnum: 'Replace this union of string literals with `z.enum()`.',
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
const { importDeclarationListener, detectZodSchemaRootNode, } = trackZodSchemaImports();
|
|
20
|
+
return {
|
|
21
|
+
ImportDeclaration: importDeclarationListener,
|
|
22
|
+
CallExpression(node) {
|
|
23
|
+
const zodSchema = detectZodSchemaRootNode(node);
|
|
24
|
+
if ((zodSchema === null || zodSchema === void 0 ? void 0 : zodSchema.schemaType) !== 'union') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const [unionArgument] = node.arguments;
|
|
28
|
+
if (unionArgument.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const zodLiteralStrings = unionArgument.elements.map((s) => {
|
|
32
|
+
if (!s) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const maybeLiteralSchema = detectZodSchemaRootNode(s);
|
|
36
|
+
if ((maybeLiteralSchema === null || maybeLiteralSchema === void 0 ? void 0 : maybeLiteralSchema.schemaType) !== 'literal') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const [literalArgument] = maybeLiteralSchema.node.arguments;
|
|
40
|
+
if (literalArgument.type === AST_NODE_TYPES.Literal &&
|
|
41
|
+
typeof literalArgument.value === 'string') {
|
|
42
|
+
return literalArgument.raw;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
});
|
|
46
|
+
if (zodLiteralStrings.some((it) => it === null)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (zodSchema.schemaDecl === 'named') {
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: 'useEnum',
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
context.report({
|
|
57
|
+
node,
|
|
58
|
+
messageId: 'useEnum',
|
|
59
|
+
fix(fixer) {
|
|
60
|
+
return [
|
|
61
|
+
fixer.replaceText(node.callee.property, 'enum'),
|
|
62
|
+
fixer.replaceText(node.arguments[0], `[${zodLiteralStrings.join(', ')}]`),
|
|
63
|
+
];
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferMetaLast = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const meta_js_1 = require("../meta.cjs");
|
|
6
|
+
const get_outermost_call_js_1 = require("../utils/get-outermost-call.cjs");
|
|
7
|
+
const is_zod_expression_js_1 = require("../utils/is-zod-expression.cjs");
|
|
8
|
+
const track_zod_schema_imports_js_1 = require("../utils/track-zod-schema-imports.cjs");
|
|
9
|
+
exports.preferMetaLast = utils_1.ESLintUtils.RuleCreator(meta_js_1.getRuleURL)({
|
|
10
|
+
name: 'prefer-meta-last',
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: { description: 'Enforce `.meta()` as last method' },
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
messages: {
|
|
16
|
+
metaNotLast: 'The `.meta()` methods should be the last one called',
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods, } = (0, track_zod_schema_imports_js_1.trackZodSchemaImports)();
|
|
23
|
+
return {
|
|
24
|
+
ImportDeclaration: importDeclarationListener,
|
|
25
|
+
CallExpression(node) {
|
|
26
|
+
if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
27
|
+
!(0, is_zod_expression_js_1.isZodExpressionEndingWithMethod)(node.callee, 'meta')) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const outer = (0, get_outermost_call_js_1.getOutermostCall)(node);
|
|
31
|
+
if (!outer) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const zodSchemaMeta = detectZodSchemaRootNode(outer);
|
|
35
|
+
const chain = collectZodChainMethods(outer);
|
|
36
|
+
if (!zodSchemaMeta) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const metaIndex = chain.findIndex((c) => c.name === 'meta');
|
|
40
|
+
if (metaIndex === -1) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const hasAnyNonMetaAfter = chain
|
|
44
|
+
.slice(metaIndex + 1)
|
|
45
|
+
.some((c) => c.name !== 'meta');
|
|
46
|
+
if (!hasAnyNonMetaAfter) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const metaCall = chain[metaIndex].node;
|
|
50
|
+
const metaCallCallee = metaCall.callee;
|
|
51
|
+
context.report({
|
|
52
|
+
node: metaCallCallee.property,
|
|
53
|
+
messageId: 'metaNotLast',
|
|
54
|
+
fix(fixer) {
|
|
55
|
+
const source = context.sourceCode;
|
|
56
|
+
const metaCallText = source.getText(metaCall);
|
|
57
|
+
const objectText = source.getText(metaCallCallee.object);
|
|
58
|
+
const onlyMetaSuffix = metaCallText.slice(objectText.length);
|
|
59
|
+
const [, removeStart] = metaCallCallee.object.range;
|
|
60
|
+
const [, removeEnd] = metaCall.range;
|
|
61
|
+
const lastCall = chain[chain.length - 1].node;
|
|
62
|
+
return [
|
|
63
|
+
fixer.removeRange([removeStart, removeEnd]),
|
|
64
|
+
fixer.insertTextAfterRange(lastCall.range, onlyMetaSuffix),
|
|
65
|
+
];
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getRuleURL } from "../meta.js";
|
|
3
|
+
import { getOutermostCall } from "../utils/get-outermost-call.js";
|
|
4
|
+
import { isZodExpressionEndingWithMethod } from "../utils/is-zod-expression.js";
|
|
5
|
+
import { trackZodSchemaImports } from "../utils/track-zod-schema-imports.js";
|
|
6
|
+
export const preferMetaLast = ESLintUtils.RuleCreator(getRuleURL)({
|
|
7
|
+
name: 'prefer-meta-last',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: { description: 'Enforce `.meta()` as last method' },
|
|
11
|
+
fixable: 'code',
|
|
12
|
+
messages: {
|
|
13
|
+
metaNotLast: 'The `.meta()` methods should be the last one called',
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods, } = trackZodSchemaImports();
|
|
20
|
+
return {
|
|
21
|
+
ImportDeclaration: importDeclarationListener,
|
|
22
|
+
CallExpression(node) {
|
|
23
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression ||
|
|
24
|
+
!isZodExpressionEndingWithMethod(node.callee, 'meta')) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const outer = getOutermostCall(node);
|
|
28
|
+
if (!outer) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const zodSchemaMeta = detectZodSchemaRootNode(outer);
|
|
32
|
+
const chain = collectZodChainMethods(outer);
|
|
33
|
+
if (!zodSchemaMeta) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const metaIndex = chain.findIndex((c) => c.name === 'meta');
|
|
37
|
+
if (metaIndex === -1) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const hasAnyNonMetaAfter = chain
|
|
41
|
+
.slice(metaIndex + 1)
|
|
42
|
+
.some((c) => c.name !== 'meta');
|
|
43
|
+
if (!hasAnyNonMetaAfter) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const metaCall = chain[metaIndex].node;
|
|
47
|
+
const metaCallCallee = metaCall.callee;
|
|
48
|
+
context.report({
|
|
49
|
+
node: metaCallCallee.property,
|
|
50
|
+
messageId: 'metaNotLast',
|
|
51
|
+
fix(fixer) {
|
|
52
|
+
const source = context.sourceCode;
|
|
53
|
+
const metaCallText = source.getText(metaCall);
|
|
54
|
+
const objectText = source.getText(metaCallCallee.object);
|
|
55
|
+
const onlyMetaSuffix = metaCallText.slice(objectText.length);
|
|
56
|
+
const [, removeStart] = metaCallCallee.object.range;
|
|
57
|
+
const [, removeEnd] = metaCall.range;
|
|
58
|
+
const lastCall = chain[chain.length - 1].node;
|
|
59
|
+
return [
|
|
60
|
+
fixer.removeRange([removeStart, removeEnd]),
|
|
61
|
+
fixer.insertTextAfterRange(lastCall.range, onlyMetaSuffix),
|
|
62
|
+
];
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferMeta = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const meta_js_1 = require("../meta.cjs");
|
|
6
|
+
const track_zod_schema_imports_js_1 = require("../utils/track-zod-schema-imports.cjs");
|
|
7
|
+
exports.preferMeta = utils_1.ESLintUtils.RuleCreator(meta_js_1.getRuleURL)({
|
|
8
|
+
name: 'prefer-meta',
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
fixable: 'code',
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'Enforce usage of `.meta()` over `.describe()`',
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
preferMeta: 'The `.describe()` method still exists for compatibility with Zod 3, but `.meta()` is now the recommended approach.',
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods, } = (0, track_zod_schema_imports_js_1.trackZodSchemaImports)();
|
|
23
|
+
return {
|
|
24
|
+
ImportDeclaration: importDeclarationListener,
|
|
25
|
+
CallExpression(node) {
|
|
26
|
+
const zodSchemaMeta = detectZodSchemaRootNode(node);
|
|
27
|
+
if (!zodSchemaMeta) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const describe = collectZodChainMethods(zodSchemaMeta.node).find((it) => it.name === 'describe');
|
|
31
|
+
if (!describe) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const { callee, arguments: [describeArg], } = describe.node;
|
|
35
|
+
context.report({
|
|
36
|
+
node,
|
|
37
|
+
messageId: 'preferMeta',
|
|
38
|
+
fix(fixer) {
|
|
39
|
+
return [
|
|
40
|
+
fixer.replaceText(callee.property, 'meta'),
|
|
41
|
+
fixer.replaceText(describeArg, `{ description: ${context.sourceCode.getText(describeArg)} }`),
|
|
42
|
+
];
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getRuleURL } from "../meta.js";
|
|
3
|
+
import { trackZodSchemaImports } from "../utils/track-zod-schema-imports.js";
|
|
4
|
+
export const preferMeta = ESLintUtils.RuleCreator(getRuleURL)({
|
|
5
|
+
name: 'prefer-meta',
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
fixable: 'code',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Enforce usage of `.meta()` over `.describe()`',
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
preferMeta: 'The `.describe()` method still exists for compatibility with Zod 3, but `.meta()` is now the recommended approach.',
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods, } = trackZodSchemaImports();
|
|
20
|
+
return {
|
|
21
|
+
ImportDeclaration: importDeclarationListener,
|
|
22
|
+
CallExpression(node) {
|
|
23
|
+
const zodSchemaMeta = detectZodSchemaRootNode(node);
|
|
24
|
+
if (!zodSchemaMeta) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const describe = collectZodChainMethods(zodSchemaMeta.node).find((it) => it.name === 'describe');
|
|
28
|
+
if (!describe) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const { callee, arguments: [describeArg], } = describe.node;
|
|
32
|
+
context.report({
|
|
33
|
+
node,
|
|
34
|
+
messageId: 'preferMeta',
|
|
35
|
+
fix(fixer) {
|
|
36
|
+
return [
|
|
37
|
+
fixer.replaceText(callee.property, 'meta'),
|
|
38
|
+
fixer.replaceText(describeArg, `{ description: ${context.sourceCode.getText(describeArg)} }`),
|
|
39
|
+
];
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferNamespaceImport = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const meta_js_1 = require("../meta.cjs");
|
|
6
|
+
const is_zod_import_source_js_1 = require("../utils/is-zod-import-source.cjs");
|
|
7
|
+
function isGroupFirstImportTypeNamespace(group) {
|
|
8
|
+
const { hasOnlyTypeImports, nodes: [firstImportNode], } = group;
|
|
9
|
+
const { specifiers } = firstImportNode;
|
|
10
|
+
if (specifiers.length === 1 &&
|
|
11
|
+
specifiers[0].type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
12
|
+
if (hasOnlyTypeImports) {
|
|
13
|
+
if (firstImportNode.importKind === 'type') {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
function shouldIdentifierBeRenamed(node) {
|
|
24
|
+
if (node.parent.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
28
|
+
node.parent.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
29
|
+
node.parent.object.name !== node.name) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function getNamespaceAliasNameFrom(node) {
|
|
35
|
+
if (node.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
36
|
+
node.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
37
|
+
return node.local.name;
|
|
38
|
+
}
|
|
39
|
+
if (node.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
40
|
+
node.imported.name === 'z') {
|
|
41
|
+
return node.local.name;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
exports.preferNamespaceImport = utils_1.ESLintUtils.RuleCreator(meta_js_1.getRuleURL)({
|
|
46
|
+
name: 'prefer-namespace-import',
|
|
47
|
+
meta: {
|
|
48
|
+
type: 'suggestion',
|
|
49
|
+
docs: {
|
|
50
|
+
description: "Enforce importing zod as a namespace import (`import * as z from 'zod'`)",
|
|
51
|
+
},
|
|
52
|
+
fixable: 'code',
|
|
53
|
+
messages: {
|
|
54
|
+
useNamespace: 'Import Zod with a namespace import',
|
|
55
|
+
removeDuplicate: 'Zod is already imported via namespace import',
|
|
56
|
+
convertUsage: 'Convert to namespace usage',
|
|
57
|
+
},
|
|
58
|
+
schema: [],
|
|
59
|
+
},
|
|
60
|
+
defaultOptions: [],
|
|
61
|
+
create(context) {
|
|
62
|
+
const { sourceCode } = context;
|
|
63
|
+
const importGroups = {};
|
|
64
|
+
return {
|
|
65
|
+
ImportDeclaration(node) {
|
|
66
|
+
const { source, importKind } = node;
|
|
67
|
+
if (!(0, is_zod_import_source_js_1.isZodImportSource)(source.value)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!importGroups[source.value]) {
|
|
71
|
+
importGroups[source.value] = {
|
|
72
|
+
hasOnlyTypeImports: true,
|
|
73
|
+
nodes: [],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (importGroups[source.value].hasOnlyTypeImports &&
|
|
77
|
+
importKind === 'value') {
|
|
78
|
+
importGroups[source.value].hasOnlyTypeImports = false;
|
|
79
|
+
}
|
|
80
|
+
importGroups[source.value].nodes.push(node);
|
|
81
|
+
},
|
|
82
|
+
'Program:exit': function () {
|
|
83
|
+
let namespaceAliasNameIndex = 0;
|
|
84
|
+
for (const importGroup of Object.values(importGroups)) {
|
|
85
|
+
const { hasOnlyTypeImports, nodes } = importGroup;
|
|
86
|
+
const [firstImportNode, ...othersImportNodes] = nodes;
|
|
87
|
+
const nodesWithVariablesToUpdate = [];
|
|
88
|
+
let namespaceAliasName = null;
|
|
89
|
+
for (const specifier of nodes.flatMap((it) => it.specifiers)) {
|
|
90
|
+
if (!namespaceAliasName) {
|
|
91
|
+
namespaceAliasName = getNamespaceAliasNameFrom(specifier);
|
|
92
|
+
if (namespaceAliasName) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
nodesWithVariablesToUpdate.push(specifier);
|
|
97
|
+
}
|
|
98
|
+
if (!namespaceAliasName) {
|
|
99
|
+
namespaceAliasName = 'z';
|
|
100
|
+
if (namespaceAliasNameIndex > 0) {
|
|
101
|
+
namespaceAliasName = `z${namespaceAliasNameIndex}`;
|
|
102
|
+
namespaceAliasNameIndex += 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const isFirstImportValid = isGroupFirstImportTypeNamespace(importGroup);
|
|
106
|
+
if (!isFirstImportValid) {
|
|
107
|
+
context.report({
|
|
108
|
+
node: firstImportNode,
|
|
109
|
+
messageId: 'useNamespace',
|
|
110
|
+
fix(fixer) {
|
|
111
|
+
const importTypeKeyword = hasOnlyTypeImports ? 'type ' : '';
|
|
112
|
+
const newImportText = `import ${importTypeKeyword}* as ${namespaceAliasName} from ${firstImportNode.source.raw};`;
|
|
113
|
+
return fixer.replaceText(firstImportNode, newImportText);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const allVariables = nodesWithVariablesToUpdate.flatMap((it) => sourceCode.getDeclaredVariables(it));
|
|
118
|
+
const allReferences = allVariables.flatMap((it) => it.references);
|
|
119
|
+
for (const ref of allReferences) {
|
|
120
|
+
const { identifier } = ref;
|
|
121
|
+
if (shouldIdentifierBeRenamed(identifier)) {
|
|
122
|
+
context.report({
|
|
123
|
+
node: identifier,
|
|
124
|
+
messageId: 'convertUsage',
|
|
125
|
+
fix(fixer) {
|
|
126
|
+
const newId = `${namespaceAliasName}.${identifier.name}`;
|
|
127
|
+
return fixer.replaceText(identifier, newId);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const extraImport of othersImportNodes) {
|
|
133
|
+
context.report({
|
|
134
|
+
node: extraImport,
|
|
135
|
+
messageId: 'removeDuplicate',
|
|
136
|
+
fix(fixer) {
|
|
137
|
+
return fixer.removeRange(extraImport.range);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { getRuleURL } from "../meta.js";
|
|
3
|
+
import { isZodImportSource } from "../utils/is-zod-import-source.js";
|
|
4
|
+
function isGroupFirstImportTypeNamespace(group) {
|
|
5
|
+
const { hasOnlyTypeImports, nodes: [firstImportNode], } = group;
|
|
6
|
+
const { specifiers } = firstImportNode;
|
|
7
|
+
if (specifiers.length === 1 &&
|
|
8
|
+
specifiers[0].type === AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
9
|
+
if (hasOnlyTypeImports) {
|
|
10
|
+
if (firstImportNode.importKind === 'type') {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function shouldIdentifierBeRenamed(node) {
|
|
21
|
+
if (node.parent.type === AST_NODE_TYPES.ImportSpecifier) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (node.parent.type === AST_NODE_TYPES.MemberExpression &&
|
|
25
|
+
node.parent.object.type === AST_NODE_TYPES.Identifier &&
|
|
26
|
+
node.parent.object.name !== node.name) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
function getNamespaceAliasNameFrom(node) {
|
|
32
|
+
if (node.type === AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
33
|
+
node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
34
|
+
return node.local.name;
|
|
35
|
+
}
|
|
36
|
+
if (node.imported.type === AST_NODE_TYPES.Identifier &&
|
|
37
|
+
node.imported.name === 'z') {
|
|
38
|
+
return node.local.name;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
export const preferNamespaceImport = ESLintUtils.RuleCreator(getRuleURL)({
|
|
43
|
+
name: 'prefer-namespace-import',
|
|
44
|
+
meta: {
|
|
45
|
+
type: 'suggestion',
|
|
46
|
+
docs: {
|
|
47
|
+
description: "Enforce importing zod as a namespace import (`import * as z from 'zod'`)",
|
|
48
|
+
},
|
|
49
|
+
fixable: 'code',
|
|
50
|
+
messages: {
|
|
51
|
+
useNamespace: 'Import Zod with a namespace import',
|
|
52
|
+
removeDuplicate: 'Zod is already imported via namespace import',
|
|
53
|
+
convertUsage: 'Convert to namespace usage',
|
|
54
|
+
},
|
|
55
|
+
schema: [],
|
|
56
|
+
},
|
|
57
|
+
defaultOptions: [],
|
|
58
|
+
create(context) {
|
|
59
|
+
const { sourceCode } = context;
|
|
60
|
+
const importGroups = {};
|
|
61
|
+
return {
|
|
62
|
+
ImportDeclaration(node) {
|
|
63
|
+
const { source, importKind } = node;
|
|
64
|
+
if (!isZodImportSource(source.value)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!importGroups[source.value]) {
|
|
68
|
+
importGroups[source.value] = {
|
|
69
|
+
hasOnlyTypeImports: true,
|
|
70
|
+
nodes: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (importGroups[source.value].hasOnlyTypeImports &&
|
|
74
|
+
importKind === 'value') {
|
|
75
|
+
importGroups[source.value].hasOnlyTypeImports = false;
|
|
76
|
+
}
|
|
77
|
+
importGroups[source.value].nodes.push(node);
|
|
78
|
+
},
|
|
79
|
+
'Program:exit': function () {
|
|
80
|
+
let namespaceAliasNameIndex = 0;
|
|
81
|
+
for (const importGroup of Object.values(importGroups)) {
|
|
82
|
+
const { hasOnlyTypeImports, nodes } = importGroup;
|
|
83
|
+
const [firstImportNode, ...othersImportNodes] = nodes;
|
|
84
|
+
const nodesWithVariablesToUpdate = [];
|
|
85
|
+
let namespaceAliasName = null;
|
|
86
|
+
for (const specifier of nodes.flatMap((it) => it.specifiers)) {
|
|
87
|
+
if (!namespaceAliasName) {
|
|
88
|
+
namespaceAliasName = getNamespaceAliasNameFrom(specifier);
|
|
89
|
+
if (namespaceAliasName) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
nodesWithVariablesToUpdate.push(specifier);
|
|
94
|
+
}
|
|
95
|
+
if (!namespaceAliasName) {
|
|
96
|
+
namespaceAliasName = 'z';
|
|
97
|
+
if (namespaceAliasNameIndex > 0) {
|
|
98
|
+
namespaceAliasName = `z${namespaceAliasNameIndex}`;
|
|
99
|
+
namespaceAliasNameIndex += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const isFirstImportValid = isGroupFirstImportTypeNamespace(importGroup);
|
|
103
|
+
if (!isFirstImportValid) {
|
|
104
|
+
context.report({
|
|
105
|
+
node: firstImportNode,
|
|
106
|
+
messageId: 'useNamespace',
|
|
107
|
+
fix(fixer) {
|
|
108
|
+
const importTypeKeyword = hasOnlyTypeImports ? 'type ' : '';
|
|
109
|
+
const newImportText = `import ${importTypeKeyword}* as ${namespaceAliasName} from ${firstImportNode.source.raw};`;
|
|
110
|
+
return fixer.replaceText(firstImportNode, newImportText);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const allVariables = nodesWithVariablesToUpdate.flatMap((it) => sourceCode.getDeclaredVariables(it));
|
|
115
|
+
const allReferences = allVariables.flatMap((it) => it.references);
|
|
116
|
+
for (const ref of allReferences) {
|
|
117
|
+
const { identifier } = ref;
|
|
118
|
+
if (shouldIdentifierBeRenamed(identifier)) {
|
|
119
|
+
context.report({
|
|
120
|
+
node: identifier,
|
|
121
|
+
messageId: 'convertUsage',
|
|
122
|
+
fix(fixer) {
|
|
123
|
+
const newId = `${namespaceAliasName}.${identifier.name}`;
|
|
124
|
+
return fixer.replaceText(identifier, newId);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const extraImport of othersImportNodes) {
|
|
130
|
+
context.report({
|
|
131
|
+
node: extraImport,
|
|
132
|
+
messageId: 'removeDuplicate',
|
|
133
|
+
fix(fixer) {
|
|
134
|
+
return fixer.removeRange(extraImport.range);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
});
|