Skip to content

Commit 287ca77

Browse files
authored
refactor(no-manual-cleanup): use custom rule creator (#249)
* refactor(no-manual-cleanup): use custom rule creator * refactor: extract detection utils for import module name * refactor(no-manual-cleanup): use detection helpers for imported modules * refactor(no-manual-cleanup): small improvements * test(no-manual-cleanup): include more variants * feat(no-manual-cleanup): report custom module * refactor: rename type for import module node * refactor: use node utils to know node type * refactor: remove unused imports * refactor: remove outdated comment line
1 parent eb17456 commit 287ca77

7 files changed

+205
-139
lines changed

lib/detect-testing-library-utils.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
2-
import { isLiteral } from './node-utils';
2+
import { getImportModuleName, isLiteral, ImportModuleNode } from './node-utils';
33

44
export type TestingLibrarySettings = {
55
'testing-library/module'?: string;
@@ -25,14 +25,11 @@ export type EnhancedRuleCreate<
2525
detectionHelpers: Readonly<DetectionHelpers>
2626
) => TRuleListener;
2727

28-
type ModuleImportation =
29-
| TSESTree.ImportDeclaration
30-
| TSESTree.CallExpression
31-
| null;
32-
3328
export type DetectionHelpers = {
34-
getTestingLibraryImportNode: () => ModuleImportation;
35-
getCustomModuleImportNode: () => ModuleImportation;
29+
getTestingLibraryImportNode: () => ImportModuleNode | null;
30+
getCustomModuleImportNode: () => ImportModuleNode | null;
31+
getTestingLibraryImportName: () => string | undefined;
32+
getCustomModuleImportName: () => string | undefined;
3633
getIsTestingLibraryImported: () => boolean;
3734
getIsValidFilename: () => boolean;
3835
canReportErrors: () => boolean;
@@ -52,8 +49,8 @@ export function detectTestingLibraryUtils<
5249
context: TestingLibraryContext<TOptions, TMessageIds>,
5350
optionsWithDefault: Readonly<TOptions>
5451
): TSESLint.RuleListener => {
55-
let importedTestingLibraryNode: ModuleImportation = null;
56-
let importedCustomModuleNode: ModuleImportation = null;
52+
let importedTestingLibraryNode: ImportModuleNode | null = null;
53+
let importedCustomModuleNode: ImportModuleNode | null = null;
5754

5855
// Init options based on shared ESLint settings
5956
const customModule = context.settings['testing-library/module'];
@@ -69,6 +66,12 @@ export function detectTestingLibraryUtils<
6966
getCustomModuleImportNode() {
7067
return importedCustomModuleNode;
7168
},
69+
getTestingLibraryImportName() {
70+
return getImportModuleName(importedTestingLibraryNode);
71+
},
72+
getCustomModuleImportName() {
73+
return getImportModuleName(importedCustomModuleNode);
74+
},
7275
/**
7376
* Gets if Testing Library is considered as imported or not.
7477
*

lib/node-utils.ts

+38-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import {
66
import { RuleContext } from '@typescript-eslint/experimental-utils/dist/ts-eslint';
77

88
export function isCallExpression(
9-
node: TSESTree.Node
9+
node: TSESTree.Node | null | undefined
1010
): node is TSESTree.CallExpression {
11-
return node && node.type === AST_NODE_TYPES.CallExpression;
11+
return node?.type === AST_NODE_TYPES.CallExpression;
1212
}
1313

1414
export function isNewExpression(
@@ -17,6 +17,7 @@ export function isNewExpression(
1717
return node && node.type === 'NewExpression';
1818
}
1919

20+
// TODO: remove this one and use ASTUtils one instead
2021
export function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier {
2122
return node && node.type === AST_NODE_TYPES.Identifier;
2223
}
@@ -69,8 +70,10 @@ export function isObjectPattern(
6970
return node && node.type === AST_NODE_TYPES.ObjectPattern;
7071
}
7172

72-
export function isProperty(node: TSESTree.Node): node is TSESTree.Property {
73-
return node && node.type === AST_NODE_TYPES.Property;
73+
export function isProperty(
74+
node: TSESTree.Node | null | undefined
75+
): node is TSESTree.Property {
76+
return node?.type === AST_NODE_TYPES.Property;
7477
}
7578

7679
export function isJSXAttribute(
@@ -126,6 +129,7 @@ export function hasThenProperty(node: TSESTree.Node): boolean {
126129
);
127130
}
128131

132+
// TODO: remove this one and use ASTUtils one instead
129133
export function isAwaitExpression(
130134
node: TSESTree.Node
131135
): node is TSESTree.AwaitExpression {
@@ -150,6 +154,12 @@ export function isArrayExpression(
150154
return node?.type === AST_NODE_TYPES.ArrayExpression;
151155
}
152156

157+
export function isImportDeclaration(
158+
node: TSESTree.Node | null | undefined
159+
): node is TSESTree.ImportDeclaration {
160+
return node?.type === AST_NODE_TYPES.ImportDeclaration;
161+
}
162+
153163
export function isAwaited(node: TSESTree.Node): boolean {
154164
return (
155165
isAwaitExpression(node) ||
@@ -176,7 +186,7 @@ export function getVariableReferences(
176186
): TSESLint.Scope.Reference[] {
177187
return (
178188
(isVariableDeclarator(node) &&
179-
context.getDeclaredVariables(node)[0].references.slice(1)) ||
189+
context.getDeclaredVariables(node)[0]?.references?.slice(1)) ||
180190
[]
181191
);
182192
}
@@ -220,3 +230,26 @@ export function isRenderVariableDeclarator(
220230

221231
return false;
222232
}
233+
234+
// TODO: extract into types file?
235+
export type ImportModuleNode =
236+
| TSESTree.ImportDeclaration
237+
| TSESTree.CallExpression;
238+
239+
export function getImportModuleName(
240+
node: ImportModuleNode | undefined | null
241+
): string | undefined {
242+
// import node of shape: import { foo } from 'bar'
243+
if (isImportDeclaration(node) && typeof node.source.value === 'string') {
244+
return node.source.value;
245+
}
246+
247+
// import node of shape: const { foo } = require('bar')
248+
if (
249+
isCallExpression(node) &&
250+
isLiteral(node.arguments[0]) &&
251+
typeof node.arguments[0].value === 'string'
252+
) {
253+
return node.arguments[0].value;
254+
}
255+
}

lib/rules/no-container.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
105105
}
106106
},
107107

108-
CallExpression(node: TSESTree.CallExpression) {
108+
CallExpression(node) {
109109
if (isMemberExpression(node.callee)) {
110110
showErrorIfChainedContainerMethod(node.callee);
111111
} else {

lib/rules/no-dom-import.ts

+13-33
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import {
2-
AST_NODE_TYPES,
3-
TSESTree,
4-
} from '@typescript-eslint/experimental-utils';
5-
import { isIdentifier, isLiteral } from '../node-utils';
1+
import { TSESTree } from '@typescript-eslint/experimental-utils';
62
import { createTestingLibraryRule } from '../create-testing-library-rule';
3+
import { isCallExpression } from '../node-utils';
74

85
export const RULE_NAME = 'no-dom-import';
96
export type MessageIds = 'noDomImport' | 'noDomImportFramework';
@@ -40,11 +37,10 @@ export default createTestingLibraryRule<Options, MessageIds>({
4037

4138
create(context, [framework], helpers) {
4239
function report(
43-
node: TSESTree.ImportDeclaration | TSESTree.Identifier,
40+
node: TSESTree.ImportDeclaration | TSESTree.CallExpression,
4441
moduleName: string
4542
) {
4643
if (framework) {
47-
const isRequire = isIdentifier(node) && node.name === 'require';
4844
const correctModuleName = moduleName.replace('dom', framework);
4945
context.report({
5046
node,
@@ -53,18 +49,16 @@ export default createTestingLibraryRule<Options, MessageIds>({
5349
module: correctModuleName,
5450
},
5551
fix(fixer) {
56-
if (isRequire) {
57-
const callExpression = node.parent as TSESTree.CallExpression;
58-
const name = callExpression.arguments[0] as TSESTree.Literal;
52+
if (isCallExpression(node)) {
53+
const name = node.arguments[0] as TSESTree.Literal;
5954

6055
// Replace the module name with the raw module name as we can't predict which punctuation the user is going to use
6156
return fixer.replaceText(
6257
name,
6358
name.raw.replace(moduleName, correctModuleName)
6459
);
6560
} else {
66-
const importDeclaration = node as TSESTree.ImportDeclaration;
67-
const name = importDeclaration.source;
61+
const name = node.source;
6862
return fixer.replaceText(
6963
name,
7064
name.raw.replace(moduleName, correctModuleName)
@@ -82,36 +76,22 @@ export default createTestingLibraryRule<Options, MessageIds>({
8276

8377
return {
8478
'Program:exit'() {
79+
const importName = helpers.getTestingLibraryImportName();
8580
const importNode = helpers.getTestingLibraryImportNode();
8681

8782
if (!importNode) {
8883
return;
8984
}
9085

91-
// import node of shape: import { foo } from 'bar'
92-
if (importNode.type === AST_NODE_TYPES.ImportDeclaration) {
93-
const domModuleName = DOM_TESTING_LIBRARY_MODULES.find(
94-
(module) => module === importNode.source.value
95-
);
86+
const domModuleName = DOM_TESTING_LIBRARY_MODULES.find(
87+
(module) => module === importName
88+
);
9689

97-
domModuleName && report(importNode, domModuleName);
90+
if (!domModuleName) {
91+
return;
9892
}
9993

100-
// import node of shape: const { foo } = require('bar')
101-
if (importNode.type === AST_NODE_TYPES.CallExpression) {
102-
const literalNodeDomModuleName = importNode.arguments.find(
103-
(arg) =>
104-
isLiteral(arg) &&
105-
typeof arg.value === 'string' &&
106-
DOM_TESTING_LIBRARY_MODULES.includes(arg.value)
107-
) as TSESTree.Literal;
108-
109-
literalNodeDomModuleName &&
110-
report(
111-
importNode.callee as TSESTree.Identifier,
112-
literalNodeDomModuleName.value as string
113-
);
114-
}
94+
report(importNode, domModuleName);
11595
},
11696
};
11797
},

0 commit comments

Comments
 (0)