11import {
2- ESLintUtils ,
3- TSESTree ,
4- ASTUtils ,
5- } from '@typescript-eslint/experimental-utils' ;
6- import {
7- getDocsUrl ,
8- LIBRARY_MODULES ,
9- hasTestingLibraryImportModule ,
10- } from '../utils' ;
11- import {
2+ getDeepestIdentifierNode ,
3+ getPropertyIdentifierNode ,
4+ getReferenceNode ,
125 isObjectPattern ,
136 isProperty ,
14- isCallExpression ,
15- isLiteral ,
16- isMemberExpression ,
17- isImportSpecifier ,
18- isRenderVariableDeclarator ,
197} from '../node-utils' ;
8+ import { createTestingLibraryRule } from '../create-testing-library-rule' ;
9+ import { ASTUtils , TSESTree } from '@typescript-eslint/experimental-utils' ;
2010
2111export const RULE_NAME = 'no-debug' ;
2212export type MessageIds = 'noDebug' ;
23- type Options = [ { renderFunctions ?: string [ ] } ] ;
13+ type Options = [ ] ;
2414
25- export default ESLintUtils . RuleCreator ( getDocsUrl ) < Options , MessageIds > ( {
15+ export default createTestingLibraryRule < Options , MessageIds > ( {
2616 name : RULE_NAME ,
2717 meta : {
2818 type : 'problem' ,
@@ -46,154 +36,67 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
4636 } ,
4737 ] ,
4838 } ,
49- defaultOptions : [
50- {
51- renderFunctions : [ ] ,
52- } ,
53- ] ,
54-
55- create ( context , [ options ] ) {
56- let hasDestructuredDebugStatement = false ;
57- const renderVariableDeclarators : TSESTree . VariableDeclarator [ ] = [ ] ;
58-
59- const { renderFunctions } = options ;
39+ defaultOptions : [ ] ,
6040
61- let hasImportedScreen = false ;
62- let wildcardImportName : string = null ;
41+ create ( context , [ ] , helpers ) {
42+ const suspiciousDebugVariableNames : string [ ] = [ ] ;
43+ const suspiciousReferenceNodes : TSESTree . Identifier [ ] = [ ] ;
6344
6445 return {
6546 VariableDeclarator ( node ) {
66- if ( isRenderVariableDeclarator ( node , [ 'render' , ...renderFunctions ] ) ) {
67- if (
68- isObjectPattern ( node . id ) &&
69- node . id . properties . some (
70- ( property ) =>
71- isProperty ( property ) &&
72- ASTUtils . isIdentifier ( property . key ) &&
73- property . key . name === 'debug'
74- )
75- ) {
76- hasDestructuredDebugStatement = true ;
77- }
78-
79- if ( node . id . type === 'Identifier' ) {
80- renderVariableDeclarators . push ( node ) ;
81- }
82- }
83- } ,
84- [ `VariableDeclarator > CallExpression > Identifier[name="require"]` ] (
85- node : TSESTree . Identifier
86- ) {
87- const { arguments : args } = node . parent as TSESTree . CallExpression ;
47+ const initIdentifierNode = getDeepestIdentifierNode ( node . init ) ;
8848
89- const literalNodeScreenModuleName = args . find (
90- ( args ) =>
91- isLiteral ( args ) &&
92- typeof args . value === 'string' &&
93- LIBRARY_MODULES . includes ( args . value )
94- ) ;
95-
96- if ( ! literalNodeScreenModuleName ) {
49+ if ( ! helpers . isRenderUtil ( initIdentifierNode ) ) {
9750 return ;
9851 }
9952
100- const declaratorNode = node . parent
101- . parent as TSESTree . VariableDeclarator ;
102-
103- hasImportedScreen =
104- isObjectPattern ( declaratorNode . id ) &&
105- declaratorNode . id . properties . some (
106- ( property ) =>
53+ // find debug obtained from render and save their name, like:
54+ // const { debug } = render();
55+ if ( isObjectPattern ( node . id ) ) {
56+ for ( const property of node . id . properties ) {
57+ if (
10758 isProperty ( property ) &&
10859 ASTUtils . isIdentifier ( property . key ) &&
109- property . key . name === 'screen'
110- ) ;
111- } ,
112- // checks if import has shape:
113- // import { screen } from '@testing-library/dom';
114- ImportDeclaration ( node : TSESTree . ImportDeclaration ) {
115- if ( ! hasTestingLibraryImportModule ( node ) ) {
116- return ;
117- }
118-
119- hasImportedScreen = node . specifiers . some (
120- ( s ) => isImportSpecifier ( s ) && s . imported . name === 'screen'
121- ) ;
122- } ,
123- // checks if import has shape:
124- // import * as dtl from '@testing-library/dom';
125- 'ImportDeclaration ImportNamespaceSpecifier' (
126- node : TSESTree . ImportNamespaceSpecifier
127- ) {
128- const importDeclarationNode = node . parent as TSESTree . ImportDeclaration ;
129- if ( ! hasTestingLibraryImportModule ( importDeclarationNode ) ) {
130- return ;
60+ property . key . name === 'debug'
61+ ) {
62+ suspiciousDebugVariableNames . push (
63+ getDeepestIdentifierNode ( property . value ) . name
64+ ) ;
65+ }
66+ }
13167 }
13268
133- wildcardImportName = node . local && node . local . name ;
134- } ,
135- [ `CallExpression > Identifier[name="debug"]` ] ( node : TSESTree . Identifier ) {
136- if ( hasDestructuredDebugStatement ) {
137- context . report ( {
138- node,
139- messageId : 'noDebug' ,
140- } ) ;
69+ // find utils kept from render and save their node, like:
70+ // const utils = render();
71+ if ( ASTUtils . isIdentifier ( node . id ) ) {
72+ suspiciousReferenceNodes . push ( node . id ) ;
14173 }
14274 } ,
143- [ `CallExpression > MemberExpression > Identifier[name="debug"]` ] (
144- node : TSESTree . Identifier
145- ) {
146- const memberExpression = node . parent as TSESTree . MemberExpression ;
147- const identifier = memberExpression . object as TSESTree . Identifier ;
148- const memberExpressionName = identifier . name ;
149- /*
150- check if `debug` used following the pattern:
151-
152- import { screen } from '@testing-library/dom';
153- ...
154- screen.debug();
155- */
156- const isScreenDebugUsed =
157- hasImportedScreen && memberExpressionName === 'screen' ;
158-
159- /*
160- check if `debug` used following the pattern:
161-
162- import * as dtl from '@testing-library/dom';
163- ...
164- dtl.debug();
165- */
166- const isNamespaceDebugUsed =
167- wildcardImportName && memberExpressionName === wildcardImportName ;
75+ CallExpression ( node ) {
76+ const callExpressionIdentifier = getDeepestIdentifierNode ( node ) ;
77+ const referenceNode = getReferenceNode ( node ) ;
78+ const referenceIdentifier = getPropertyIdentifierNode ( referenceNode ) ;
79+
80+ const isDebugUtil = helpers . isDebugUtil ( callExpressionIdentifier ) ;
81+ const isDeclaredDebugVariable = suspiciousDebugVariableNames . includes (
82+ callExpressionIdentifier . name
83+ ) ;
84+ const isChainedReferenceDebug = suspiciousReferenceNodes . some (
85+ ( suspiciousReferenceIdentifier ) => {
86+ return (
87+ callExpressionIdentifier . name === 'debug' &&
88+ suspiciousReferenceIdentifier . name === referenceIdentifier . name
89+ ) ;
90+ }
91+ ) ;
16892
169- if ( isScreenDebugUsed || isNamespaceDebugUsed ) {
93+ if ( isDebugUtil || isDeclaredDebugVariable || isChainedReferenceDebug ) {
17094 context . report ( {
171- node,
95+ node : callExpressionIdentifier ,
17296 messageId : 'noDebug' ,
17397 } ) ;
17498 }
17599 } ,
176- 'Program:exit' ( ) {
177- renderVariableDeclarators . forEach ( ( renderVar ) => {
178- const renderVarReferences = context
179- . getDeclaredVariables ( renderVar ) [ 0 ]
180- . references . slice ( 1 ) ;
181- renderVarReferences . forEach ( ( ref ) => {
182- const parent = ref . identifier . parent ;
183- if (
184- isMemberExpression ( parent ) &&
185- ASTUtils . isIdentifier ( parent . property ) &&
186- parent . property . name === 'debug' &&
187- isCallExpression ( parent . parent )
188- ) {
189- context . report ( {
190- node : parent . property ,
191- messageId : 'noDebug' ,
192- } ) ;
193- }
194- } ) ;
195- } ) ;
196- } ,
197100 } ;
198101 } ,
199102} ) ;
0 commit comments