11'use strict' ;
22const { visitIf} = require ( 'enhance-visitors' ) ;
3+ const { getStaticValue, isOpeningParenToken, isCommaToken} = require ( 'eslint-utils' ) ;
34const util = require ( '../util' ) ;
45const createAvaRule = require ( '../create-ava-rule' ) ;
56
67const expectedNbArguments = {
8+ assert : {
9+ min : 1 ,
10+ max : 2
11+ } ,
712 deepEqual : {
813 min : 2 ,
914 max : 3
@@ -44,6 +49,10 @@ const expectedNbArguments = {
4449 min : 1 ,
4550 max : 2
4651 } ,
52+ notThrowsAsync : {
53+ min : 1 ,
54+ max : 2
55+ } ,
4756 pass : {
4857 min : 0 ,
4958 max : 1
@@ -72,6 +81,10 @@ const expectedNbArguments = {
7281 min : 1 ,
7382 max : 3
7483 } ,
84+ throwsAsync : {
85+ min : 1 ,
86+ max : 3
87+ } ,
7588 true : {
7689 min : 1 ,
7790 max : 2
@@ -86,6 +99,111 @@ const expectedNbArguments = {
8699 }
87100} ;
88101
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+
89207const create = context => {
90208 const ava = createAvaRule ( ) ;
91209 const options = context . options [ 0 ] || { } ;
@@ -141,19 +259,102 @@ const create = context => {
141259 report ( node , `Not enough arguments. Expected at least ${ nArgs . min } .` ) ;
142260 } else if ( node . arguments . length > nArgs . max ) {
143261 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 ;
146265
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+ }
151271 }
272+
273+ checkArgumentOrder ( { node, assertion : members [ 0 ] , context} ) ;
152274 }
153275 } )
154276 } ) ;
155277} ;
156278
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+
157358const schema = [ {
158359 type : 'object' ,
159360 properties : {
@@ -170,6 +371,7 @@ const schema = [{
170371module . exports = {
171372 create,
172373 meta : {
374+ fixable : 'code' ,
173375 docs : {
174376 url : util . getDocsUrl ( __filename )
175377 } ,
0 commit comments