1
1
import Ajv from 'ajv' ;
2
- import { Linter } from 'eslint' ;
2
+ import { ESLint , Linter } from 'eslint' ;
3
3
import { z , ZodType } from 'zod' ;
4
- import { GraphQLESLintRule , parseForESLint , rules } from '@graphql-eslint/eslint-plugin' ;
4
+ import { GraphQLESLintRule , parser , rules } from '@graphql-eslint/eslint-plugin' ;
5
5
import { RELEVANT_RULES } from './rules' ;
6
6
7
7
const ajv = new Ajv ( {
@@ -12,24 +12,23 @@ const ajv = new Ajv({
12
12
allowMatchingProperties : true ,
13
13
} ) ;
14
14
const linter = new Linter ( ) ;
15
- linter . defineParser ( '@graphql-eslint/eslint-plugin' , { parseForESLint } ) ;
16
15
17
- for ( const [ ruleId , rule ] of Object . entries ( rules ) ) {
18
- linter . defineRule ( ruleId , rule as any ) ;
19
- }
20
-
21
- const RULE_LEVEL = z . union ( [ z . number ( ) . min ( 0 ) . max ( 2 ) , z . enum ( [ 'off' , 'warn' , 'error' ] ) ] ) ;
16
+ const RULE_LEVEL = z . union ( [
17
+ //
18
+ z . number ( ) . min ( 0 ) . max ( 2 ) ,
19
+ z . enum ( [ 'off' , 'warn' , 'error' ] ) ,
20
+ ] ) ;
22
21
23
- type RulemapValidationType = {
22
+ type RuleMapValidationType = {
24
23
[ RuleKey in keyof typeof rules ] : ZodType ;
25
24
} ;
26
25
27
26
export function normalizeAjvSchema (
28
- schema : GraphQLESLintRule [ 'meta' ] [ 'schema' ] ,
29
- ) : GraphQLESLintRule [ 'meta' ] [ 'schema' ] {
27
+ schema : NonNullable < GraphQLESLintRule [ 'meta' ] > [ 'schema' ] ,
28
+ ) : NonNullable < GraphQLESLintRule [ 'meta' ] > [ 'schema' ] {
30
29
if ( Array . isArray ( schema ) ) {
31
30
if ( schema . length === 0 ) {
32
- return null ;
31
+ return ;
33
32
}
34
33
35
34
return {
@@ -40,44 +39,48 @@ export function normalizeAjvSchema(
40
39
} ;
41
40
}
42
41
43
- return schema || null ;
42
+ return schema ;
44
43
}
45
44
46
45
export function createInputValidationSchema ( ) {
47
46
return z
48
47
. object (
49
48
RELEVANT_RULES . reduce ( ( acc , [ name , rule ] ) => {
50
- const schema = normalizeAjvSchema ( rule . meta . schema ) ;
49
+ const schema = normalizeAjvSchema ( rule . meta ! . schema ) ;
51
50
const validate = schema ? ajv . compile ( schema ) : null ;
51
+ const cfg = z . union ( [
52
+ z . tuple ( [ RULE_LEVEL ] ) ,
53
+ z . tuple (
54
+ validate
55
+ ? [
56
+ RULE_LEVEL ,
57
+ z . custom ( data => {
58
+ const asArray = ( Array . isArray ( data ) ? data : [ data ] ) . filter ( Boolean ) ;
59
+ const result = validate ( asArray ) ;
60
+
61
+ if ( result ) {
62
+ return true ;
63
+ }
64
+
65
+ throw new Error (
66
+ `Failed to validate rule "${ name } " configuration: ${ ajv . errorsText (
67
+ validate . errors ,
68
+ ) } `,
69
+ ) ;
70
+ } ) ,
71
+ ]
72
+ : [ RULE_LEVEL ] ,
73
+ ) ,
74
+ ] ) ;
52
75
53
76
return {
54
77
...acc ,
55
- [ name ] : z . union ( [
56
- z . tuple ( [ RULE_LEVEL ] ) ,
57
- z . tuple (
58
- validate
59
- ? [
60
- RULE_LEVEL ,
61
- z . custom ( data => {
62
- const asArray = ( Array . isArray ( data ) ? data : [ data ] ) . filter ( Boolean ) ;
63
- const result = validate ( asArray ) ;
64
-
65
- if ( result ) {
66
- return true ;
67
- }
68
-
69
- throw new Error (
70
- `Failed to validate rule "${ name } " configuration: ${ ajv . errorsText (
71
- validate . errors ,
72
- ) } `,
73
- ) ;
74
- } ) ,
75
- ]
76
- : [ RULE_LEVEL ] ,
77
- ) ,
78
- ] ) ,
78
+ // v3 rules were using just a raw name, and v4 rule is using the plugin name as prefix
79
+ // This fix should make sure both will work.
80
+ [ name ] : cfg ,
81
+ [ `@graphql-eslint/${ name } ` ] : cfg ,
79
82
} ;
80
- } , { } as RulemapValidationType ) ,
83
+ } , { } as RuleMapValidationType ) ,
81
84
)
82
85
. required ( )
83
86
. partial ( )
@@ -86,18 +89,68 @@ export function createInputValidationSchema() {
86
89
87
90
export type PolicyConfigurationObject = z . infer < ReturnType < typeof createInputValidationSchema > > ;
88
91
92
+ type NarrowPrefixKeys < T extends Record < string , any > , Prefix extends string > = {
93
+ [ K in keyof T as `${Prefix } ${string & K } `] : T [ K ] ;
94
+ } ;
95
+
96
+ type NormalizedPolicyConfigurationObject = NarrowPrefixKeys <
97
+ PolicyConfigurationObject ,
98
+ '@graphql-eslint/'
99
+ > ;
100
+
101
+ /**
102
+ * Transforms v3/v4 policy to v4, ensuring "@graphql-eslint" prefix is used.
103
+
104
+ * @param inputPolicy v3/v4 policy
105
+ * @returns v4
106
+ */
107
+ function normalizeInputPolicy (
108
+ inputPolicy : PolicyConfigurationObject ,
109
+ ) : NormalizedPolicyConfigurationObject {
110
+ return Object . keys ( inputPolicy ) . reduce ( ( acc , key ) => {
111
+ const normalizedKey = (
112
+ key . startsWith ( '@graphql-eslint/' ) ? key : `@graphql-eslint/${ key } `
113
+ ) as keyof NormalizedPolicyConfigurationObject ;
114
+
115
+ acc [ normalizedKey ] = inputPolicy [ key as keyof PolicyConfigurationObject ] ;
116
+ return acc ;
117
+ } , { } as NormalizedPolicyConfigurationObject ) ;
118
+ }
119
+
89
120
export async function schemaPolicyCheck ( input : {
90
121
source : string ;
91
122
schema : string ;
92
123
policy : PolicyConfigurationObject ;
93
124
} ) {
94
- return linter . verify (
125
+ const normalizedPolicy = normalizeInputPolicy ( input . policy ) ;
126
+
127
+ const rulesMap : Record < string , ESLint . Plugin > = {
128
+ // "any" here is used because we have weird typing issues with v3 -> v4.
129
+ '@graphql-eslint' : { rules : rules as any } ,
130
+ } ;
131
+
132
+ const linterResult = linter . verify (
95
133
input . source ,
96
134
{
97
- parser : '@graphql-eslint/eslint-plugin' ,
98
- parserOptions : { schema : input . schema } ,
99
- rules : input . policy ,
135
+ files : [ '*.graphql' ] ,
136
+ plugins : rulesMap ,
137
+ languageOptions : {
138
+ parser,
139
+ parserOptions : {
140
+ schemaSdl : input . schema ,
141
+ filePath : 'schema.graphql' ,
142
+ } ,
143
+ } ,
144
+ rules : normalizedPolicy ,
100
145
} ,
101
146
'schema.graphql' ,
102
147
) ;
148
+
149
+ return linterResult . map ( r => {
150
+ return {
151
+ ...r ,
152
+ // v4 returns is a bit different from v3, so we need to handle it differently to keep the responses the same.
153
+ ruleId : r . ruleId ?. replace ( '@graphql-eslint/' , '' ) ,
154
+ } ;
155
+ } ) ;
103
156
}
0 commit comments