@nest-boot/eslint-plugin 7.0.4 → 7.0.6
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/rules/graphql/graphql-field-config-from-types.d.ts +3 -1
- package/dist/rules/graphql/graphql-field-config-from-types.js +39 -41
- package/dist/rules/graphql/graphql-field-config-from-types.js.map +1 -1
- package/dist/rules/graphql/graphql-field-definite-assignment.d.ts +3 -1
- package/dist/rules/graphql/graphql-field-definite-assignment.js +13 -13
- package/dist/rules/graphql/graphql-field-definite-assignment.js.map +1 -1
- package/dist/rules/import/import-bullmq.d.ts +3 -1
- package/dist/rules/import/import-bullmq.js +4 -4
- package/dist/rules/import/import-bullmq.js.map +1 -1
- package/dist/rules/import/import-graphql.d.ts +3 -1
- package/dist/rules/import/import-graphql.js +4 -4
- package/dist/rules/import/import-graphql.js.map +1 -1
- package/dist/rules/import/import-mikro-orm.d.ts +3 -1
- package/dist/rules/import/import-mikro-orm.js +4 -4
- package/dist/rules/import/import-mikro-orm.js.map +1 -1
- package/dist/rules/index.d.ts +21 -7
- package/dist/rules/mikro-orm/entity-field-definite-assignment.d.ts +3 -1
- package/dist/rules/mikro-orm/entity-field-definite-assignment.js +10 -10
- package/dist/rules/mikro-orm/entity-field-definite-assignment.js.map +1 -1
- package/dist/rules/mikro-orm/entity-property-config-from-types.d.ts +3 -1
- package/dist/rules/mikro-orm/entity-property-config-from-types.js +106 -106
- package/dist/rules/mikro-orm/entity-property-config-from-types.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/createRule.d.ts +3 -1
- package/dist/utils/decorators.d.ts +16 -16
- package/dist/utils/decorators.js +16 -16
- package/package.json +14 -9
- package/src/rules/graphql/graphql-field-config-from-types.spec.ts +18 -18
- package/src/rules/graphql/graphql-field-config-from-types.ts +40 -44
- package/src/rules/graphql/graphql-field-definite-assignment.spec.ts +11 -11
- package/src/rules/graphql/graphql-field-definite-assignment.ts +13 -13
- package/src/rules/import/import-bullmq.spec.ts +9 -9
- package/src/rules/import/import-bullmq.ts +5 -4
- package/src/rules/import/import-graphql.spec.ts +8 -8
- package/src/rules/import/import-graphql.ts +4 -4
- package/src/rules/import/import-mikro-orm.spec.ts +8 -8
- package/src/rules/import/import-mikro-orm.ts +4 -4
- package/src/rules/mikro-orm/entity-field-definite-assignment.spec.ts +18 -18
- package/src/rules/mikro-orm/entity-field-definite-assignment.ts +10 -10
- package/src/rules/mikro-orm/entity-property-config-from-types.spec.ts +22 -22
- package/src/rules/mikro-orm/entity-property-config-from-types.ts +111 -110
- package/src/utils/decorators.ts +16 -16
- package/tsconfig.json +0 -1
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
-
export declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener
|
|
2
|
+
export declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import { TSESTree } from "@typescript-eslint/utils";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* @param classDeclaration
|
|
5
|
-
* @param decoratorNames
|
|
6
|
-
* @returns
|
|
3
|
+
* Checks whether a class has the specified decorator(s).
|
|
4
|
+
* @param classDeclaration - The class declaration node.
|
|
5
|
+
* @param decoratorNames - Decorator name(s) (can be a string or an array of strings).
|
|
6
|
+
* @returns Whether the class has the specified decorator(s).
|
|
7
7
|
*/
|
|
8
8
|
export declare function hasClassDecorator(classDeclaration: TSESTree.ClassDeclaration, decoratorNames: string | string[]): boolean;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param propertyDefinition
|
|
12
|
-
* @param decoratorNames
|
|
13
|
-
* @returns
|
|
10
|
+
* Checks whether a property has the specified decorator(s).
|
|
11
|
+
* @param propertyDefinition - The property definition node.
|
|
12
|
+
* @param decoratorNames - Decorator name(s) (can be a string or an array of strings).
|
|
13
|
+
* @returns Whether the property has the specified decorator(s).
|
|
14
14
|
*/
|
|
15
15
|
export declare function hasPropertyDecorator(propertyDefinition: TSESTree.PropertyDefinition, decoratorNames: string | string[]): boolean;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @param classDeclaration
|
|
19
|
-
* @param decoratorName
|
|
20
|
-
* @returns
|
|
17
|
+
* Gets a decorator from a class declaration.
|
|
18
|
+
* @param classDeclaration - The class declaration node.
|
|
19
|
+
* @param decoratorName - The decorator name.
|
|
20
|
+
* @returns The decorator node, or null if not found.
|
|
21
21
|
*/
|
|
22
22
|
export declare function getClassDecorator(classDeclaration: TSESTree.ClassDeclaration, decoratorName: string): TSESTree.Decorator | null;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param propertyDefinition
|
|
26
|
-
* @param decoratorName
|
|
27
|
-
* @returns
|
|
24
|
+
* Gets a decorator from a property definition.
|
|
25
|
+
* @param propertyDefinition - The property definition node.
|
|
26
|
+
* @param decoratorName - The decorator name.
|
|
27
|
+
* @returns The decorator node, or null if not found.
|
|
28
28
|
*/
|
|
29
29
|
export declare function getPropertyDecorator(propertyDefinition: TSESTree.PropertyDefinition, decoratorName: string): TSESTree.Decorator | null;
|
package/dist/utils/decorators.js
CHANGED
|
@@ -6,10 +6,10 @@ exports.getClassDecorator = getClassDecorator;
|
|
|
6
6
|
exports.getPropertyDecorator = getPropertyDecorator;
|
|
7
7
|
const utils_1 = require("@typescript-eslint/utils");
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @param classDeclaration
|
|
11
|
-
* @param decoratorNames
|
|
12
|
-
* @returns
|
|
9
|
+
* Checks whether a class has the specified decorator(s).
|
|
10
|
+
* @param classDeclaration - The class declaration node.
|
|
11
|
+
* @param decoratorNames - Decorator name(s) (can be a string or an array of strings).
|
|
12
|
+
* @returns Whether the class has the specified decorator(s).
|
|
13
13
|
*/
|
|
14
14
|
function hasClassDecorator(classDeclaration, decoratorNames) {
|
|
15
15
|
const names = Array.isArray(decoratorNames)
|
|
@@ -26,10 +26,10 @@ function hasClassDecorator(classDeclaration, decoratorNames) {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param propertyDefinition
|
|
31
|
-
* @param decoratorNames
|
|
32
|
-
* @returns
|
|
29
|
+
* Checks whether a property has the specified decorator(s).
|
|
30
|
+
* @param propertyDefinition - The property definition node.
|
|
31
|
+
* @param decoratorNames - Decorator name(s) (can be a string or an array of strings).
|
|
32
|
+
* @returns Whether the property has the specified decorator(s).
|
|
33
33
|
*/
|
|
34
34
|
function hasPropertyDecorator(propertyDefinition, decoratorNames) {
|
|
35
35
|
const names = Array.isArray(decoratorNames)
|
|
@@ -46,10 +46,10 @@ function hasPropertyDecorator(propertyDefinition, decoratorNames) {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
* @param classDeclaration
|
|
51
|
-
* @param decoratorName
|
|
52
|
-
* @returns
|
|
49
|
+
* Gets a decorator from a class declaration.
|
|
50
|
+
* @param classDeclaration - The class declaration node.
|
|
51
|
+
* @param decoratorName - The decorator name.
|
|
52
|
+
* @returns The decorator node, or null if not found.
|
|
53
53
|
*/
|
|
54
54
|
function getClassDecorator(classDeclaration, decoratorName) {
|
|
55
55
|
return (classDeclaration.decorators.find((decorator) => {
|
|
@@ -59,10 +59,10 @@ function getClassDecorator(classDeclaration, decoratorName) {
|
|
|
59
59
|
}) ?? null);
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @param propertyDefinition
|
|
64
|
-
* @param decoratorName
|
|
65
|
-
* @returns
|
|
62
|
+
* Gets a decorator from a property definition.
|
|
63
|
+
* @param propertyDefinition - The property definition node.
|
|
64
|
+
* @param decoratorName - The decorator name.
|
|
65
|
+
* @returns The decorator node, or null if not found.
|
|
66
66
|
*/
|
|
67
67
|
function getPropertyDecorator(propertyDefinition, decoratorName) {
|
|
68
68
|
return (propertyDefinition.decorators.find((decorator) => {
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-boot/eslint-plugin",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.6",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/nest-boot/nest-boot.git",
|
|
7
|
+
"directory": "packages/eslint-plugin"
|
|
8
|
+
},
|
|
4
9
|
"license": "MIT",
|
|
5
10
|
"main": "./dist/index.js",
|
|
6
11
|
"exports": "./dist/index.js",
|
|
7
12
|
"dependencies": {
|
|
8
|
-
"@typescript-eslint/utils": "^8.
|
|
13
|
+
"@typescript-eslint/utils": "^8.56.1"
|
|
9
14
|
},
|
|
10
15
|
"peerDependencies": {
|
|
11
16
|
"@typescript-eslint/parser": "^8.0.0",
|
|
@@ -13,20 +18,20 @@
|
|
|
13
18
|
"typescript": "^5.0.0"
|
|
14
19
|
},
|
|
15
20
|
"devDependencies": {
|
|
16
|
-
"@eslint/eslintrc": "^3.3.
|
|
17
|
-
"@eslint/js": "^9.
|
|
21
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
22
|
+
"@eslint/js": "^9.39.2",
|
|
18
23
|
"@jest/globals": "^30.2.0",
|
|
19
24
|
"@types/node": "^22.18.6",
|
|
20
|
-
"@typescript-eslint/parser": "^8.
|
|
21
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
22
|
-
"eslint": "^9.
|
|
25
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
26
|
+
"@typescript-eslint/rule-tester": "^8.56.1",
|
|
27
|
+
"eslint": "^9.39.3",
|
|
23
28
|
"eslint-config-prettier": "^10.1.8",
|
|
24
29
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
25
30
|
"jest": "^29.7.0",
|
|
26
31
|
"ts-jest": "^29.4.4",
|
|
27
32
|
"typescript": "^5.9.3",
|
|
28
|
-
"typescript-eslint": "^8.
|
|
29
|
-
"@nest-boot/tsconfig": "^7.0.
|
|
33
|
+
"typescript-eslint": "^8.56.1",
|
|
34
|
+
"@nest-boot/tsconfig": "^7.0.2"
|
|
30
35
|
},
|
|
31
36
|
"publishConfig": {
|
|
32
37
|
"access": "public"
|
|
@@ -3,7 +3,7 @@ import rule from "./graphql-field-config-from-types";
|
|
|
3
3
|
|
|
4
4
|
tester.run("graphql-field-config-from-types", rule, {
|
|
5
5
|
valid: [
|
|
6
|
-
//
|
|
6
|
+
// Correct string type
|
|
7
7
|
/* typescript */ `
|
|
8
8
|
@ObjectType()
|
|
9
9
|
class User {
|
|
@@ -11,7 +11,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
11
11
|
name!: string;
|
|
12
12
|
}
|
|
13
13
|
`,
|
|
14
|
-
//
|
|
14
|
+
// Correct number type (Float)
|
|
15
15
|
/* typescript */ `
|
|
16
16
|
@ObjectType()
|
|
17
17
|
class User {
|
|
@@ -19,7 +19,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
19
19
|
score!: number;
|
|
20
20
|
}
|
|
21
21
|
`,
|
|
22
|
-
//
|
|
22
|
+
// Correct number type (Int)
|
|
23
23
|
/* typescript */ `
|
|
24
24
|
@ObjectType()
|
|
25
25
|
class User {
|
|
@@ -27,7 +27,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
27
27
|
age!: number;
|
|
28
28
|
}
|
|
29
29
|
`,
|
|
30
|
-
//
|
|
30
|
+
// Correct boolean type
|
|
31
31
|
/* typescript */ `
|
|
32
32
|
@ObjectType()
|
|
33
33
|
class User {
|
|
@@ -35,7 +35,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
35
35
|
isActive!: boolean;
|
|
36
36
|
}
|
|
37
37
|
`,
|
|
38
|
-
//
|
|
38
|
+
// Correct nullable configuration
|
|
39
39
|
/* typescript */ `
|
|
40
40
|
@ObjectType()
|
|
41
41
|
class User {
|
|
@@ -43,7 +43,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
43
43
|
name?: string;
|
|
44
44
|
}
|
|
45
45
|
`,
|
|
46
|
-
//
|
|
46
|
+
// Correct array type
|
|
47
47
|
/* typescript */ `
|
|
48
48
|
@ObjectType()
|
|
49
49
|
class User {
|
|
@@ -51,7 +51,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
51
51
|
tags!: string[];
|
|
52
52
|
}
|
|
53
53
|
`,
|
|
54
|
-
// ID
|
|
54
|
+
// ID type
|
|
55
55
|
/* typescript */ `
|
|
56
56
|
@ObjectType()
|
|
57
57
|
class User {
|
|
@@ -59,7 +59,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
59
59
|
id!: string;
|
|
60
60
|
}
|
|
61
61
|
`,
|
|
62
|
-
//
|
|
62
|
+
// Custom type
|
|
63
63
|
/* typescript */ `
|
|
64
64
|
@ObjectType()
|
|
65
65
|
class User {
|
|
@@ -67,7 +67,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
67
67
|
profile!: Profile;
|
|
68
68
|
}
|
|
69
69
|
`,
|
|
70
|
-
//
|
|
70
|
+
// Property with @HideField decorator does not need @Field
|
|
71
71
|
/* typescript */ `
|
|
72
72
|
@ObjectType()
|
|
73
73
|
class User {
|
|
@@ -75,13 +75,13 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
75
75
|
password!: string;
|
|
76
76
|
}
|
|
77
77
|
`,
|
|
78
|
-
//
|
|
78
|
+
// Non-GraphQL model class is not checked
|
|
79
79
|
/* typescript */ `
|
|
80
80
|
class NotAGraphQLModel {
|
|
81
81
|
field: string;
|
|
82
82
|
}
|
|
83
83
|
`,
|
|
84
|
-
//
|
|
84
|
+
// Complex config object (with spread operator and conditional expression)
|
|
85
85
|
/* typescript */ `
|
|
86
86
|
@ObjectType()
|
|
87
87
|
class Connection {
|
|
@@ -98,7 +98,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
98
98
|
`,
|
|
99
99
|
],
|
|
100
100
|
invalid: [
|
|
101
|
-
// @Field
|
|
101
|
+
// @Field type mismatch
|
|
102
102
|
{
|
|
103
103
|
code: /* typescript */ `
|
|
104
104
|
@ObjectType()
|
|
@@ -116,7 +116,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
116
116
|
`,
|
|
117
117
|
errors: [{ messageId: "alignFieldDecoratorWithTsType" }],
|
|
118
118
|
},
|
|
119
|
-
// nullable
|
|
119
|
+
// nullable configuration mismatch
|
|
120
120
|
{
|
|
121
121
|
code: /* typescript */ `
|
|
122
122
|
@ObjectType()
|
|
@@ -134,7 +134,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
134
134
|
`,
|
|
135
135
|
errors: [{ messageId: "alignFieldDecoratorWithTsType" }],
|
|
136
136
|
},
|
|
137
|
-
//
|
|
137
|
+
// Should not have nullable but it is set
|
|
138
138
|
{
|
|
139
139
|
code: /* typescript */ `
|
|
140
140
|
@ObjectType()
|
|
@@ -152,7 +152,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
152
152
|
`,
|
|
153
153
|
errors: [{ messageId: "alignFieldDecoratorWithTsType" }],
|
|
154
154
|
},
|
|
155
|
-
//
|
|
155
|
+
// Array type mismatch
|
|
156
156
|
{
|
|
157
157
|
code: /* typescript */ `
|
|
158
158
|
@ObjectType()
|
|
@@ -170,7 +170,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
170
170
|
`,
|
|
171
171
|
errors: [{ messageId: "alignFieldDecoratorWithTsType" }],
|
|
172
172
|
},
|
|
173
|
-
//
|
|
173
|
+
// Missing type function but has other config options (should be preserved)
|
|
174
174
|
{
|
|
175
175
|
code: /* typescript */ `
|
|
176
176
|
@ObjectType()
|
|
@@ -191,7 +191,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
191
191
|
`,
|
|
192
192
|
errors: [{ messageId: "alignFieldDecoratorWithTsType" }],
|
|
193
193
|
},
|
|
194
|
-
//
|
|
194
|
+
// nullable config should preserve other options
|
|
195
195
|
{
|
|
196
196
|
code: /* typescript */ `
|
|
197
197
|
@ObjectType()
|
|
@@ -209,7 +209,7 @@ tester.run("graphql-field-config-from-types", rule, {
|
|
|
209
209
|
`,
|
|
210
210
|
errors: [{ messageId: "alignFieldDecoratorWithTsType" }],
|
|
211
211
|
},
|
|
212
|
-
//
|
|
212
|
+
// Complex config object (with spread operator) missing type function
|
|
213
213
|
{
|
|
214
214
|
code: /* typescript */ `
|
|
215
215
|
@ObjectType()
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
hasClassDecorator,
|
|
8
8
|
} from "../../utils/decorators";
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// Custom Fix object type for deferred fix application
|
|
11
11
|
interface CustomFix {
|
|
12
12
|
type: "insert" | "replace";
|
|
13
13
|
range: readonly [number, number];
|
|
@@ -35,7 +35,7 @@ export default createRule<
|
|
|
35
35
|
type: "problem",
|
|
36
36
|
docs: {
|
|
37
37
|
description:
|
|
38
|
-
"
|
|
38
|
+
"Automatically generate or fix @Field decorator type and nullable configuration based on TypeScript types (with array support).",
|
|
39
39
|
},
|
|
40
40
|
fixable: "code",
|
|
41
41
|
schema: [
|
|
@@ -49,7 +49,7 @@ export default createRule<
|
|
|
49
49
|
enum: ["ignore", "remove"],
|
|
50
50
|
},
|
|
51
51
|
description:
|
|
52
|
-
"
|
|
52
|
+
"Configure behavior for each decorator. ignore: skip checks; remove: remove @Field. Default: { HideField: 'remove', OneToOne: 'remove', OneToMany: 'remove', ManyToOne: 'remove', ManyToMany: 'remove' }",
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
additionalProperties: false,
|
|
@@ -57,9 +57,9 @@ export default createRule<
|
|
|
57
57
|
],
|
|
58
58
|
messages: {
|
|
59
59
|
alignFieldDecoratorWithTsType:
|
|
60
|
-
"@Field
|
|
60
|
+
"@Field decorator should align with the TypeScript type (type and nullable).",
|
|
61
61
|
removeFieldDecorator:
|
|
62
|
-
"
|
|
62
|
+
"Property has a @{{decoratorName}} decorator, @Field decorator should be removed.",
|
|
63
63
|
},
|
|
64
64
|
},
|
|
65
65
|
defaultOptions: [
|
|
@@ -85,7 +85,7 @@ export default createRule<
|
|
|
85
85
|
case AST_NODE_TYPES.TSStringKeyword:
|
|
86
86
|
return "String";
|
|
87
87
|
case AST_NODE_TYPES.TSNumberKeyword:
|
|
88
|
-
//
|
|
88
|
+
// Default number → Float (GraphQL default is also Float; Int must be declared explicitly)
|
|
89
89
|
return "Float";
|
|
90
90
|
default:
|
|
91
91
|
return null;
|
|
@@ -129,12 +129,12 @@ export default createRule<
|
|
|
129
129
|
? property.typeAnnotation.typeAnnotation
|
|
130
130
|
: null;
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Optional property (?) is treated as nullable
|
|
133
133
|
if (property.optional) {
|
|
134
134
|
isNullable = true;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
//
|
|
137
|
+
// Handle null/undefined in union types
|
|
138
138
|
if (baseTypeNode?.type === AST_NODE_TYPES.TSUnionType) {
|
|
139
139
|
const hasNullish = baseTypeNode.types.some((t: TSESTree.TypeNode) => {
|
|
140
140
|
return (
|
|
@@ -153,7 +153,7 @@ export default createRule<
|
|
|
153
153
|
}) ?? null;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
//
|
|
156
|
+
// First unwrap Ref<T> and Opt<T> → T, and handle null/undefined within
|
|
157
157
|
if (
|
|
158
158
|
baseTypeNode?.type === AST_NODE_TYPES.TSTypeReference &&
|
|
159
159
|
baseTypeNode.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
@@ -161,7 +161,7 @@ export default createRule<
|
|
|
161
161
|
baseTypeNode.typeName.name === "Opt")
|
|
162
162
|
) {
|
|
163
163
|
let inner = baseTypeNode.typeArguments?.params[0] ?? null;
|
|
164
|
-
if (inner
|
|
164
|
+
if (inner?.type === AST_NODE_TYPES.TSUnionType) {
|
|
165
165
|
const hasNullish = inner.types.some((t: TSESTree.TypeNode) => {
|
|
166
166
|
return (
|
|
167
167
|
t.type === AST_NODE_TYPES.TSNullKeyword ||
|
|
@@ -180,7 +180,7 @@ export default createRule<
|
|
|
180
180
|
baseTypeNode = inner ?? baseTypeNode;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Array type (T[] or Array<T>) - checked after unwrapping Opt/Ref
|
|
184
184
|
const elementTypeNode = baseTypeNode
|
|
185
185
|
? extractArrayElementType(baseTypeNode)
|
|
186
186
|
: null;
|
|
@@ -191,7 +191,7 @@ export default createRule<
|
|
|
191
191
|
const targetTypeNode: TSESTree.TypeNode | null =
|
|
192
192
|
elementTypeNode ?? baseTypeNode;
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// When no explicit type, try to infer from literal initializer
|
|
195
195
|
if (!targetTypeNode) {
|
|
196
196
|
if (property.value?.type === AST_NODE_TYPES.Literal) {
|
|
197
197
|
const value = property.value.value;
|
|
@@ -206,7 +206,7 @@ export default createRule<
|
|
|
206
206
|
return null;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// id
|
|
209
|
+
// id field prefers GraphQL scalar ID
|
|
210
210
|
const propertyName =
|
|
211
211
|
property.key.type === AST_NODE_TYPES.Identifier
|
|
212
212
|
? property.key.name
|
|
@@ -220,7 +220,7 @@ export default createRule<
|
|
|
220
220
|
return { typeName: "ID", isArray, isNullable };
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
// Record<*, *> → GraphQLJSONObject
|
|
223
|
+
// Record<*, *> → GraphQLJSONObject (no restriction on key/value types)
|
|
224
224
|
if (
|
|
225
225
|
targetTypeNode.type === AST_NODE_TYPES.TSTypeReference &&
|
|
226
226
|
targetTypeNode.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
@@ -229,13 +229,13 @@ export default createRule<
|
|
|
229
229
|
return { typeName: "GraphQLJSONObject", isArray, isNullable };
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
//
|
|
232
|
+
// Keyword types → GraphQL scalars
|
|
233
233
|
const scalar = scalarFromTsKeyword(targetTypeNode.type);
|
|
234
234
|
if (scalar) {
|
|
235
235
|
return { typeName: scalar, isArray, isNullable };
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
//
|
|
238
|
+
// Identifier (class/custom type)
|
|
239
239
|
const ident = getIdentifierName(targetTypeNode);
|
|
240
240
|
if (ident) {
|
|
241
241
|
return { typeName: ident, isArray, isNullable };
|
|
@@ -313,68 +313,64 @@ export default createRule<
|
|
|
313
313
|
? `() => [${typeName}]`
|
|
314
314
|
: `() => ${typeName}`;
|
|
315
315
|
|
|
316
|
-
//
|
|
316
|
+
// Extract existing config options (except nullable)
|
|
317
317
|
const callExpr = fieldDecorator.expression;
|
|
318
318
|
const existingOptions: string[] = [];
|
|
319
319
|
|
|
320
|
-
//
|
|
320
|
+
// Check if there is an existing config object
|
|
321
321
|
let optionsArg: TSESTree.ObjectExpression | null = null;
|
|
322
322
|
if (
|
|
323
323
|
callExpr.type === AST_NODE_TYPES.CallExpression &&
|
|
324
324
|
callExpr.arguments.length > 0
|
|
325
325
|
) {
|
|
326
|
-
//
|
|
327
|
-
if (
|
|
328
|
-
callExpr.arguments[0] &&
|
|
329
|
-
callExpr.arguments[0].type === AST_NODE_TYPES.ObjectExpression
|
|
330
|
-
) {
|
|
326
|
+
// If the first argument is an object expression (no type function)
|
|
327
|
+
if (callExpr.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression) {
|
|
331
328
|
optionsArg = callExpr.arguments[0];
|
|
332
329
|
}
|
|
333
|
-
//
|
|
330
|
+
// If the second argument is an object expression (has type function)
|
|
334
331
|
else if (
|
|
335
|
-
callExpr.arguments[1]
|
|
336
|
-
callExpr.arguments[1].type === AST_NODE_TYPES.ObjectExpression
|
|
332
|
+
callExpr.arguments[1]?.type === AST_NODE_TYPES.ObjectExpression
|
|
337
333
|
) {
|
|
338
334
|
optionsArg = callExpr.arguments[1];
|
|
339
335
|
}
|
|
340
336
|
}
|
|
341
337
|
|
|
342
|
-
//
|
|
338
|
+
// Extract existing options, while handling nullable
|
|
343
339
|
let hasNullableProperty = false;
|
|
344
340
|
if (optionsArg) {
|
|
345
341
|
optionsArg.properties.forEach((prop: TSESTree.ObjectLiteralElement) => {
|
|
346
|
-
//
|
|
342
|
+
// Handle spread elements (SpreadElement)
|
|
347
343
|
if (prop.type === AST_NODE_TYPES.SpreadElement) {
|
|
348
344
|
const propText = source.getText(prop);
|
|
349
345
|
existingOptions.push(propText);
|
|
350
346
|
return;
|
|
351
347
|
}
|
|
352
|
-
//
|
|
348
|
+
// Handle regular properties (Property)
|
|
353
349
|
if (
|
|
354
350
|
prop.key.type === AST_NODE_TYPES.Identifier &&
|
|
355
351
|
prop.key.name === "nullable"
|
|
356
352
|
) {
|
|
357
353
|
hasNullableProperty = true;
|
|
358
|
-
//
|
|
354
|
+
// Decide whether to keep or update nullable based on type
|
|
359
355
|
if (info.isNullable) {
|
|
360
356
|
existingOptions.push("nullable: true");
|
|
361
357
|
}
|
|
362
|
-
//
|
|
358
|
+
// If nullable is not needed, skip (don't keep)
|
|
363
359
|
} else {
|
|
364
|
-
//
|
|
360
|
+
// Keep other existing config options
|
|
365
361
|
const propText = source.getText(prop);
|
|
366
362
|
existingOptions.push(propText);
|
|
367
363
|
}
|
|
368
364
|
});
|
|
369
365
|
}
|
|
370
366
|
|
|
371
|
-
//
|
|
367
|
+
// If nullable is needed but not in original config, prepend it
|
|
372
368
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
373
369
|
if (info.isNullable && !hasNullableProperty) {
|
|
374
370
|
existingOptions.unshift("nullable: true");
|
|
375
371
|
}
|
|
376
372
|
|
|
377
|
-
//
|
|
373
|
+
// Build the new decorator text
|
|
378
374
|
const optionsExpr =
|
|
379
375
|
existingOptions.length > 0 ? `, { ${existingOptions.join(", ")} }` : "";
|
|
380
376
|
const newDecoratorText = `@Field(${typeExpr}${optionsExpr})`;
|
|
@@ -422,7 +418,7 @@ export default createRule<
|
|
|
422
418
|
ManyToMany: "remove",
|
|
423
419
|
};
|
|
424
420
|
|
|
425
|
-
//
|
|
421
|
+
// Check if the property has a configured decorator
|
|
426
422
|
let foundDecoratorName: string | null = null;
|
|
427
423
|
let foundBehavior: DecoratorBehavior | null = null;
|
|
428
424
|
|
|
@@ -440,12 +436,12 @@ export default createRule<
|
|
|
440
436
|
}
|
|
441
437
|
}
|
|
442
438
|
|
|
443
|
-
//
|
|
439
|
+
// If behavior is "ignore", skip directly
|
|
444
440
|
if (foundBehavior === "ignore") return;
|
|
445
441
|
|
|
446
442
|
const fieldDecorator = getPropertyDecorator(member, "Field");
|
|
447
443
|
|
|
448
|
-
//
|
|
444
|
+
// If behavior is "remove" and has @Field, remove @Field
|
|
449
445
|
if (foundBehavior === "remove" && fieldDecorator) {
|
|
450
446
|
context.report({
|
|
451
447
|
node: fieldDecorator,
|
|
@@ -454,11 +450,11 @@ export default createRule<
|
|
|
454
450
|
decoratorName: foundDecoratorName ?? "unknown",
|
|
455
451
|
},
|
|
456
452
|
fix: (fixer) => {
|
|
457
|
-
//
|
|
453
|
+
// Remove the entire decorator line (including newline)
|
|
458
454
|
const decoratorStart = fieldDecorator.range[0];
|
|
459
455
|
const decoratorEnd = fieldDecorator.range[1];
|
|
460
456
|
|
|
461
|
-
//
|
|
457
|
+
// Find the newline and whitespace after the decorator
|
|
462
458
|
const textAfter = source.text.slice(
|
|
463
459
|
decoratorEnd,
|
|
464
460
|
decoratorEnd + 10,
|
|
@@ -474,13 +470,13 @@ export default createRule<
|
|
|
474
470
|
return;
|
|
475
471
|
}
|
|
476
472
|
|
|
477
|
-
//
|
|
473
|
+
// If behavior is "remove" but no @Field, skip
|
|
478
474
|
if (foundBehavior === "remove") return;
|
|
479
475
|
|
|
480
476
|
const typeInfo = computeTypeInfo(member);
|
|
481
477
|
if (!typeInfo?.typeName) return;
|
|
482
478
|
|
|
483
|
-
//
|
|
479
|
+
// If there is no @Field decorator, add one
|
|
484
480
|
if (!fieldDecorator) {
|
|
485
481
|
context.report({
|
|
486
482
|
node: member,
|
|
@@ -493,7 +489,7 @@ export default createRule<
|
|
|
493
489
|
return;
|
|
494
490
|
}
|
|
495
491
|
|
|
496
|
-
//
|
|
492
|
+
// If there is an existing ArrowFunction but it doesn't match expectations, fix it
|
|
497
493
|
const callExpr = fieldDecorator.expression;
|
|
498
494
|
if (callExpr.type !== AST_NODE_TYPES.CallExpression) return;
|
|
499
495
|
|
|
@@ -525,9 +521,9 @@ export default createRule<
|
|
|
525
521
|
},
|
|
526
522
|
);
|
|
527
523
|
|
|
528
|
-
//
|
|
524
|
+
// For number types, both Int and Float are valid
|
|
529
525
|
const isNumberType = typeInfo.typeName === "Float";
|
|
530
|
-
const actualTypeText = calleeText.replace(/^\[|\]$/g, ""); //
|
|
526
|
+
const actualTypeText = calleeText.replace(/^\[|\]$/g, ""); // Remove array brackets
|
|
531
527
|
const isValidNumberType =
|
|
532
528
|
isNumberType &&
|
|
533
529
|
(actualTypeText === "Int" || actualTypeText === "Float");
|