-
Notifications
You must be signed in to change notification settings - Fork 3
/
no-identical-titles.ts
114 lines (110 loc) · 4.37 KB
/
no-identical-titles.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
import { type TSESTree, ASTUtils } from "@typescript-eslint/utils";
import { createRule, getImportStatus } from "../utils";
/**
* Rule object for no-identical-titles.
*/
export default createRule({
name: "no-identical-titles",
meta: {
docs: {
description: "Enforce each test case to have a unique title.",
recommended: false,
},
messages: {
duplicateTitles: "More than one tests have the same title",
duplicateSuites: "More than one test suites have the same title",
},
type: "suggestion",
schema: [],
},
defaultOptions: [],
create: (context) => {
const {
ast: { body },
} = context.getSourceCode();
const uvuTest = getImportStatus({ module: "uvu", member: "test" }, body);
const uvuSuite = getImportStatus({ module: "uvu", member: "suite" }, body);
const testTitles = new Set<string>();
const suiteTitles = new Set<string>();
const namedTestTitles = new Map<string, Set<string>>();
let testListener;
if (uvuTest === undefined) {
testListener = {};
} else {
const selector = uvuTest.isNamed
? `CallExpression[callee.name=${uvuTest.alias}]`
: `CallExpression[callee.object.name=${uvuTest.namespace}][callee.property.name=test]`;
testListener = {
[selector]: (node: TSESTree.CallExpression) => {
const title = ASTUtils.getStringIfConstant(node.arguments[0]);
if (title != undefined) {
if (testTitles.has(title)) {
context.report({ messageId: "duplicateTitles", node });
} else {
testTitles.add(title);
}
}
},
};
}
let suiteListener;
if (uvuSuite === undefined) {
suiteListener = {};
} else {
const suiteDeclaration = `VariableDeclarator > ${
uvuSuite.isNamed
? `CallExpression[callee.name=${uvuSuite.alias}]`
: `CallExpression[callee.object.name=${uvuSuite.namespace}][callee.property.name=suite]`
}`;
suiteListener = {
[suiteDeclaration]: (node: TSESTree.CallExpression) => {
const suiteTitle = ASTUtils.getStringIfConstant(node.arguments[0]);
const [{ name: suiteName }] = context.getDeclaredVariables(
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion --
* The selector `suiteDeclaration` ensures that the variable node has a parent of
* VariableDeclarator. Therefore, the simplicity brought by this non-null assertion
* overweighs the type-safety gained by an extra if-statement.
*/
node.parent!
);
if (suiteTitle != undefined) {
if (suiteTitles.has(suiteTitle)) {
context.report({ messageId: "duplicateSuites", node });
} else {
suiteTitles.add(suiteTitle);
namedTestTitles.set(suiteName, new Set());
}
}
},
"CallExpression[callee.type=Identifier]": (
node: TSESTree.CallExpression
) => {
// This downcast is safe because the query `[callee.type=Identifier]` guarantees that
// `node.callee` corresponds to an identifier.
const { name } = node.callee as TSESTree.Identifier;
if (namedTestTitles.has(name)) {
const namedTitle = ASTUtils.getStringIfConstant(node.arguments[0]);
if (namedTitle != undefined) {
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion --
* We know `namedTestTitles.get(name)` can't be undefined if `namedTestTitles.has(name)`,
* but the compiler doesn't.
* cf. https://github.com/microsoft/TypeScript/issues/13086
*/
if (namedTestTitles.get(name)!.has(namedTitle)) {
context.report({ messageId: "duplicateTitles", node });
} else {
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion --
* We know `namedTestTitles.get(name)` can't be undefined if `namedTestTitles.has(name)`,
* but the compiler doesn't.
* cf. https://github.com/microsoft/TypeScript/issues/13086
*/
namedTestTitles.get(name)!.add(namedTitle);
}
}
}
},
};
}
return { ...testListener, ...suiteListener };
},
});