11import type { Literal } from 'estree' ;
2- import { createRule , parseJestFnCall } from './utils' ;
2+ import { type ParsedJestFnCall , createRule , parseJestFnCall } from './utils' ;
33
4- const createFixerImports = (
5- usesImport : boolean ,
6- functionsToImport : string [ ] ,
7- ) => {
4+ const createFixerImports = ( isModule : boolean , functionsToImport : string [ ] ) => {
85 const allImportsFormatted = functionsToImport . filter ( Boolean ) . join ( ', ' ) ;
96
10- return usesImport
7+ return isModule
118 ? `import { ${ allImportsFormatted } } from '@jest/globals';`
129 : `const { ${ allImportsFormatted } } = require('@jest/globals');` ;
1310} ;
@@ -30,7 +27,7 @@ export default createRule({
3027 defaultOptions : [ ] ,
3128 create ( context ) {
3229 const importedJestFunctions : string [ ] = [ ] ;
33- const usedJestFunctions = new Set < string > ( ) ;
30+ const usedJestFunctions : ParsedJestFnCall [ ] = [ ] ;
3431
3532 return {
3633 CallExpression ( node ) {
@@ -44,138 +41,141 @@ export default createRule({
4441 importedJestFunctions . push ( jestFnCall . name ) ;
4542 }
4643
47- usedJestFunctions . add ( jestFnCall . name ) ;
44+ usedJestFunctions . push ( jestFnCall ) ;
4845 } ,
4946 'Program:exit' ( ) {
50- const jestFunctionsToImport = Array . from ( usedJestFunctions ) . filter (
51- jestFunction => ! importedJestFunctions . includes ( jestFunction ) ,
47+ const jestFunctionsToReport = usedJestFunctions . filter (
48+ jestFunction => ! importedJestFunctions . includes ( jestFunction . name ) ,
5249 ) ;
5350
54- if ( jestFunctionsToImport . length > 0 ) {
55- const node = context . getSourceCode ( ) . ast ;
56- const jestFunctionsToImportFormatted =
57- jestFunctionsToImport . join ( ', ' ) ;
58-
59- context . report ( {
60- node,
61- messageId : 'preferImportingJestGlobal' ,
62- data : { jestFunctions : jestFunctionsToImportFormatted } ,
63- fix ( fixer ) {
64- const sourceCode = context . getSourceCode ( ) ;
65- const usesImport = sourceCode . ast . body . some (
66- node => node . type === 'ImportDeclaration' ,
67- ) ;
68- const [ firstNode ] = sourceCode . ast . body ;
51+ if ( ! jestFunctionsToReport . length ) {
52+ return ;
53+ }
54+ const jestFunctionsToImport = jestFunctionsToReport . map (
55+ jestFunction => jestFunction . name ,
56+ ) ;
57+ const reportingNode = jestFunctionsToReport [ 0 ] . head . node ;
6958
70- let firstNodeValue ;
59+ const jestFunctionsToImportFormatted = jestFunctionsToImport . join ( ', ' ) ;
7160
72- if ( firstNode . type === 'ExpressionStatement' ) {
73- const firstExpression = firstNode . expression as Literal ;
74- const { value } = firstExpression ;
61+ const isModule = context . parserOptions . sourceType === 'module' ;
7562
76- firstNodeValue = value ;
77- }
63+ context . report ( {
64+ node : reportingNode ,
65+ messageId : 'preferImportingJestGlobal' ,
66+ data : { jestFunctions : jestFunctionsToImportFormatted } ,
67+ fix ( fixer ) {
68+ const sourceCode = context . getSourceCode ( ) ;
69+ const [ firstNode ] = sourceCode . ast . body ;
7870
79- const useStrictDirectiveExists =
80- firstNode . type === 'ExpressionStatement' &&
81- firstNodeValue === 'use strict' ;
71+ let firstNodeValue ;
8272
83- if ( useStrictDirectiveExists ) {
84- return fixer . insertTextAfter (
85- firstNode ,
86- `\n${ createFixerImports ( usesImport , jestFunctionsToImport ) } ` ,
87- ) ;
88- }
73+ if ( firstNode . type === 'ExpressionStatement' ) {
74+ const firstExpression = firstNode . expression as Literal ;
75+ const { value } = firstExpression ;
8976
90- const importNode = sourceCode . ast . body . find (
91- node =>
92- node . type === 'ImportDeclaration' &&
93- node . source . value === '@jest/globals' ,
94- ) ;
77+ firstNodeValue = value ;
78+ }
9579
96- if ( importNode && importNode . type === 'ImportDeclaration' ) {
97- const existingImports = importNode . specifiers . map ( specifier => {
98- /* istanbul ignore else */
99- if ( specifier . type === 'ImportSpecifier' ) {
100- return specifier . imported ?. name ;
101- }
102-
103- // istanbul ignore next
104- return null ;
105- } ) ;
106- const allImports = [
107- ...new Set ( [
108- ...existingImports . filter (
109- ( imp ) : imp is string => imp !== null ,
110- ) ,
111- ...jestFunctionsToImport ,
112- ] ) ,
113- ] ;
114-
115- return fixer . replaceText (
116- importNode ,
117- createFixerImports ( usesImport , allImports ) ,
118- ) ;
119- }
120-
121- const requireNode = sourceCode . ast . body . find (
122- node =>
123- node . type === 'VariableDeclaration' &&
124- node . declarations . some (
125- declaration =>
126- declaration . init &&
127- ( declaration . init as any ) . callee &&
128- ( declaration . init as any ) . callee . name === 'require' &&
129- ( declaration . init as any ) . arguments ?. [ 0 ] ?. type ===
130- 'Literal' &&
131- ( declaration . init as any ) . arguments ?. [ 0 ] ?. value ===
132- '@jest/globals' ,
133- ) ,
80+ const useStrictDirectiveExists =
81+ firstNode . type === 'ExpressionStatement' &&
82+ firstNodeValue === 'use strict' ;
83+
84+ if ( useStrictDirectiveExists ) {
85+ return fixer . insertTextAfter (
86+ firstNode ,
87+ `\n${ createFixerImports ( isModule , jestFunctionsToImport ) } ` ,
13488 ) ;
89+ }
90+
91+ const importNode = sourceCode . ast . body . find (
92+ node =>
93+ node . type === 'ImportDeclaration' &&
94+ node . source . value === '@jest/globals' ,
95+ ) ;
96+
97+ if ( importNode && importNode . type === 'ImportDeclaration' ) {
98+ const existingImports = importNode . specifiers . map ( specifier => {
99+ /* istanbul ignore else */
100+ if ( specifier . type === 'ImportSpecifier' ) {
101+ return specifier . imported ?. name ;
102+ }
103+
104+ // istanbul ignore next
105+ return null ;
106+ } ) ;
107+ const allImports = [
108+ ...new Set ( [
109+ ...existingImports . filter (
110+ ( imp ) : imp is string => imp !== null ,
111+ ) ,
112+ ...jestFunctionsToImport ,
113+ ] ) ,
114+ ] ;
135115
136- if ( requireNode && requireNode . type === 'VariableDeclaration' ) {
137- const existingImports =
138- requireNode . declarations [ 0 ] ?. id . type === 'ObjectPattern'
139- ? requireNode . declarations [ 0 ] ?. id . properties ?. map (
140- property => {
116+ return fixer . replaceText (
117+ importNode ,
118+ createFixerImports ( isModule , allImports ) ,
119+ ) ;
120+ }
121+
122+ const requireNode = sourceCode . ast . body . find (
123+ node =>
124+ node . type === 'VariableDeclaration' &&
125+ node . declarations . some (
126+ declaration =>
127+ declaration . init &&
128+ ( declaration . init as any ) . callee &&
129+ ( declaration . init as any ) . callee . name === 'require' &&
130+ ( declaration . init as any ) . arguments ?. [ 0 ] ?. type ===
131+ 'Literal' &&
132+ ( declaration . init as any ) . arguments ?. [ 0 ] ?. value ===
133+ '@jest/globals' ,
134+ ) ,
135+ ) ;
136+
137+ if ( requireNode && requireNode . type === 'VariableDeclaration' ) {
138+ const existingImports =
139+ requireNode . declarations [ 0 ] ?. id . type === 'ObjectPattern'
140+ ? requireNode . declarations [ 0 ] ?. id . properties ?. map (
141+ property => {
142+ /* istanbul ignore else */
143+ if ( property . type === 'Property' ) {
141144 /* istanbul ignore else */
142- if ( property . type === 'Property' ) {
143- /* istanbul ignore else */
144- if ( property . key . type === 'Identifier' ) {
145- return property . key . name ;
146- }
145+ if ( property . key . type === 'Identifier' ) {
146+ return property . key . name ;
147147 }
148+ }
149+
150+ // istanbul ignore next
151+ return null ;
152+ } ,
153+ ) ||
154+ // istanbul ignore next
155+ [ ]
156+ : // istanbul ignore next
157+ [ ] ;
158+ const allImports = [
159+ ...new Set ( [
160+ ...existingImports . filter (
161+ ( imp ) : imp is string => imp !== null ,
162+ ) ,
163+ ...jestFunctionsToImport ,
164+ ] ) ,
165+ ] ;
148166
149- // istanbul ignore next
150- return null ;
151- } ,
152- ) ||
153- // istanbul ignore next
154- [ ]
155- : // istanbul ignore next
156- [ ] ;
157- const allImports = [
158- ...new Set ( [
159- ...existingImports . filter (
160- ( imp ) : imp is string => imp !== null ,
161- ) ,
162- ...jestFunctionsToImport ,
163- ] ) ,
164- ] ;
165-
166- return fixer . replaceText (
167- requireNode ,
168- `${ createFixerImports ( usesImport , allImports ) } ` ,
169- ) ;
170- }
171-
172- return fixer . insertTextBefore (
173- node ,
174- `${ createFixerImports ( usesImport , jestFunctionsToImport ) } \n` ,
167+ return fixer . replaceText (
168+ requireNode ,
169+ `${ createFixerImports ( isModule , allImports ) } ` ,
175170 ) ;
176- } ,
177- } ) ;
178- }
171+ }
172+
173+ return fixer . insertTextBefore (
174+ reportingNode ,
175+ `${ createFixerImports ( isModule , jestFunctionsToImport ) } \n` ,
176+ ) ;
177+ } ,
178+ } ) ;
179179 } ,
180180 } ;
181181 } ,
0 commit comments