Skip to content

Commit 9bcf595

Browse files
authored
refactor(refactor no-debug): migrate to v4 (#293)
* refactor(no-debug): use new rule creator * test(no-debug): improve current invalid error assertions * docs(no-debug): fix typo * refactor(no-debug): report debug call expressions with detection helpers * refactor(no-debug): report debug from renders with detection helpers * refactor(no-debug): remove unnecessary checks
1 parent ea62638 commit 9bcf595

File tree

5 files changed

+343
-157
lines changed

5 files changed

+343
-157
lines changed

docs/rules/no-debug.md

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Disallow the use of `debug` (no-debug)
22

3-
Just like `console.log` statements pollutes the browser's output, debug statements also pollutes the tests if one of your team mates forgot to remove it. `debug` statements should be used when you actually want to debug your tests but should not be pushed to the codebase.
3+
Just like `console.log` statements pollutes the browser's output, debug statements also pollutes the tests if one of your teammates forgot to remove it. `debug` statements should be used when you actually want to debug your tests but should not be pushed to the codebase.
44

55
## Rule Details
66

@@ -28,12 +28,6 @@ const { screen } = require('@testing-library/react');
2828
screen.debug();
2929
```
3030

31-
If you use [custom render functions](https://testing-library.com/docs/example-react-redux) then you can set a config option in your `.eslintrc` to look for these.
32-
33-
```
34-
"testing-library/no-debug": ["error", {"renderFunctions":["renderWithRedux", "renderWithRouter"]}],
35-
```
36-
3731
## Further Reading
3832

3933
- [debug API in React Testing Library](https://testing-library.com/docs/react-testing-library/api#debug)

lib/detect-testing-library-utils.ts

+14
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ type IsAsyncUtilFn = (
6969
) => boolean;
7070
type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean;
7171
type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean;
72+
type IsDebugUtilFn = (node: TSESTree.Identifier) => boolean;
7273
type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
7374
type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
7475
type CanReportErrorsFn = () => boolean;
@@ -96,6 +97,7 @@ export interface DetectionHelpers {
9697
isAsyncUtil: IsAsyncUtilFn;
9798
isFireEventMethod: IsFireEventMethodFn;
9899
isRenderUtil: IsRenderUtilFn;
100+
isDebugUtil: IsDebugUtilFn;
99101
isPresenceAssert: IsPresenceAssertFn;
100102
isAbsenceAssert: IsAbsenceAssertFn;
101103
canReportErrors: CanReportErrorsFn;
@@ -406,6 +408,17 @@ export function detectTestingLibraryUtils<
406408
);
407409
};
408410

411+
const isDebugUtil: IsDebugUtilFn = (node) => {
412+
return isTestingLibraryUtil(
413+
node,
414+
(identifierNodeName, originalNodeName) => {
415+
return [identifierNodeName, originalNodeName]
416+
.filter(Boolean)
417+
.includes('debug');
418+
}
419+
);
420+
};
421+
409422
/**
410423
* Determines whether a given MemberExpression node is a presence assert
411424
*
@@ -549,6 +562,7 @@ export function detectTestingLibraryUtils<
549562
isAsyncUtil,
550563
isFireEventMethod,
551564
isRenderUtil,
565+
isDebugUtil,
552566
isPresenceAssert,
553567
isAbsenceAssert,
554568
canReportErrors,

lib/node-utils.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ export function getPropertyIdentifierNode(
392392
}
393393

394394
/**
395-
* Gets the deepest identifier node from a given node.
395+
* Gets the deepest identifier node in the expression from a given node.
396396
*
397397
* Opposite of {@link getReferenceNode}
398398
*
@@ -416,11 +416,15 @@ export function getDeepestIdentifierNode(
416416
return getDeepestIdentifierNode(node.callee);
417417
}
418418

419+
if (ASTUtils.isAwaitExpression(node)) {
420+
return getDeepestIdentifierNode(node.argument);
421+
}
422+
419423
return null;
420424
}
421425

422426
/**
423-
* Gets the farthest node from a given node.
427+
* Gets the farthest node in the expression from a given node.
424428
*
425429
* Opposite of {@link getDeepestIdentifierNode}
426430

lib/rules/no-debug.ts

+48-145
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
11
import {
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

2111
export const RULE_NAME = 'no-debug';
2212
export 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

Comments
 (0)