11import { createRule } from './tsUtils' ;
22import {
3- TSESTree ,
43 AST_NODE_TYPES ,
4+ TSESTree ,
55} from '@typescript-eslint/experimental-utils' ;
66
7- const isIdentifier = (
8- callee : TSESTree . LeftHandSideExpression ,
9- ) : callee is TSESTree . Identifier => ! ! callee ;
7+ interface JestFunctionIdentifier extends TSESTree . Identifier {
8+ name : 'it' | 'test' | 'describe' ;
9+ }
10+
11+ interface TestFunctionCallExpression extends TSESTree . CallExpression {
12+ callee : JestFunctionIdentifier ;
13+ }
14+
15+ type ArgumentLiteral = TSESTree . Literal | TSESTree . TemplateLiteral ;
16+
17+ interface FirstArgumentStringCallExpression extends TSESTree . CallExpression {
18+ arguments : [ ArgumentLiteral ] ;
19+ }
20+
21+ type CallExpressionWithCorrectCalleeAndArguments = TestFunctionCallExpression &
22+ FirstArgumentStringCallExpression ;
1023
1124const isItTestOrDescribeFunction = (
12- callee : TSESTree . LeftHandSideExpression ,
13- ) : callee is TSESTree . Identifier => {
14- if ( ! isIdentifier ( callee ) ) {
25+ node : TSESTree . CallExpression ,
26+ ) : node is TestFunctionCallExpression => {
27+ const { callee } = node ;
28+
29+ if ( callee . type !== AST_NODE_TYPES . Identifier ) {
1530 return false ;
1631 }
1732
18- return (
19- callee . name === 'it' || callee . name === 'test' || callee . name === 'describe'
20- ) ;
33+ const { name } = callee ;
34+
35+ return name === 'it' || name === 'test' || name === 'describe' ;
2136} ;
2237
23- const isItDescription = (
24- expression ?: TSESTree . Expression ,
25- ) : expression is TSESTree . Literal | TSESTree . TemplateLiteral =>
26- ! ! expression &&
27- ( expression . type === AST_NODE_TYPES . Literal ||
28- expression . type === AST_NODE_TYPES . TemplateLiteral ) ;
38+ const hasStringAsFirstArgument = (
39+ node : TSESTree . CallExpression ,
40+ ) : node is FirstArgumentStringCallExpression =>
41+ node . arguments &&
42+ node . arguments [ 0 ] &&
43+ ( node . arguments [ 0 ] . type === AST_NODE_TYPES . Literal ||
44+ node . arguments [ 0 ] . type === AST_NODE_TYPES . TemplateLiteral ) ;
45+
46+ const isJestFunctionWithLiteralArg = (
47+ node : TSESTree . CallExpression ,
48+ ) : node is CallExpressionWithCorrectCalleeAndArguments =>
49+ isItTestOrDescribeFunction ( node ) && hasStringAsFirstArgument ( node ) ;
2950
30- const testDescription = (
31- firstArgument : TSESTree . Literal | TSESTree . TemplateLiteral ,
32- ) => {
33- if ( firstArgument . type === AST_NODE_TYPES . Literal ) {
34- return firstArgument . value as string ;
51+ const testDescription = ( argument : ArgumentLiteral ) : string | null => {
52+ if ( argument . type === AST_NODE_TYPES . Literal ) {
53+ const { value } = argument ;
54+
55+ if ( typeof value === 'string' ) {
56+ return value ;
57+ }
58+ return null ;
3559 }
3660
37- return firstArgument . quasis [ 0 ] . value . raw ;
61+ return argument . quasis [ 0 ] . value . raw ;
3862} ;
3963
40- const descriptionBeginsWithLowerCase = (
41- node : TSESTree . CallExpression ,
42- ) : string | false => {
43- const {
44- arguments : [ firstArgument ] ,
45- callee,
46- } = node ;
47- if ( isItTestOrDescribeFunction ( callee ) && isItDescription ( firstArgument ) ) {
48- const description = testDescription ( firstArgument ) ;
49- if ( ! description [ 0 ] ) {
50- return false ;
51- }
64+ const jestFunctionName = (
65+ node : CallExpressionWithCorrectCalleeAndArguments ,
66+ ) => {
67+ const description = testDescription ( node . arguments [ 0 ] ) ;
68+ if ( description === null ) {
69+ return null ;
70+ }
5271
53- if ( description [ 0 ] !== description [ 0 ] . toLowerCase ( ) ) {
54- return callee . name ;
55- }
72+ const firstCharacter = description . charAt ( 0 ) ;
73+
74+ if ( ! firstCharacter ) {
75+ return null ;
76+ }
77+
78+ if ( firstCharacter !== firstCharacter . toLowerCase ( ) ) {
79+ return node . callee . name ;
5680 }
57- return false ;
81+
82+ return null ;
5883} ;
5984
6085export default createRule ( {
@@ -81,7 +106,9 @@ export default createRule({
81106 } ,
82107 ] ,
83108 } as const ,
84- defaultOptions : [ { ignore : [ ] } as { ignore : readonly string [ ] } ] ,
109+ defaultOptions : [
110+ { ignore : [ ] } as { ignore : readonly ( JestFunctionIdentifier [ 'name' ] ) [ ] } ,
111+ ] ,
85112 create ( context , [ { ignore } ] ) {
86113 const ignoredFunctionNames = ignore . reduce <
87114 Record < string , true | undefined >
@@ -90,12 +117,16 @@ export default createRule({
90117 return accumulator ;
91118 } , Object . create ( null ) ) ;
92119
93- const isIgnoredFunctionName = ( node : TSESTree . CallExpression ) =>
94- ignoredFunctionNames [ ( node . callee as TSESTree . Identifier ) . name ] ;
120+ const isIgnoredFunctionName = (
121+ node : CallExpressionWithCorrectCalleeAndArguments ,
122+ ) => ignoredFunctionNames [ node . callee . name ] ;
95123
96124 return {
97125 CallExpression ( node ) {
98- const erroneousMethod = descriptionBeginsWithLowerCase ( node ) ;
126+ if ( ! isJestFunctionWithLiteralArg ( node ) ) {
127+ return ;
128+ }
129+ const erroneousMethod = jestFunctionName ( node ) ;
99130
100131 if ( erroneousMethod && ! isIgnoredFunctionName ( node ) ) {
101132 context . report ( {
@@ -104,10 +135,8 @@ export default createRule({
104135 node,
105136 fix ( fixer ) {
106137 const [ firstArg ] = node . arguments ;
107- // guaranteed by descriptionBeginsWithLowerCase
108- const description = testDescription ( firstArg as
109- | TSESTree . Literal
110- | TSESTree . TemplateLiteral ) ;
138+ // guaranteed by jestFunctionName
139+ const description = testDescription ( firstArg ) ! ;
111140
112141 const rangeIgnoringQuotes : [ number , number ] = [
113142 firstArg . range [ 0 ] + 1 ,
0 commit comments