1
1
'use strict' ;
2
2
const { visitIf} = require ( 'enhance-visitors' ) ;
3
+ const { getStaticValue, isOpeningParenToken, isCommaToken} = require ( 'eslint-utils' ) ;
3
4
const util = require ( '../util' ) ;
4
5
const createAvaRule = require ( '../create-ava-rule' ) ;
5
6
6
7
const expectedNbArguments = {
8
+ assert : {
9
+ min : 1 ,
10
+ max : 2
11
+ } ,
7
12
deepEqual : {
8
13
min : 2 ,
9
14
max : 3
@@ -44,6 +49,10 @@ const expectedNbArguments = {
44
49
min : 1 ,
45
50
max : 2
46
51
} ,
52
+ notThrowsAsync : {
53
+ min : 1 ,
54
+ max : 2
55
+ } ,
47
56
pass : {
48
57
min : 0 ,
49
58
max : 1
@@ -72,6 +81,10 @@ const expectedNbArguments = {
72
81
min : 1 ,
73
82
max : 3
74
83
} ,
84
+ throwsAsync : {
85
+ min : 1 ,
86
+ max : 3
87
+ } ,
75
88
true : {
76
89
min : 1 ,
77
90
max : 2
@@ -86,6 +99,111 @@ const expectedNbArguments = {
86
99
}
87
100
} ;
88
101
102
+ const actualExpectedAssertions = new Set ( [
103
+ 'deepEqual' ,
104
+ 'is' ,
105
+ 'like' ,
106
+ 'not' ,
107
+ 'notDeepEqual' ,
108
+ 'throws' ,
109
+ 'throwsAsync'
110
+ ] ) ;
111
+
112
+ const relationalActualExpectedAssertions = new Set ( [
113
+ 'assert' ,
114
+ 'truthy' ,
115
+ 'falsy' ,
116
+ 'true' ,
117
+ 'false'
118
+ ] ) ;
119
+
120
+ const comparisonOperators = new Map ( [
121
+ [ '>' , '<' ] ,
122
+ [ '>=' , '<=' ] ,
123
+ [ '==' , '==' ] ,
124
+ [ '===' , '===' ] ,
125
+ [ '!=' , '!=' ] ,
126
+ [ '!==' , '!==' ] ,
127
+ [ '<=' , '>=' ] ,
128
+ [ '<' , '>' ]
129
+ ] ) ;
130
+
131
+ const flipOperator = operator => comparisonOperators . get ( operator ) ;
132
+
133
+ function isStatic ( node ) {
134
+ const staticValue = getStaticValue ( node ) ;
135
+ return staticValue !== null && typeof staticValue . value !== 'function' ;
136
+ }
137
+
138
+ function * sourceRangesOfArguments ( sourceCode , callExpression ) {
139
+ const openingParen = sourceCode . getTokenAfter (
140
+ callExpression . callee ,
141
+ { filter : token => isOpeningParenToken ( token ) }
142
+ ) ;
143
+
144
+ const closingParen = sourceCode . getLastToken ( callExpression ) ;
145
+
146
+ for ( const [ index , argument ] of callExpression . arguments . entries ( ) ) {
147
+ const previousToken = index === 0 ?
148
+ openingParen :
149
+ sourceCode . getTokenBefore (
150
+ argument ,
151
+ { filter : token => isCommaToken ( token ) }
152
+ ) ;
153
+
154
+ const nextToken = index === callExpression . arguments . length - 1 ?
155
+ closingParen :
156
+ sourceCode . getTokenAfter (
157
+ argument ,
158
+ { filter : token => isCommaToken ( token ) }
159
+ ) ;
160
+
161
+ const firstToken = sourceCode . getTokenAfter (
162
+ previousToken ,
163
+ { includeComments : true }
164
+ ) ;
165
+
166
+ const lastToken = sourceCode . getTokenBefore (
167
+ nextToken ,
168
+ { includeComments : true }
169
+ ) ;
170
+
171
+ yield [ firstToken . range [ 0 ] , lastToken . range [ 1 ] ] ;
172
+ }
173
+ }
174
+
175
+ function sourceOfBinaryExpressionComponents ( sourceCode , node ) {
176
+ const { operator, left, right} = node ;
177
+
178
+ const operatorToken = sourceCode . getFirstTokenBetween (
179
+ left ,
180
+ right ,
181
+ { filter : token => token . value === operator }
182
+ ) ;
183
+
184
+ const previousToken = sourceCode . getTokenBefore ( node ) ;
185
+ const nextToken = sourceCode . getTokenAfter ( node ) ;
186
+
187
+ const leftRange = [
188
+ sourceCode . getTokenAfter ( previousToken , { includeComments : true } ) . range [ 0 ] ,
189
+ sourceCode . getTokenBefore ( operatorToken , { includeComments : true } ) . range [ 1 ]
190
+ ] ;
191
+
192
+ const rightRange = [
193
+ sourceCode . getTokenAfter ( operatorToken , { includeComments : true } ) . range [ 0 ] ,
194
+ sourceCode . getTokenBefore ( nextToken , { includeComments : true } ) . range [ 1 ]
195
+ ] ;
196
+
197
+ return [ leftRange , operatorToken , rightRange ] ;
198
+ }
199
+
200
+ function noComments ( sourceCode , ...nodes ) {
201
+ return nodes . every ( node => {
202
+ const { leading, trailing} = sourceCode . getComments ( node ) ;
203
+ return leading . length === 0 && trailing . length === 0 ;
204
+ } ) ;
205
+ }
206
+
89
207
const create = context => {
90
208
const ava = createAvaRule ( ) ;
91
209
const options = context . options [ 0 ] || { } ;
@@ -141,19 +259,102 @@ const create = context => {
141
259
report ( node , `Not enough arguments. Expected at least ${ nArgs . min } .` ) ;
142
260
} else if ( node . arguments . length > nArgs . max ) {
143
261
report ( node , `Too many arguments. Expected at most ${ nArgs . max } .` ) ;
144
- } else if ( enforcesMessage && nArgs . min !== nArgs . max ) {
145
- const hasMessage = gottenArgs === nArgs . max ;
262
+ } else {
263
+ if ( enforcesMessage && nArgs . min !== nArgs . max ) {
264
+ const hasMessage = gottenArgs === nArgs . max ;
146
265
147
- if ( ! hasMessage && shouldHaveMessage ) {
148
- report ( node , 'Expected an assertion message, but found none.' ) ;
149
- } else if ( hasMessage && ! shouldHaveMessage ) {
150
- report ( node , 'Expected no assertion message, but found one.' ) ;
266
+ if ( ! hasMessage && shouldHaveMessage ) {
267
+ report ( node , 'Expected an assertion message, but found none.' ) ;
268
+ } else if ( hasMessage && ! shouldHaveMessage ) {
269
+ report ( node , 'Expected no assertion message, but found one.' ) ;
270
+ }
151
271
}
272
+
273
+ checkArgumentOrder ( { node, assertion : members [ 0 ] , context} ) ;
152
274
}
153
275
} )
154
276
} ) ;
155
277
} ;
156
278
279
+ function checkArgumentOrder ( { node, assertion, context} ) {
280
+ const [ first , second ] = node . arguments ;
281
+ if ( actualExpectedAssertions . has ( assertion ) && second ) {
282
+ const [ leftNode , rightNode ] = [ first , second ] ;
283
+ if ( isStatic ( leftNode ) && ! isStatic ( rightNode ) ) {
284
+ context . report (
285
+ makeOutOfOrder2ArgumentReport ( { node, leftNode, rightNode, context} )
286
+ ) ;
287
+ }
288
+ } else if (
289
+ relationalActualExpectedAssertions . has ( assertion ) &&
290
+ first &&
291
+ first . type === 'BinaryExpression' &&
292
+ comparisonOperators . has ( first . operator )
293
+ ) {
294
+ const [ leftNode , rightNode ] = [ first . left , first . right ] ;
295
+ if ( isStatic ( leftNode ) && ! isStatic ( rightNode ) ) {
296
+ context . report (
297
+ makeOutOfOrder1ArgumentReport ( { node : first , leftNode, rightNode, context} )
298
+ ) ;
299
+ }
300
+ }
301
+ }
302
+
303
+ function makeOutOfOrder2ArgumentReport ( { node, leftNode, rightNode, context} ) {
304
+ const sourceCode = context . getSourceCode ( ) ;
305
+ const [ leftRange , rightRange ] = sourceRangesOfArguments ( sourceCode , node ) ;
306
+ const report = {
307
+ message : 'Expected values should come after actual values.' ,
308
+ loc : {
309
+ start : sourceCode . getLocFromIndex ( leftRange [ 0 ] ) ,
310
+ end : sourceCode . getLocFromIndex ( rightRange [ 1 ] )
311
+ }
312
+ } ;
313
+
314
+ if ( noComments ( sourceCode , leftNode , rightNode ) ) {
315
+ report . fix = fixer => {
316
+ const leftText = sourceCode . getText ( ) . slice ( ...leftRange ) ;
317
+ const rightText = sourceCode . getText ( ) . slice ( ...rightRange ) ;
318
+ return [
319
+ fixer . replaceTextRange ( leftRange , rightText ) ,
320
+ fixer . replaceTextRange ( rightRange , leftText )
321
+ ] ;
322
+ } ;
323
+ }
324
+
325
+ return report ;
326
+ }
327
+
328
+ function makeOutOfOrder1ArgumentReport ( { node, leftNode, rightNode, context} ) {
329
+ const sourceCode = context . getSourceCode ( ) ;
330
+ const [
331
+ leftRange ,
332
+ operatorToken ,
333
+ rightRange
334
+ ] = sourceOfBinaryExpressionComponents ( sourceCode , node ) ;
335
+ const report = {
336
+ message : 'Expected values should come after actual values.' ,
337
+ loc : {
338
+ start : sourceCode . getLocFromIndex ( leftRange [ 0 ] ) ,
339
+ end : sourceCode . getLocFromIndex ( rightRange [ 1 ] )
340
+ }
341
+ } ;
342
+
343
+ if ( noComments ( sourceCode , leftNode , rightNode , node ) ) {
344
+ report . fix = fixer => {
345
+ const leftText = sourceCode . getText ( ) . slice ( ...leftRange ) ;
346
+ const rightText = sourceCode . getText ( ) . slice ( ...rightRange ) ;
347
+ return [
348
+ fixer . replaceTextRange ( leftRange , rightText ) ,
349
+ fixer . replaceText ( operatorToken , flipOperator ( node . operator ) ) ,
350
+ fixer . replaceTextRange ( rightRange , leftText )
351
+ ] ;
352
+ } ;
353
+ }
354
+
355
+ return report ;
356
+ }
357
+
157
358
const schema = [ {
158
359
type : 'object' ,
159
360
properties : {
@@ -170,6 +371,7 @@ const schema = [{
170
371
module . exports = {
171
372
create,
172
373
meta : {
374
+ fixable : 'code' ,
173
375
docs : {
174
376
url : util . getDocsUrl ( __filename )
175
377
} ,
0 commit comments