-
Notifications
You must be signed in to change notification settings - Fork 147
/
Copy pathdetect-testing-library-utils.ts
147 lines (129 loc) · 5.12 KB
/
detect-testing-library-utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
export type TestingLibrarySettings = {
'testing-library/module'?: string;
'testing-library/file-name'?: string;
};
export type TestingLibraryContext<
TOptions extends readonly unknown[],
TMessageIds extends string
> = Readonly<
TSESLint.RuleContext<TMessageIds, TOptions> & {
settings: TestingLibrarySettings;
}
>;
export type EnhancedRuleCreate<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
> = (
context: TestingLibraryContext<TOptions, TMessageIds>,
optionsWithDefault: Readonly<TOptions>,
detectionHelpers: Readonly<DetectionHelpers>
) => TRuleListener;
export type DetectionHelpers = {
getIsTestingLibraryImported: () => boolean;
getIsValidFileName: () => boolean;
canReportErrors: () => boolean;
};
const DEFAULT_FILE_NAME_PATTERN = '^.*\\.(test|spec)\\.[jt]sx?$';
/**
* Enhances a given rule `create` with helpers to detect Testing Library utils.
*/
export function detectTestingLibraryUtils<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
>(ruleCreate: EnhancedRuleCreate<TOptions, TMessageIds, TRuleListener>) {
return (
context: TestingLibraryContext<TOptions, TMessageIds>,
optionsWithDefault: Readonly<TOptions>
): TSESLint.RuleListener => {
let isImportingTestingLibraryModule = false;
let isImportingCustomModule = false;
// Init options based on shared ESLint settings
const customModule = context.settings['testing-library/module'];
const fileNamePattern =
context.settings['testing-library/file-name'] ??
DEFAULT_FILE_NAME_PATTERN;
// Helpers for Testing Library detection.
const helpers: DetectionHelpers = {
/**
* Gets if Testing Library is considered as imported or not.
*
* By default, it is ALWAYS considered as imported. This is what we call
* "aggressive reporting" so we don't miss TL utils reexported from
* custom modules.
*
* However, there is a setting to customize the module where TL utils can
* be imported from: "testing-library/module". If this setting is enabled,
* then this method will return `true` ONLY IF a testing-library package
* or custom module are imported.
*/
getIsTestingLibraryImported() {
if (!customModule) {
return true;
}
return isImportingTestingLibraryModule || isImportingCustomModule;
},
/**
* Gets if name of the file being analyzed is valid or not.
*
* This is based on "testing-library/file-name" setting.
*/
getIsValidFileName() {
const fileName = context.getFilename();
return !!fileName.match(fileNamePattern);
},
/**
* Wraps all conditions that must be met to report rules.
*/
canReportErrors() {
return this.getIsTestingLibraryImported() && this.getIsValidFileName();
},
};
// Instructions for Testing Library detection.
const detectionInstructions: TSESLint.RuleListener = {
/**
* This ImportDeclaration rule listener will check if Testing Library related
* modules are loaded. Since imports happen first thing in a file, it's
* safe to use `isImportingTestingLibraryModule` and `isImportingCustomModule`
* since they will have corresponding value already updated when reporting other
* parts of the file.
*/
ImportDeclaration(node: TSESTree.ImportDeclaration) {
if (!isImportingTestingLibraryModule) {
// check only if testing library import not found yet so we avoid
// to override isImportingTestingLibraryModule after it's found
isImportingTestingLibraryModule = /testing-library/g.test(
node.source.value as string
);
}
if (!isImportingCustomModule) {
// check only if custom module import not found yet so we avoid
// to override isImportingCustomModule after it's found
const importName = String(node.source.value);
isImportingCustomModule = importName.endsWith(customModule);
}
},
};
// update given rule to inject Testing Library detection
const ruleInstructions = ruleCreate(context, optionsWithDefault, helpers);
const enhancedRuleInstructions: TSESLint.RuleListener = {};
const allKeys = new Set(
Object.keys(detectionInstructions).concat(Object.keys(ruleInstructions))
);
// Iterate over ALL instructions keys so we can override original rule instructions
// to prevent their execution if conditions to report errors are not met.
allKeys.forEach((instruction) => {
enhancedRuleInstructions[instruction] = (node) => {
if (instruction in detectionInstructions) {
detectionInstructions[instruction](node);
}
if (helpers.canReportErrors() && ruleInstructions[instruction]) {
return ruleInstructions[instruction](node);
}
};
});
return enhancedRuleInstructions;
};
}