1
- import { RuleTester } from 'eslint' ;
2
1
import { readFileSync } from 'fs' ;
3
- import { ASTKindToNode } from 'graphql' ;
4
2
import { resolve } from 'path' ;
3
+ import { RuleTester , AST , Linter , Rule } from 'eslint' ;
4
+ import { ASTKindToNode } from 'graphql' ;
5
+ import { codeFrameColumns } from '@babel/code-frame' ;
5
6
import { GraphQLESTreeNode } from './estree-parser' ;
6
7
import { GraphQLESLintRule , ParserOptions } from './types' ;
7
8
8
9
export type GraphQLESLintRuleListener < WithTypeInfo extends boolean = false > = {
9
10
[ K in keyof ASTKindToNode ] ?: ( node : GraphQLESTreeNode < ASTKindToNode [ K ] , WithTypeInfo > ) => void ;
10
- } &
11
- Record < string , any > ;
11
+ } & Record < string , any > ;
12
12
13
13
export type GraphQLValidTestCase < Options > = Omit < RuleTester . ValidTestCase , 'options' | 'parserOptions' > & {
14
14
options ?: Options ;
@@ -20,15 +20,22 @@ export type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
20
20
output ?: string | null ;
21
21
} ;
22
22
23
- export class GraphQLRuleTester extends require ( 'eslint' ) . RuleTester {
23
+ export class GraphQLRuleTester extends RuleTester {
24
+ config : {
25
+ parser : string ;
26
+ parserOptions : ParserOptions ;
27
+ } ;
28
+
24
29
constructor ( parserOptions : ParserOptions = { } ) {
25
- super ( {
30
+ const config = {
26
31
parser : require . resolve ( '@graphql-eslint/eslint-plugin' ) ,
27
32
parserOptions : {
28
33
...parserOptions ,
29
34
skipGraphQLConfig : true ,
30
35
} ,
31
- } ) ;
36
+ } ;
37
+ super ( config ) ;
38
+ this . config = config ;
32
39
}
33
40
34
41
fromMockFile ( path : string ) : string {
@@ -43,6 +50,89 @@ export class GraphQLRuleTester extends require('eslint').RuleTester {
43
50
invalid : GraphQLInvalidTestCase < Config > [ ] ;
44
51
}
45
52
) : void {
46
- super . run ( name , rule , tests ) ;
53
+ super . run ( name , rule as Rule . RuleModule , tests ) ;
54
+
55
+ // Skip snapshot testing if `expect` variable is not defined
56
+ if ( typeof expect === 'undefined' ) {
57
+ return ;
58
+ }
59
+
60
+ const linter = new Linter ( ) ;
61
+ linter . defineRule ( name , rule as Rule . RuleModule ) ;
62
+
63
+ for ( const testCase of tests . invalid ) {
64
+ const verifyConfig = getVerifyConfig ( name , this . config , testCase ) ;
65
+ defineParser ( linter , verifyConfig . parser ) ;
66
+
67
+ const { code, filename } = testCase ;
68
+
69
+ const messages = linter . verify ( code , verifyConfig , { filename } ) ;
70
+
71
+ for ( const message of messages ) {
72
+ if ( message . fatal ) {
73
+ throw new Error ( message . message ) ;
74
+ }
75
+
76
+ const messageForSnapshot = visualizeEslintMessage ( code , message ) ;
77
+ // eslint-disable-next-line no-undef
78
+ expect ( messageForSnapshot ) . toMatchSnapshot ( ) ;
79
+ }
80
+ }
47
81
}
48
82
}
83
+
84
+ function getVerifyConfig ( ruleId : string , testerConfig , testCase ) {
85
+ const { options, parserOptions, parser = testerConfig . parser } = testCase ;
86
+
87
+ return {
88
+ ...testerConfig ,
89
+ parser,
90
+ parserOptions : {
91
+ ...testerConfig . parserOptions ,
92
+ ...parserOptions ,
93
+ } ,
94
+ rules : {
95
+ [ ruleId ] : [ 'error' , ...( Array . isArray ( options ) ? options : [ ] ) ] ,
96
+ } ,
97
+ } ;
98
+ }
99
+
100
+ const parsers = new WeakMap ( ) ;
101
+
102
+ function defineParser ( linter : Linter , parser : string ) : void {
103
+ if ( ! parser ) {
104
+ return ;
105
+ }
106
+ if ( ! parsers . has ( linter ) ) {
107
+ parsers . set ( linter , new Set ( ) ) ;
108
+ }
109
+
110
+ const defined = parsers . get ( linter ) ;
111
+ if ( ! defined . has ( parser ) ) {
112
+ defined . add ( parser ) ;
113
+ linter . defineParser ( parser , require ( parser ) ) ;
114
+ }
115
+ }
116
+
117
+ function visualizeEslintMessage ( text : string , result : Linter . LintMessage ) : string {
118
+ const { line, column, endLine, endColumn, message } = result ;
119
+ const location : Partial < AST . SourceLocation > = {
120
+ start : {
121
+ line,
122
+ column,
123
+ } ,
124
+ } ;
125
+
126
+ if ( typeof endLine === 'number' && typeof endColumn === 'number' ) {
127
+ location . end = {
128
+ line : endLine ,
129
+ column : endColumn ,
130
+ } ;
131
+ }
132
+
133
+ return codeFrameColumns ( text , location as AST . SourceLocation , {
134
+ linesAbove : Number . POSITIVE_INFINITY ,
135
+ linesBelow : Number . POSITIVE_INFINITY ,
136
+ message,
137
+ } ) ;
138
+ }
0 commit comments