1
+ /* eslint-env jest */
1
2
import { readFileSync } from 'fs' ;
2
3
import { resolve } from 'path' ;
3
- import { RuleTester , AST , Linter } from 'eslint' ;
4
+ import { RuleTester , AST , Linter , Rule } from 'eslint' ;
4
5
import { ASTKindToNode } from 'graphql' ;
5
6
import { codeFrameColumns } from '@babel/code-frame' ;
6
7
import { GraphQLESTreeNode } from './estree-parser' ;
@@ -35,6 +36,12 @@ function printCode(code: string): string {
35
36
) ;
36
37
}
37
38
39
+ // A simple version of `SourceCodeFixer.applyFixes`
40
+ // https://github.com/eslint/eslint/issues/14936#issuecomment-906746754
41
+ function applyFix ( code : string , fix : Rule . Fix ) : string {
42
+ return [ code . slice ( 0 , fix . range [ 0 ] ) , fix . text , code . slice ( fix . range [ 1 ] ) ] . join ( '' ) ;
43
+ }
44
+
38
45
export class GraphQLRuleTester extends RuleTester {
39
46
config : {
40
47
parser : string ;
@@ -97,37 +104,73 @@ export class GraphQLRuleTester extends RuleTester {
97
104
const hasOnlyTest = tests . invalid . some ( t => t . only ) ;
98
105
99
106
for ( const testCase of tests . invalid ) {
100
- const { only, code , filename } = testCase ;
107
+ const { only, filename , options } = testCase ;
101
108
if ( hasOnlyTest && ! only ) {
102
109
continue ;
103
110
}
104
111
112
+ const code = removeTrailingBlankLines ( testCase . code ) ;
105
113
const verifyConfig = getVerifyConfig ( name , this . config , testCase ) ;
106
114
defineParser ( linter , verifyConfig . parser ) ;
107
115
108
116
const messages = linter . verify ( code , verifyConfig , { filename } ) ;
109
117
const messageForSnapshot : string [ ] = [ ] ;
118
+ const hasMultipleMessages = messages . length > 1 ;
119
+ if ( hasMultipleMessages ) {
120
+ messageForSnapshot . push ( 'Code' , indentCode ( printCode ( code ) ) ) ;
121
+ }
122
+ if ( options ) {
123
+ const opts = JSON . stringify ( options , null , 2 ) . slice ( 1 , - 1 ) ;
124
+ messageForSnapshot . push ( '⚙️ Options' , indentCode ( removeTrailingBlankLines ( opts ) , 2 ) ) ;
125
+ }
126
+
110
127
for ( const [ index , message ] of messages . entries ( ) ) {
111
128
if ( message . fatal ) {
112
129
throw new Error ( message . message ) ;
113
130
}
114
131
115
- messageForSnapshot . push ( `❌ Error ${ index + 1 } /${ messages . length } ` , visualizeEslintMessage ( code , message ) ) ;
132
+ const codeWithMessage = visualizeEslintMessage ( code , message , hasMultipleMessages ? 1 : undefined ) ;
133
+ messageForSnapshot . push ( printWithIndex ( '❌ Error' , index , messages . length ) , indentCode ( codeWithMessage ) ) ;
134
+
135
+ const { suggestions } = message ;
116
136
137
+ // Don't print suggestions in snapshots for too big codes
138
+ if ( suggestions && ( code . match ( / \n / g) || '' ) . length < 1000 ) {
139
+ for ( const [ i , suggestion ] of message . suggestions . entries ( ) ) {
140
+ const output = applyFix ( code , suggestion . fix ) ;
141
+ const title = printWithIndex ( '💡 Suggestion' , i , suggestions . length , suggestion . desc ) ;
142
+ messageForSnapshot . push ( title , indentCode ( printCode ( output ) , 2 ) ) ;
143
+ }
144
+ }
117
145
}
146
+
118
147
if ( rule . meta . fixable ) {
119
148
const { fixed, output } = linter . verifyAndFix ( code , verifyConfig , { filename } ) ;
120
149
if ( fixed ) {
121
- messageForSnapshot . push ( '🔧 Autofix output' , indentCode ( printCode ( output ) , 2 ) ) ;
150
+ messageForSnapshot . push ( '🔧 Autofix output' , indentCode ( codeFrameColumns ( output , { } as any ) ) ) ;
122
151
}
123
152
}
124
153
expect ( messageForSnapshot . join ( '\n\n' ) ) . toMatchSnapshot ( ) ;
125
154
}
126
155
}
127
156
}
128
157
158
+ function removeTrailingBlankLines ( text : string ) : string {
159
+ return text . replace ( / ^ \s * \n / gm, '' ) . trimEnd ( ) ;
160
+ }
161
+
162
+ function printWithIndex ( title : string , index : number , total : number , description ?: string ) : string {
163
+ if ( total > 1 ) {
164
+ title += ` ${ index + 1 } /${ total } ` ;
165
+ }
166
+ if ( description ) {
167
+ title += `: ${ description } ` ;
168
+ }
169
+ return title ;
170
+ }
171
+
129
172
function getVerifyConfig ( ruleId : string , testerConfig , testCase ) {
130
- const { options , parserOptions , parser = testerConfig . parser } = testCase ;
173
+ const { parser = testerConfig . parser , parserOptions , options } = testCase ;
131
174
132
175
return {
133
176
...testerConfig ,
@@ -137,7 +180,7 @@ function getVerifyConfig(ruleId: string, testerConfig, testCase) {
137
180
...parserOptions ,
138
181
} ,
139
182
rules : {
140
- [ ruleId ] : [ 'error' , ...( Array . isArray ( options ) ? options : [ ] ) ] ,
183
+ [ ruleId ] : Array . isArray ( options ) ? [ 'error' , ...options ] : 'error' ,
141
184
} ,
142
185
} ;
143
186
}
@@ -159,7 +202,11 @@ function defineParser(linter: Linter, parser: string): void {
159
202
}
160
203
}
161
204
162
- function visualizeEslintMessage ( text : string , result : Linter . LintMessage ) : string {
205
+ function visualizeEslintMessage (
206
+ text : string ,
207
+ result : Linter . LintMessage ,
208
+ linesOffset = Number . POSITIVE_INFINITY
209
+ ) : string {
163
210
const { line, column, endLine, endColumn, message } = result ;
164
211
const location : Partial < AST . SourceLocation > = {
165
212
start : {
@@ -176,8 +223,8 @@ function visualizeEslintMessage(text: string, result: Linter.LintMessage): strin
176
223
}
177
224
178
225
return codeFrameColumns ( text , location as AST . SourceLocation , {
179
- linesAbove : Number . POSITIVE_INFINITY ,
180
- linesBelow : Number . POSITIVE_INFINITY ,
226
+ linesAbove : linesOffset ,
227
+ linesBelow : linesOffset ,
181
228
message,
182
229
} ) ;
183
230
}
0 commit comments