11import Ajv from 'ajv' ;
2- import { Linter } from 'eslint' ;
2+ import { ESLint , Linter } from 'eslint' ;
33import { z , ZodType } from 'zod' ;
4- import { GraphQLESLintRule , parseForESLint , rules } from '@graphql-eslint/eslint-plugin' ;
4+ import { GraphQLESLintRule , parser , rules } from '@graphql-eslint/eslint-plugin' ;
55import { RELEVANT_RULES } from './rules' ;
66
77const ajv = new Ajv ( {
@@ -12,24 +12,23 @@ const ajv = new Ajv({
1212 allowMatchingProperties : true ,
1313} ) ;
1414const linter = new Linter ( ) ;
15- linter . defineParser ( '@graphql-eslint/eslint-plugin' , { parseForESLint } ) ;
1615
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+ ] ) ;
2221
23- type RulemapValidationType = {
22+ type RuleMapValidationType = {
2423 [ RuleKey in keyof typeof rules ] : ZodType ;
2524} ;
2625
2726export function normalizeAjvSchema (
28- schema : GraphQLESLintRule [ 'meta' ] [ 'schema' ] ,
29- ) : GraphQLESLintRule [ 'meta' ] [ 'schema' ] {
27+ schema : NonNullable < GraphQLESLintRule [ 'meta' ] > [ 'schema' ] ,
28+ ) : NonNullable < GraphQLESLintRule [ 'meta' ] > [ 'schema' ] {
3029 if ( Array . isArray ( schema ) ) {
3130 if ( schema . length === 0 ) {
32- return null ;
31+ return ;
3332 }
3433
3534 return {
@@ -40,44 +39,48 @@ export function normalizeAjvSchema(
4039 } ;
4140 }
4241
43- return schema || null ;
42+ return schema ;
4443}
4544
4645export function createInputValidationSchema ( ) {
4746 return z
4847 . object (
4948 RELEVANT_RULES . reduce ( ( acc , [ name , rule ] ) => {
50- const schema = normalizeAjvSchema ( rule . meta . schema ) ;
49+ const schema = normalizeAjvSchema ( rule . meta ! . schema ) ;
5150 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+ ] ) ;
5275
5376 return {
5477 ...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 ,
7982 } ;
80- } , { } as RulemapValidationType ) ,
83+ } , { } as RuleMapValidationType ) ,
8184 )
8285 . required ( )
8386 . partial ( )
@@ -86,18 +89,68 @@ export function createInputValidationSchema() {
8689
8790export type PolicyConfigurationObject = z . infer < ReturnType < typeof createInputValidationSchema > > ;
8891
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+
89120export async function schemaPolicyCheck ( input : {
90121 source : string ;
91122 schema : string ;
92123 policy : PolicyConfigurationObject ;
93124} ) {
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 (
95133 input . source ,
96134 {
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 ,
100145 } ,
101146 'schema.graphql' ,
102147 ) ;
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+ } ) ;
103156}
0 commit comments