Skip to content

Commit 40759e7

Browse files
committed
feat: Add support for settings.globalAliases to support custom test/expect names
1 parent ece2939 commit 40759e7

40 files changed

+976
-49
lines changed

src/rules/expect-expect.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import ESTree from 'estree';
33
import { dig, isExpectCall, isTestCall } from '../utils/ast';
44

55
function isAssertionCall(
6+
context: Rule.RuleContext,
67
node: ESTree.CallExpression,
78
assertFunctionNames: string[],
89
) {
910
return (
10-
isExpectCall(node) ||
11+
isExpectCall(context, node) ||
1112
assertFunctionNames.find((name) => dig(node.callee, name))
1213
);
1314
}
@@ -36,9 +37,11 @@ export default {
3637

3738
return {
3839
CallExpression(node) {
39-
if (isTestCall(node, ['fixme', 'only', 'skip'])) {
40+
if (isTestCall(context, node, ['fixme', 'only', 'skip'])) {
4041
unchecked.push(node);
41-
} else if (isAssertionCall(node, options.assertFunctionNames)) {
42+
} else if (
43+
isAssertionCall(context, node, options.assertFunctionNames)
44+
) {
4245
const ancestors = sourceCode.getAncestors
4346
? sourceCode.getAncestors(node)
4447
: context.getAncestors();

src/rules/missing-playwright-await.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const playwrightTestMatchers = [
6262
];
6363

6464
function getCallType(
65+
context: Rule.RuleContext,
6566
node: ESTree.CallExpression & Rule.NodeParentExtension,
6667
awaitableMatchers: Set<string>,
6768
) {
@@ -74,7 +75,7 @@ function getCallType(
7475
return { messageId: 'testStep', node };
7576
}
7677

77-
const expectType = getExpectType(node);
78+
const expectType = getExpectType(context, node);
7879
if (!expectType) return;
7980

8081
const [lastMatcher] = getMatchers(node).slice(-1);
@@ -152,7 +153,7 @@ export default {
152153

153154
return {
154155
CallExpression(node) {
155-
const result = getCallType(node, awaitableMatchers);
156+
const result = getCallType(context, node, awaitableMatchers);
156157
const isValid = result ? checkValidity(result.node) : false;
157158

158159
if (result && !isValid) {

src/rules/no-conditional-in-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default {
66
function checkConditional(node: Rule.Node & Rule.NodeParentExtension) {
77
const call = findParent(node, 'CallExpression');
88

9-
if (call && isTestCall(call)) {
9+
if (call && isTestCall(context, call)) {
1010
context.report({ messageId: 'conditionalInTest', node });
1111
}
1212
}

src/rules/no-focused-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default {
66
return {
77
CallExpression(node) {
88
if (
9-
(isTestCall(node) || isDescribeCall(node)) &&
9+
(isTestCall(context, node) || isDescribeCall(node)) &&
1010
node.callee.type === 'MemberExpression' &&
1111
isPropertyAccessor(node.callee, 'only')
1212
) {

src/rules/no-restricted-matchers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default {
1010

1111
return {
1212
CallExpression(node) {
13-
const expectCall = parseExpectCall(node);
13+
const expectCall = parseExpectCall(context, node);
1414
if (!expectCall) return;
1515

1616
Object.entries(restrictedChains)

src/rules/no-skipped-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ export default {
1515
const { callee } = node;
1616

1717
if (
18-
(isTestIdentifier(callee) || isDescribeCall(node)) &&
18+
(isTestIdentifier(context, callee) || isDescribeCall(node)) &&
1919
callee.type === 'MemberExpression' &&
2020
isPropertyAccessor(callee, 'skip')
2121
) {
22-
const isHook = isTestCall(node) || isDescribeCall(node);
22+
const isHook = isTestCall(context, node) || isDescribeCall(node);
2323

2424
// If allowConditional is enabled and it's not a test/describe hook,
2525
// we ignore any `test.skip` calls that have no arguments.

src/rules/no-useless-not.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default {
1414
create(context) {
1515
return {
1616
CallExpression(node) {
17-
const expectCall = parseExpectCall(node);
17+
const expectCall = parseExpectCall(context, node);
1818
if (!expectCall) return;
1919

2020
// As the name implies, this rule only implies if the not modifier is

src/rules/prefer-lowercase-title.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default {
2424
CallExpression(node) {
2525
const method = isDescribeCall(node)
2626
? 'test.describe'
27-
: isTestCall(node)
27+
: isTestCall(context, node)
2828
? 'test'
2929
: null;
3030

src/rules/prefer-strict-equal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default {
66
create(context) {
77
return {
88
CallExpression(node) {
9-
const expectCall = parseExpectCall(node);
9+
const expectCall = parseExpectCall(context, node);
1010

1111
if (expectCall?.matcherName === 'toEqual') {
1212
context.report({

src/rules/prefer-to-be.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export default {
5252
create(context) {
5353
return {
5454
CallExpression(node) {
55-
const expectCall = parseExpectCall(node);
55+
const expectCall = parseExpectCall(context, node);
5656
if (!expectCall) return;
5757

5858
const notMatchers = ['toBeUndefined', 'toBeDefined'];

src/rules/prefer-to-contain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default {
2525
create(context) {
2626
return {
2727
CallExpression(node) {
28-
const expectCall = parseExpectCall(node);
28+
const expectCall = parseExpectCall(context, node);
2929
if (!expectCall || expectCall.args.length === 0) return;
3030

3131
const { args, matcher, matcherName } = expectCall;

src/rules/prefer-to-have-count.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
create(context) {
1010
return {
1111
CallExpression(node) {
12-
const expectCall = parseExpectCall(node);
12+
const expectCall = parseExpectCall(context, node);
1313
if (!expectCall || !matchers.has(expectCall.matcherName)) {
1414
return;
1515
}

src/rules/prefer-to-have-length.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
create(context) {
1010
return {
1111
CallExpression(node) {
12-
const expectCall = parseExpectCall(node);
12+
const expectCall = parseExpectCall(context, node);
1313
if (!expectCall || !lengthMatchers.has(expectCall.matcherName)) {
1414
return;
1515
}

src/rules/prefer-web-first-assertions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default {
6262
create(context) {
6363
return {
6464
CallExpression(node) {
65-
const expectCall = parseExpectCall(node);
65+
const expectCall = parseExpectCall(context, node);
6666
if (!expectCall) return;
6767

6868
const [arg] = node.arguments;

src/rules/require-soft-assertions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default {
55
create(context) {
66
return {
77
CallExpression(node) {
8-
if (getExpectType(node) === 'standalone') {
8+
if (getExpectType(context, node) === 'standalone') {
99
context.report({
1010
fix: (fixer) => fixer.insertTextAfter(node.callee, '.soft'),
1111
messageId: 'requireSoft',

src/rules/require-top-level-describe.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export default {
3030
}
3131
}
3232
} else if (!describeCount) {
33-
if (isTestCall(node)) {
33+
if (isTestCall(context, node)) {
3434
context.report({ messageId: 'unexpectedTest', node: node.callee });
35-
} else if (isTestHook(node)) {
35+
} else if (isTestHook(context, node)) {
3636
context.report({ messageId: 'unexpectedHook', node: node.callee });
3737
}
3838
}

src/rules/valid-expect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export default {
3939

4040
return {
4141
CallExpression(node) {
42-
if (!isExpectCall(node)) return;
42+
if (!isExpectCall(context, node)) return;
4343

44-
const expectCall = parseExpectCall(node);
44+
const expectCall = parseExpectCall(context, node);
4545
if (!expectCall) {
4646
context.report({ messageId: 'matcherNotFound', node });
4747
} else {

src/rules/valid-title.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default {
111111
return {
112112
CallExpression(node) {
113113
const isDescribe = isDescribeCall(node);
114-
const isTest = isTestCall(node);
114+
const isTest = isTestCall(context, node);
115115
if (!isDescribe && !isTest) {
116116
return;
117117
}

src/utils/ast.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,14 @@ export function isPropertyAccessor(
6363
return getStringValue(node.property) === name;
6464
}
6565

66-
export function isTestIdentifier(node: ESTree.Node) {
66+
export function isTestIdentifier(context: Rule.RuleContext, node: ESTree.Node) {
67+
const aliases = context.settings.playwright?.globalAliases?.test ?? [];
68+
const testNames = ['test', ...aliases];
69+
const regex = new RegExp(`^(${testNames.join('|')})$`);
70+
6771
return (
68-
isIdentifier(node, 'test') ||
69-
(node.type === 'MemberExpression' && isIdentifier(node.object, 'test'))
72+
isIdentifier(node, regex) ||
73+
(node.type === 'MemberExpression' && isIdentifier(node.object, regex))
7074
);
7175
}
7276

@@ -108,9 +112,13 @@ export function findParent<T extends ESTree.Node['type']>(
108112
: findParent(node.parent, type);
109113
}
110114

111-
export function isTestCall(node: ESTree.CallExpression, modifiers?: string[]) {
115+
export function isTestCall(
116+
context: Rule.RuleContext,
117+
node: ESTree.CallExpression,
118+
modifiers?: string[],
119+
) {
112120
return (
113-
isTestIdentifier(node.callee) &&
121+
isTestIdentifier(context, node.callee) &&
114122
!isDescribeCall(node) &&
115123
(node.callee.type !== 'MemberExpression' ||
116124
!modifiers ||
@@ -123,10 +131,13 @@ export function isTestCall(node: ESTree.CallExpression, modifiers?: string[]) {
123131
}
124132

125133
const testHooks = new Set(['afterAll', 'afterEach', 'beforeAll', 'beforeEach']);
126-
export function isTestHook(node: ESTree.CallExpression) {
134+
export function isTestHook(
135+
context: Rule.RuleContext,
136+
node: ESTree.CallExpression,
137+
) {
127138
return (
128139
node.callee.type === 'MemberExpression' &&
129-
isIdentifier(node.callee.object, 'test') &&
140+
isTestIdentifier(context, node.callee.object) &&
130141
testHooks.has(getStringValue(node.callee.property))
131142
);
132143
}
@@ -135,23 +146,32 @@ const expectSubCommands = new Set(['soft', 'poll']);
135146
export type ExpectType = 'poll' | 'soft' | 'standalone';
136147

137148
export function getExpectType(
149+
context: Rule.RuleContext,
138150
node: ESTree.CallExpression,
139151
): ExpectType | undefined {
140-
if (isIdentifier(node.callee, /(^expect|Expect)$/)) {
152+
const aliases = context.settings.playwright?.globalAliases?.expect ?? [];
153+
const expectNames = ['expect', ...aliases];
154+
const regex = new RegExp(`(^(${expectNames.join('|')})|Expect)$`);
155+
156+
if (isIdentifier(node.callee, regex)) {
141157
return 'standalone';
142158
}
143159

144160
if (
145161
node.callee.type === 'MemberExpression' &&
162+
// TODO: Maybe
146163
isIdentifier(node.callee.object, 'expect')
147164
) {
148165
const type = getStringValue(node.callee.property);
149166
return expectSubCommands.has(type) ? (type as ExpectType) : undefined;
150167
}
151168
}
152169

153-
export function isExpectCall(node: ESTree.CallExpression) {
154-
return !!getExpectType(node);
170+
export function isExpectCall(
171+
context: Rule.RuleContext,
172+
node: ESTree.CallExpression,
173+
) {
174+
return !!getExpectType(context, node);
155175
}
156176

157177
export function getMatchers(

src/utils/parseExpectCall.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ export interface ParsedExpectCall {
1818
}
1919

2020
export function parseExpectCall(
21+
context: Rule.RuleContext,
2122
node: ESTree.CallExpression & Rule.NodeParentExtension,
2223
): ParsedExpectCall | undefined {
23-
if (!isExpectCall(node)) {
24+
if (!isExpectCall(context, node)) {
2425
return;
2526
}
2627

test/spec/expect-expect.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ runRuleTester('expect-expect', rule, {
3030
name: 'Custom assert function',
3131
options: [{ assertFunctionNames: ['wayComplexCustomCondition'] }],
3232
},
33+
{
34+
code: 'it("should pass", () => hi(true).toBeDefined())',
35+
errors: [{ messageId: 'noAssertions' }],
36+
name: 'Global aliases',
37+
settings: {
38+
playwright: {
39+
globalAliases: { test: ['it'] },
40+
},
41+
},
42+
},
3343
],
3444
valid: [
3545
'foo();',
@@ -72,5 +82,23 @@ runRuleTester('expect-expect', rule, {
7282
name: 'Custom assert class method',
7383
options: [{ assertFunctionNames: ['assertCustomCondition'] }],
7484
},
85+
{
86+
code: 'it("should pass", () => expect(true).toBeDefined())',
87+
name: 'Global alias - test',
88+
settings: {
89+
playwright: {
90+
globalAliases: { test: ['it'] },
91+
},
92+
},
93+
},
94+
{
95+
code: 'test("should pass", () => assert(true).toBeDefined())',
96+
name: 'Global alias - assert',
97+
settings: {
98+
playwright: {
99+
globalAliases: { expect: ['assert'] },
100+
},
101+
},
102+
},
75103
],
76104
});

0 commit comments

Comments
 (0)