Skip to content

Commit 02ec945

Browse files
authoredJun 4, 2022
feat: support aliases for jest globals (e.g. context) (#1129)
1 parent 8beaad6 commit 02ec945

34 files changed

+287
-97
lines changed
 

‎README.md

+19
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ doing:
5959
This is included in all configs shared by this plugin, so can be omitted if
6060
extending them.
6161

62+
#### Aliased Jest globals
63+
64+
You can tell this plugin about any global Jests you have aliased using the
65+
`globalAliases` setting:
66+
67+
```json
68+
{
69+
"settings": {
70+
"jest": {
71+
"globalAliases": {
72+
"describe": ["context"],
73+
"fdescribe": ["fcontext"],
74+
"xdescribe": ["xcontext"]
75+
}
76+
}
77+
}
78+
}
79+
```
80+
6281
### Running rules only on test-related files
6382

6483
The rules provided by this plugin assume that the files they are checking are

‎src/rules/__tests__/no-focused-tests.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ ruleTester.run('no-focused-tests', rule, {
4444
},
4545
],
4646
},
47+
{
48+
code: 'context.only()',
49+
errors: [
50+
{
51+
messageId: 'focusedTest',
52+
column: 9,
53+
line: 1,
54+
suggestions: [
55+
{
56+
messageId: 'suggestRemoveFocus',
57+
output: 'context()',
58+
},
59+
],
60+
},
61+
],
62+
settings: { jest: { globalAliases: { describe: ['context'] } } },
63+
},
4764
{
4865
code: 'describe.only.each()()',
4966
errors: [

‎src/rules/__tests__/no-identical-title.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -243,5 +243,15 @@ ruleTester.run('no-identical-title', rule, {
243243
`,
244244
errors: [{ messageId: 'multipleTestTitle', column: 6, line: 3 }],
245245
},
246+
{
247+
code: dedent`
248+
context('foo', () => {
249+
describe('foe', () => {});
250+
});
251+
describe('foo', () => {});
252+
`,
253+
errors: [{ messageId: 'multipleDescribeTitle', column: 10, line: 4 }],
254+
settings: { jest: { globalAliases: { describe: ['context'] } } },
255+
},
246256
],
247257
});

‎src/rules/__tests__/valid-expect-in-promise.test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1613,5 +1613,26 @@ ruleTester.run('valid-expect-in-promise', rule, {
16131613
},
16141614
],
16151615
},
1616+
{
1617+
code: dedent`
1618+
promiseThatThis('is valid', async () => {
1619+
const promise = loadNumber().then(number => {
1620+
expect(typeof number).toBe('number');
1621+
1622+
return number + 1;
1623+
});
1624+
1625+
expect(anotherPromise).resolves.toBe(1);
1626+
});
1627+
`,
1628+
errors: [
1629+
{
1630+
messageId: 'expectInFloatingPromise',
1631+
line: 2,
1632+
column: 9,
1633+
},
1634+
],
1635+
settings: { jest: { globalAliases: { xit: ['promiseThatThis'] } } },
1636+
},
16161637
],
16171638
});

‎src/rules/consistent-test-it.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ export default createRule<
7272

7373
return {
7474
CallExpression(node: TSESTree.CallExpression) {
75-
const scope = context.getScope();
76-
const jestFnCall = parseJestFnCall(node, scope);
75+
const jestFnCall = parseJestFnCall(node, context);
7776

7877
if (!jestFnCall) {
7978
return;
@@ -129,7 +128,7 @@ export default createRule<
129128
}
130129
},
131130
'CallExpression:exit'(node) {
132-
if (isTypeOfJestFnCall(node, context.getScope(), ['describe'])) {
131+
if (isTypeOfJestFnCall(node, context, ['describe'])) {
133132
describeNestingLevel--;
134133
}
135134
},

‎src/rules/expect-expect.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export default createRule<
9696
const testCallExpressions =
9797
getTestCallExpressionsFromDeclaredVariables(
9898
declaredVariables,
99-
context.getScope(),
99+
context,
100100
);
101101

102102
checkCallExpressionUsed(testCallExpressions);
@@ -114,7 +114,7 @@ export default createRule<
114114
const name = getNodeName(node.callee) ?? '';
115115

116116
if (
117-
isTypeOfJestFnCall(node, context.getScope(), ['test']) ||
117+
isTypeOfJestFnCall(node, context, ['test']) ||
118118
additionalTestBlockFunctions.includes(name)
119119
) {
120120
if (

‎src/rules/max-nested-describe.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default createRule({
3838

3939
if (
4040
parent?.type !== AST_NODE_TYPES.CallExpression ||
41-
!isTypeOfJestFnCall(parent, context.getScope(), ['describe'])
41+
!isTypeOfJestFnCall(parent, context, ['describe'])
4242
) {
4343
return;
4444
}
@@ -61,7 +61,7 @@ export default createRule({
6161

6262
if (
6363
parent?.type === AST_NODE_TYPES.CallExpression &&
64-
isTypeOfJestFnCall(parent, context.getScope(), ['describe'])
64+
isTypeOfJestFnCall(parent, context, ['describe'])
6565
) {
6666
describeCallbackStack.pop();
6767
}

‎src/rules/no-conditional-expect.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ export default createRule({
4242
const declaredVariables = context.getDeclaredVariables(node);
4343
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
4444
declaredVariables,
45-
context.getScope(),
45+
context,
4646
);
4747

4848
if (testCallExpressions.length > 0) {
4949
inTestCase = true;
5050
}
5151
},
5252
CallExpression(node: TSESTree.CallExpression) {
53-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
53+
if (isTypeOfJestFnCall(node, context, ['test'])) {
5454
inTestCase = true;
5555
}
5656

@@ -73,7 +73,7 @@ export default createRule({
7373
}
7474
},
7575
'CallExpression:exit'(node) {
76-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
76+
if (isTypeOfJestFnCall(node, context, ['test'])) {
7777
inTestCase = false;
7878
}
7979

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ export default createRule({
3030

3131
return {
3232
CallExpression(node: TSESTree.CallExpression) {
33-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
33+
if (isTypeOfJestFnCall(node, context, ['test'])) {
3434
inTestCase = true;
3535
}
3636
},
3737
'CallExpression:exit'(node) {
38-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
38+
if (isTypeOfJestFnCall(node, context, ['test'])) {
3939
inTestCase = false;
4040
}
4141
},

‎src/rules/no-disabled-tests.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default createRule({
3131

3232
return {
3333
CallExpression(node) {
34-
const jestFnCall = parseJestFnCall(node, context.getScope());
34+
const jestFnCall = parseJestFnCall(node, context);
3535

3636
if (!jestFnCall) {
3737
return;
@@ -65,7 +65,7 @@ export default createRule({
6565
}
6666
},
6767
'CallExpression:exit'(node) {
68-
const jestFnCall = parseJestFnCall(node, context.getScope());
68+
const jestFnCall = parseJestFnCall(node, context);
6969

7070
if (!jestFnCall) {
7171
return;

‎src/rules/no-done-callback.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { createRule, getNodeName, isFunction, parseJestFnCall } from './utils';
44
const findCallbackArg = (
55
node: TSESTree.CallExpression,
66
isJestEach: boolean,
7-
scope: TSESLint.Scope.Scope,
7+
context: TSESLint.RuleContext<string, unknown[]>,
88
): TSESTree.CallExpression['arguments'][0] | null => {
99
if (isJestEach) {
1010
return node.arguments[1];
1111
}
1212

13-
const jestFnCall = parseJestFnCall(node, scope);
13+
const jestFnCall = parseJestFnCall(node, context);
1414

1515
if (jestFnCall?.type === 'hook' && node.arguments.length >= 1) {
1616
return node.arguments[0];
@@ -60,7 +60,7 @@ export default createRule({
6060
return;
6161
}
6262

63-
const callback = findCallbackArg(node, isJestEach, context.getScope());
63+
const callback = findCallbackArg(node, isJestEach, context);
6464
const callbackArgIndex = Number(isJestEach);
6565

6666
if (

‎src/rules/no-duplicate-hooks.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ export default createRule({
2020

2121
return {
2222
CallExpression(node) {
23-
const scope = context.getScope();
24-
25-
const jestFnCall = parseJestFnCall(node, scope);
23+
const jestFnCall = parseJestFnCall(node, context);
2624

2725
if (jestFnCall?.type === 'describe') {
2826
hookContexts.push({});
@@ -45,7 +43,7 @@ export default createRule({
4543
}
4644
},
4745
'CallExpression:exit'(node) {
48-
if (isTypeOfJestFnCall(node, context.getScope(), ['describe'])) {
46+
if (isTypeOfJestFnCall(node, context, ['describe'])) {
4947
hookContexts.pop();
5048
}
5149
},

‎src/rules/no-export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default createRule({
3434
},
3535

3636
CallExpression(node) {
37-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
37+
if (isTypeOfJestFnCall(node, context, ['test'])) {
3838
hasTestCase = true;
3939
}
4040
},

‎src/rules/no-focused-tests.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ export default createRule({
2222
create(context) {
2323
return {
2424
CallExpression(node) {
25-
const scope = context.getScope();
26-
27-
const jestFnCall = parseJestFnCall(node, scope);
25+
const jestFnCall = parseJestFnCall(node, context);
2826

2927
if (jestFnCall?.type !== 'test' && jestFnCall?.type !== 'describe') {
3028
return;

‎src/rules/no-hooks.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default createRule<
3232
create(context, [{ allow = [] }]) {
3333
return {
3434
CallExpression(node) {
35-
const jestFnCall = parseJestFnCall(node, context.getScope());
35+
const jestFnCall = parseJestFnCall(node, context);
3636

3737
if (
3838
jestFnCall?.type === 'hook' &&

‎src/rules/no-identical-title.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ export default createRule({
4040

4141
return {
4242
CallExpression(node) {
43-
const scope = context.getScope();
4443
const currentLayer = contexts[contexts.length - 1];
4544

46-
const jestFnCall = parseJestFnCall(node, scope);
45+
const jestFnCall = parseJestFnCall(node, context);
4746

4847
if (!jestFnCall) {
4948
return;
@@ -87,7 +86,7 @@ export default createRule({
8786
currentLayer.describeTitles.push(title);
8887
},
8988
'CallExpression:exit'(node) {
90-
if (isTypeOfJestFnCall(node, context.getScope(), ['describe'])) {
89+
if (isTypeOfJestFnCall(node, context, ['describe'])) {
9190
contexts.pop();
9291
}
9392
},

‎src/rules/no-if.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export default createRule({
7575

7676
return {
7777
CallExpression(node) {
78-
const jestFnCall = parseJestFnCall(node, context.getScope());
78+
const jestFnCall = parseJestFnCall(node, context);
7979

8080
if (jestFnCall?.type === 'test') {
8181
stack.push(true);
@@ -92,7 +92,7 @@ export default createRule({
9292
const declaredVariables = context.getDeclaredVariables(node);
9393
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
9494
declaredVariables,
95-
context.getScope(),
95+
context,
9696
);
9797

9898
stack.push(testCallExpressions.length > 0);

‎src/rules/no-standalone-expect.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010

1111
const getBlockType = (
1212
statement: TSESTree.BlockStatement,
13-
scope: TSESLint.Scope.Scope,
13+
context: TSESLint.RuleContext<string, unknown[]>,
1414
): 'function' | 'describe' | null => {
1515
const func = statement.parent;
1616

@@ -37,7 +37,7 @@ const getBlockType = (
3737
// if it's not a variable, it will be callExpr, we only care about describe
3838
if (
3939
expr.type === AST_NODE_TYPES.CallExpression &&
40-
isTypeOfJestFnCall(expr, scope, ['describe'])
40+
isTypeOfJestFnCall(expr, context, ['describe'])
4141
) {
4242
return 'describe';
4343
}
@@ -85,7 +85,7 @@ export default createRule<
8585
additionalTestBlockFunctions.includes(getNodeName(node) || '');
8686

8787
const isTestBlock = (node: TSESTree.CallExpression): boolean =>
88-
isTypeOfJestFnCall(node, context.getScope(), ['test']) ||
88+
isTypeOfJestFnCall(node, context, ['test']) ||
8989
isCustomTestBlockFunction(node);
9090

9191
return {
@@ -123,16 +123,15 @@ export default createRule<
123123
},
124124

125125
BlockStatement(statement) {
126-
const blockType = getBlockType(statement, context.getScope());
126+
const blockType = getBlockType(statement, context);
127127

128128
if (blockType) {
129129
callStack.push(blockType);
130130
}
131131
},
132132
'BlockStatement:exit'(statement: TSESTree.BlockStatement) {
133133
if (
134-
callStack[callStack.length - 1] ===
135-
getBlockType(statement, context.getScope())
134+
callStack[callStack.length - 1] === getBlockType(statement, context)
136135
) {
137136
callStack.pop();
138137
}

‎src/rules/no-test-prefixes.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ export default createRule({
2020
create(context) {
2121
return {
2222
CallExpression(node) {
23-
const scope = context.getScope();
24-
const jestFnCall = parseJestFnCall(node, scope);
23+
const jestFnCall = parseJestFnCall(node, context);
2524

2625
if (jestFnCall?.type !== 'describe' && jestFnCall?.type !== 'test') {
2726
return;

‎src/rules/no-test-return-statement.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default createRule({
3838
create(context) {
3939
return {
4040
CallExpression(node) {
41-
if (!isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
41+
if (!isTypeOfJestFnCall(node, context, ['test'])) {
4242
return;
4343
}
4444

@@ -55,7 +55,7 @@ export default createRule({
5555
const declaredVariables = context.getDeclaredVariables(node);
5656
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
5757
declaredVariables,
58-
context.getScope(),
58+
context,
5959
);
6060

6161
if (testCallExpressions.length === 0) return;

‎src/rules/prefer-expect-assertions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export default createRule<[RuleOptions], MessageIds>({
157157
ForOfStatement: enterForLoop,
158158
'ForOfStatement:exit': exitForLoop,
159159
CallExpression(node) {
160-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
160+
if (isTypeOfJestFnCall(node, context, ['test'])) {
161161
inTestCaseCall = true;
162162

163163
return;
@@ -174,7 +174,7 @@ export default createRule<[RuleOptions], MessageIds>({
174174
}
175175
},
176176
'CallExpression:exit'(node: TSESTree.CallExpression) {
177-
if (!isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
177+
if (!isTypeOfJestFnCall(node, context, ['test'])) {
178178
return;
179179
}
180180

‎src/rules/prefer-hooks-in-order.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default createRule({
2828
return;
2929
}
3030

31-
const jestFnCall = parseJestFnCall(node, context.getScope());
31+
const jestFnCall = parseJestFnCall(node, context);
3232

3333
if (jestFnCall?.type !== 'hook') {
3434
// Reset the previousHookIndex when encountering something different from a hook
@@ -57,7 +57,7 @@ export default createRule({
5757
previousHookIndex = currentHookIndex;
5858
},
5959
'CallExpression:exit'(node) {
60-
if (isTypeOfJestFnCall(node, context.getScope(), ['hook'])) {
60+
if (isTypeOfJestFnCall(node, context, ['hook'])) {
6161
inHook = false;
6262

6363
return;

‎src/rules/prefer-hooks-on-top.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ export default createRule({
2020

2121
return {
2222
CallExpression(node) {
23-
const scope = context.getScope();
24-
25-
if (isTypeOfJestFnCall(node, scope, ['test'])) {
23+
if (isTypeOfJestFnCall(node, context, ['test'])) {
2624
hooksContext[hooksContext.length - 1] = true;
2725
}
2826
if (
2927
hooksContext[hooksContext.length - 1] &&
30-
isTypeOfJestFnCall(node, scope, ['hook'])
28+
isTypeOfJestFnCall(node, context, ['hook'])
3129
) {
3230
context.report({
3331
messageId: 'noHookOnTop',

‎src/rules/prefer-lowercase-title.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,7 @@ export default createRule<
104104

105105
return {
106106
CallExpression(node: TSESTree.CallExpression) {
107-
const scope = context.getScope();
108-
109-
const jestFnCall = parseJestFnCall(node, scope);
107+
const jestFnCall = parseJestFnCall(node, context);
110108

111109
if (!jestFnCall || !hasStringAsFirstArgument(node)) {
112110
return;
@@ -162,7 +160,7 @@ export default createRule<
162160
});
163161
},
164162
'CallExpression:exit'(node: TSESTree.CallExpression) {
165-
if (isTypeOfJestFnCall(node, context.getScope(), ['describe'])) {
163+
if (isTypeOfJestFnCall(node, context, ['describe'])) {
166164
numberOfDescribeBlocks--;
167165
}
168166
},

‎src/rules/prefer-snapshot-hint.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,13 @@ export default createRule<[('always' | 'multi')?], keyof typeof messages>({
106106
ArrowFunctionExpression: enterExpression,
107107
'ArrowFunctionExpression:exit': exitExpression,
108108
'CallExpression:exit'(node) {
109-
const scope = context.getScope();
110-
111-
if (isTypeOfJestFnCall(node, scope, ['describe', 'test'])) {
109+
if (isTypeOfJestFnCall(node, context, ['describe', 'test'])) {
112110
/* istanbul ignore next */
113111
expressionDepth = depths.pop() ?? 0;
114112
}
115113
},
116114
CallExpression(node) {
117-
const scope = context.getScope();
118-
119-
if (isTypeOfJestFnCall(node, scope, ['describe', 'test'])) {
115+
if (isTypeOfJestFnCall(node, context, ['describe', 'test'])) {
120116
depths.push(expressionDepth);
121117
expressionDepth = 0;
122118
}

‎src/rules/prefer-todo.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default createRule({
6969
CallExpression(node) {
7070
const [title, callback] = node.arguments;
7171

72-
const jestFnCall = parseJestFnCall(node, context.getScope());
72+
const jestFnCall = parseJestFnCall(node, context);
7373

7474
if (
7575
!title ||

‎src/rules/require-hook.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010

1111
const isJestFnCall = (
1212
node: TSESTree.CallExpression,
13-
scope: TSESLint.Scope.Scope,
13+
context: TSESLint.RuleContext<string, unknown[]>,
1414
): boolean => {
15-
if (parseJestFnCall(node, scope)) {
15+
if (parseJestFnCall(node, context)) {
1616
return true;
1717
}
1818

@@ -28,15 +28,15 @@ const isNullOrUndefined = (node: TSESTree.Expression): boolean => {
2828

2929
const shouldBeInHook = (
3030
node: TSESTree.Node,
31-
scope: TSESLint.Scope.Scope,
31+
context: TSESLint.RuleContext<string, unknown[]>,
3232
allowedFunctionCalls: readonly string[] = [],
3333
): boolean => {
3434
switch (node.type) {
3535
case AST_NODE_TYPES.ExpressionStatement:
36-
return shouldBeInHook(node.expression, scope, allowedFunctionCalls);
36+
return shouldBeInHook(node.expression, context, allowedFunctionCalls);
3737
case AST_NODE_TYPES.CallExpression:
3838
return !(
39-
isJestFnCall(node, scope) ||
39+
isJestFnCall(node, context) ||
4040
allowedFunctionCalls.includes(getNodeName(node) as string)
4141
);
4242
case AST_NODE_TYPES.VariableDeclaration: {
@@ -92,9 +92,7 @@ export default createRule<
9292

9393
const checkBlockBody = (body: TSESTree.BlockStatement['body']) => {
9494
for (const statement of body) {
95-
if (
96-
shouldBeInHook(statement, context.getScope(), allowedFunctionCalls)
97-
) {
95+
if (shouldBeInHook(statement, context, allowedFunctionCalls)) {
9896
context.report({
9997
node: statement,
10098
messageId: 'useHook',
@@ -109,7 +107,7 @@ export default createRule<
109107
},
110108
CallExpression(node) {
111109
if (
112-
!isTypeOfJestFnCall(node, context.getScope(), ['describe']) ||
110+
!isTypeOfJestFnCall(node, context, ['describe']) ||
113111
node.arguments.length < 2
114112
) {
115113
return;

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ export default createRule<
4444

4545
return {
4646
CallExpression(node) {
47-
const scope = context.getScope();
48-
49-
const jestFnCall = parseJestFnCall(node, scope);
47+
const jestFnCall = parseJestFnCall(node, context);
5048

5149
if (!jestFnCall) {
5250
return;
@@ -87,7 +85,7 @@ export default createRule<
8785
}
8886
},
8987
'CallExpression:exit'(node: TSESTree.CallExpression) {
90-
if (isTypeOfJestFnCall(node, context.getScope(), ['describe'])) {
88+
if (isTypeOfJestFnCall(node, context, ['describe'])) {
9189
numberOfDescribeBlocks--;
9290
}
9391
},

‎src/rules/utils/__tests__/parseJestFnCall.test.ts

+123-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const rule = createRule({
6161
defaultOptions: [],
6262
create: context => ({
6363
CallExpression(node) {
64-
const jestFnCall = parseJestFnCall(node, context.getScope());
64+
const jestFnCall = parseJestFnCall(node, context);
6565

6666
if (jestFnCall) {
6767
context.report({
@@ -331,6 +331,128 @@ ruleTester.run('cjs', rule, {
331331
invalid: [],
332332
});
333333

334+
ruleTester.run('global aliases', rule, {
335+
valid: [
336+
{
337+
code: 'xcontext("skip this please", () => {});',
338+
settings: { jest: { globalAliases: { describe: ['context'] } } },
339+
},
340+
],
341+
invalid: [
342+
{
343+
code: 'context("when there is an error", () => {})',
344+
errors: [
345+
{
346+
messageId: 'details' as const,
347+
data: expectedParsedJestFnCallResultData({
348+
name: 'describe',
349+
type: 'describe',
350+
head: {
351+
original: 'describe',
352+
local: 'context',
353+
type: 'global',
354+
node: 'context',
355+
},
356+
members: [],
357+
}),
358+
column: 1,
359+
line: 1,
360+
},
361+
],
362+
settings: { jest: { globalAliases: { describe: ['context'] } } },
363+
},
364+
{
365+
code: 'context.skip("when there is an error", () => {})',
366+
errors: [
367+
{
368+
messageId: 'details' as const,
369+
data: expectedParsedJestFnCallResultData({
370+
name: 'describe',
371+
type: 'describe',
372+
head: {
373+
original: 'describe',
374+
local: 'context',
375+
type: 'global',
376+
node: 'context',
377+
},
378+
members: ['skip'],
379+
}),
380+
column: 1,
381+
line: 1,
382+
},
383+
],
384+
settings: { jest: { globalAliases: { describe: ['context'] } } },
385+
},
386+
{
387+
code: dedent`
388+
context("when there is an error", () => {})
389+
xcontext("skip this please", () => {});
390+
`,
391+
errors: [
392+
{
393+
messageId: 'details' as const,
394+
data: expectedParsedJestFnCallResultData({
395+
name: 'xdescribe',
396+
type: 'describe',
397+
head: {
398+
original: 'xdescribe',
399+
local: 'xcontext',
400+
type: 'global',
401+
node: 'xcontext',
402+
},
403+
members: [],
404+
}),
405+
column: 1,
406+
line: 2,
407+
},
408+
],
409+
settings: { jest: { globalAliases: { xdescribe: ['xcontext'] } } },
410+
},
411+
{
412+
code: dedent`
413+
context("when there is an error", () => {})
414+
describe("when there is an error", () => {})
415+
xcontext("skip this please", () => {});
416+
`,
417+
errors: [
418+
{
419+
messageId: 'details' as const,
420+
data: expectedParsedJestFnCallResultData({
421+
name: 'describe',
422+
type: 'describe',
423+
head: {
424+
original: 'describe',
425+
local: 'context',
426+
type: 'global',
427+
node: 'context',
428+
},
429+
members: [],
430+
}),
431+
column: 1,
432+
line: 1,
433+
},
434+
{
435+
messageId: 'details' as const,
436+
data: expectedParsedJestFnCallResultData({
437+
name: 'describe',
438+
type: 'describe',
439+
head: {
440+
original: null,
441+
local: 'describe',
442+
type: 'global',
443+
node: 'describe',
444+
},
445+
members: [],
446+
}),
447+
column: 1,
448+
line: 2,
449+
},
450+
],
451+
settings: { jest: { globalAliases: { describe: ['context'] } } },
452+
},
453+
],
454+
});
455+
334456
ruleTester.run('typescript', rule, {
335457
valid: [
336458
{

‎src/rules/utils/misc.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export const isFunction = (node: TSESTree.Node): node is FunctionExpression =>
121121

122122
export const getTestCallExpressionsFromDeclaredVariables = (
123123
declaredVariables: readonly TSESLint.Scope.Variable[],
124-
scope: TSESLint.Scope.Scope,
124+
context: TSESLint.RuleContext<string, unknown[]>,
125125
): TSESTree.CallExpression[] => {
126126
return declaredVariables.reduce<TSESTree.CallExpression[]>(
127127
(acc, { references }) =>
@@ -131,7 +131,7 @@ export const getTestCallExpressionsFromDeclaredVariables = (
131131
.filter(
132132
(node): node is TSESTree.CallExpression =>
133133
node?.type === AST_NODE_TYPES.CallExpression &&
134-
isTypeOfJestFnCall(node, scope, ['test']),
134+
isTypeOfJestFnCall(node, context, ['test']),
135135
),
136136
),
137137
[],

‎src/rules/utils/parseJestFnCall.ts

+33-7
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import {
1313

1414
export const isTypeOfJestFnCall = (
1515
node: TSESTree.CallExpression,
16-
scope: TSESLint.Scope.Scope,
16+
context: TSESLint.RuleContext<string, unknown[]>,
1717
types: JestFnType[],
1818
): boolean => {
19-
const jestFnCall = parseJestFnCall(node, scope);
19+
const jestFnCall = parseJestFnCall(node, context);
2020

2121
return jestFnCall !== null && types.includes(jestFnCall.type);
2222
};
@@ -140,9 +140,35 @@ const ValidJestFnCallChains = [
140140
'xtest.failing',
141141
];
142142

143+
declare module '@typescript-eslint/utils/dist/ts-eslint' {
144+
export interface SharedConfigurationSettings {
145+
jest?: {
146+
globalAliases?: Record<string, string[]>;
147+
version?: number | string;
148+
};
149+
}
150+
}
151+
152+
const resolvePossibleAliasedGlobal = (
153+
global: string,
154+
context: TSESLint.RuleContext<string, unknown[]>,
155+
) => {
156+
const globalAliases = context.settings.jest?.globalAliases ?? {};
157+
158+
const alias = Object.entries(globalAliases).find(([, aliases]) =>
159+
aliases.includes(global),
160+
);
161+
162+
if (alias) {
163+
return alias[0];
164+
}
165+
166+
return null;
167+
};
168+
143169
export const parseJestFnCall = (
144170
node: TSESTree.CallExpression,
145-
scope: TSESLint.Scope.Scope,
171+
context: TSESLint.RuleContext<string, unknown[]>,
146172
): ParsedJestFnCall | null => {
147173
// ensure that we're at the "top" of the function call chain otherwise when
148174
// parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
@@ -190,7 +216,7 @@ export const parseJestFnCall = (
190216
return null;
191217
}
192218

193-
const resolved = resolveToJestFn(scope, getAccessorValue(first));
219+
const resolved = resolveToJestFn(context, getAccessorValue(first));
194220

195221
// we're not a jest function
196222
if (!resolved) {
@@ -364,10 +390,10 @@ interface ResolvedJestFn {
364390
}
365391

366392
const resolveToJestFn = (
367-
scope: TSESLint.Scope.Scope,
393+
context: TSESLint.RuleContext<string, unknown[]>,
368394
identifier: string,
369395
): ResolvedJestFn | null => {
370-
const references = collectReferences(scope);
396+
const references = collectReferences(context.getScope());
371397

372398
const maybeImport = references.imports.get(identifier);
373399

@@ -392,7 +418,7 @@ const resolveToJestFn = (
392418
}
393419

394420
return {
395-
original: null,
421+
original: resolvePossibleAliasedGlobal(identifier, context),
396422
local: identifier,
397423
type: 'global',
398424
};

‎src/rules/valid-describe-callback.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default createRule({
4141
create(context) {
4242
return {
4343
CallExpression(node) {
44-
const jestFnCall = parseJestFnCall(node, context.getScope());
44+
const jestFnCall = parseJestFnCall(node, context);
4545

4646
if (jestFnCall?.type !== 'describe') {
4747
return;

‎src/rules/valid-expect-in-promise.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ const findTopMostCallExpression = (
7070

7171
const isTestCaseCallWithCallbackArg = (
7272
node: TSESTree.CallExpression,
73-
scope: TSESLint.Scope.Scope,
73+
context: TSESLint.RuleContext<string, unknown[]>,
7474
): boolean => {
75-
const jestCallFn = parseJestFnCall(node, scope);
75+
const jestCallFn = parseJestFnCall(node, context);
7676

7777
if (jestCallFn?.type !== 'test') {
7878
return false;
@@ -324,7 +324,7 @@ const findFirstBlockBodyUp = (
324324

325325
const isDirectlyWithinTestCaseCall = (
326326
node: TSESTree.Node,
327-
scope: TSESLint.Scope.Scope,
327+
context: TSESLint.RuleContext<string, unknown[]>,
328328
): boolean => {
329329
let parent: TSESTree.Node['parent'] = node;
330330

@@ -334,7 +334,7 @@ const isDirectlyWithinTestCaseCall = (
334334

335335
return (
336336
parent?.type === AST_NODE_TYPES.CallExpression &&
337-
isTypeOfJestFnCall(parent, scope, ['test'])
337+
isTypeOfJestFnCall(parent, context, ['test'])
338338
);
339339
}
340340

@@ -390,7 +390,7 @@ export default createRule({
390390
CallExpression(node: TSESTree.CallExpression) {
391391
// there are too many ways that the done argument could be used with
392392
// promises that contain expect that would make the promise safe for us
393-
if (isTestCaseCallWithCallbackArg(node, context.getScope())) {
393+
if (isTestCaseCallWithCallbackArg(node, context)) {
394394
inTestCaseWithDoneCallback = true;
395395

396396
return;
@@ -415,7 +415,7 @@ export default createRule({
415415
// make promises containing expects safe in a test for us to be able to
416416
// accurately check, so we just bail out completely if it's present
417417
if (inTestCaseWithDoneCallback) {
418-
if (isTypeOfJestFnCall(node, context.getScope(), ['test'])) {
418+
if (isTypeOfJestFnCall(node, context, ['test'])) {
419419
inTestCaseWithDoneCallback = false;
420420
}
421421

@@ -442,10 +442,7 @@ export default createRule({
442442
// or our parent is not directly within the test case, we stop checking
443443
// because we're most likely in the body of a function being defined
444444
// within the test, which we can't track
445-
if (
446-
!parent ||
447-
!isDirectlyWithinTestCaseCall(parent, context.getScope())
448-
) {
445+
if (!parent || !isDirectlyWithinTestCaseCall(parent, context)) {
449446
return;
450447
}
451448

‎src/rules/valid-title.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,7 @@ export default createRule<[Options], MessageIds>({
179179

180180
return {
181181
CallExpression(node: TSESTree.CallExpression) {
182-
const scope = context.getScope();
183-
184-
const jestFnCall = parseJestFnCall(node, scope);
182+
const jestFnCall = parseJestFnCall(node, context);
185183

186184
if (jestFnCall?.type !== 'describe' && jestFnCall?.type !== 'test') {
187185
return;

0 commit comments

Comments
 (0)
Please sign in to comment.