Skip to content

Commit 684545f

Browse files
authored
fix: detect test modes in options objects (#937)
1 parent 48abf8d commit 684545f

7 files changed

Lines changed: 127 additions & 13 deletions

File tree

src/rules/no-disabled-tests.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { createEslintRule, getAccessorValue } from '../utils'
1+
import {
2+
createEslintRule,
3+
findVitestModeProperty,
4+
getAccessorValue,
5+
} from '../utils'
26
import { parseVitestFnCall, resolveScope } from '../utils/parse-vitest-fn-call'
37
import { getScope } from '../utils/scope'
48

@@ -61,13 +65,19 @@ export default createEslintRule<Options, MESSAGE_ID>({
6165
const skipMember = vitestFnCall.members.find(
6266
(s) => getAccessorValue(s) === 'skip',
6367
)
64-
if (vitestFnCall.name.startsWith('x') || skipMember !== undefined) {
68+
const skipProperty =
69+
vitestFnCall.type === 'describe' || vitestFnCall.type === 'test'
70+
? findVitestModeProperty(node, 'skip')
71+
: null
72+
const skipNode = skipMember ?? skipProperty?.key
73+
74+
if (vitestFnCall.name.startsWith('x') || skipNode) {
6575
context.report({
6676
messageId:
6777
vitestFnCall.type === 'describe'
6878
? 'disabledSuite'
6979
: 'disabledTest',
70-
node: skipMember ?? vitestFnCall.head.node,
80+
node: skipNode ?? vitestFnCall.head.node,
7181
})
7282
}
7383
},

src/rules/no-focused-tests.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { createEslintRule, getAccessorValue } from '../utils'
1+
import {
2+
createEslintRule,
3+
findVitestModeProperty,
4+
getAccessorValue,
5+
} from '../utils'
26
import { parseVitestFnCall } from '../utils/parse-vitest-fn-call'
37

48
export type MessageIds = 'noFocusedTests'
@@ -54,14 +58,27 @@ export default createEslintRule<Options, MessageIds>({
5458
(m) => getAccessorValue(m) === 'only',
5559
)
5660

57-
if (isTestOrDescribe && onlyNode) {
61+
const onlyProperty = findVitestModeProperty(node, 'only')
62+
const focusedNode = onlyNode ?? onlyProperty?.key
63+
64+
if (isTestOrDescribe && focusedNode) {
5865
context.report({
59-
node: onlyNode,
66+
node: focusedNode,
6067
messageId: 'noFocusedTests',
61-
fix: (fixer) =>
62-
fixable
63-
? fixer.removeRange([onlyNode.range[0] - 1, onlyNode.range[1]])
64-
: null,
68+
fix: (fixer) => {
69+
if (!fixable) return null
70+
71+
if (onlyNode)
72+
return fixer.removeRange([
73+
onlyNode.range[0] - 1,
74+
onlyNode.range[1],
75+
])
76+
77+
if (onlyProperty)
78+
return fixer.replaceText(onlyProperty.value, 'false')
79+
80+
return null
81+
},
6582
})
6683
}
6784
},

src/rules/warn-todo.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createEslintRule } from '../utils'
1+
import { createEslintRule, findVitestModeProperty } from '../utils'
22
import { parseVitestFnCall } from '../utils/parse-vitest-fn-call'
33

44
const RULE_NAME = 'warn-todo'
@@ -32,11 +32,14 @@ export default createEslintRule({
3232
(m) => m.type === 'Identifier' && m.name === 'todo',
3333
)
3434

35-
if (!todoMember) return
35+
const todoProperty = findVitestModeProperty(node, 'todo')
36+
const todoNode = todoMember ?? todoProperty?.key
37+
38+
if (!todoNode) return
3639

3740
context.report({
3841
messageId: 'warnTodo',
39-
node: todoMember,
42+
node: todoNode,
4043
})
4144
},
4245
}

src/utils/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,29 @@ export const getAccessorValue = <S extends string = string>(
147147
? accessor.name
148148
: getStringValue(accessor)
149149

150+
export const findVitestModeProperty = (
151+
node: TSESTree.CallExpression,
152+
mode: 'only' | 'skip' | 'todo',
153+
): TSESTree.Property | null => {
154+
const options = node.arguments[1]
155+
156+
if (options?.type !== AST_NODE_TYPES.ObjectExpression) return null
157+
158+
const property = options.properties.findLast(
159+
(property): property is TSESTree.Property =>
160+
property.type === AST_NODE_TYPES.Property &&
161+
isSupportedAccessor(property.key, mode),
162+
)
163+
164+
if (
165+
property?.value.type !== AST_NODE_TYPES.Literal ||
166+
property.value.value !== true
167+
)
168+
return null
169+
170+
return property
171+
}
172+
150173
/**
151174
* Gets the value of the given `StringNode`.
152175
*

tests/no-disabled-tests.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ruleTester.run(rule.name, rule, {
1010
'it.each("foo", () => {})',
1111
'it.concurrent("foo", function () {})',
1212
'test("foo", function () {})',
13+
'test("foo", { skip: false }, function () {})',
1314
'test.only("foo", function () {})',
1415
'test.concurrent("foo", function () {})',
1516
'describe[`${"skip"}`]("foo", function () {})',
@@ -53,6 +54,30 @@ ruleTester.run(rule.name, rule, {
5354
},
5455
],
5556
},
57+
{
58+
code: 'describe("foo", { skip: true }, function () {})',
59+
errors: [
60+
{
61+
column: 19,
62+
endColumn: 23,
63+
endLine: 1,
64+
line: 1,
65+
messageId: 'disabledSuite',
66+
},
67+
],
68+
},
69+
{
70+
code: 'test("foo", { skip: true }, function () {})',
71+
errors: [
72+
{
73+
column: 15,
74+
endColumn: 19,
75+
endLine: 1,
76+
line: 1,
77+
messageId: 'disabledTest',
78+
},
79+
],
80+
},
5681
{
5782
code: 'xtest("foo", function () {})',
5883
errors: [

tests/no-focused-tests.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ruleTester.run(rule.name, rule, {
88
'it.for([])("test", () => {});',
99

1010
'test("test", () => {});',
11+
'test("test", { only: false }, () => {});',
1112
'test.each([])("test", () => {});',
1213
'test.for([])("test", () => {});',
1314

@@ -84,6 +85,23 @@ ruleTester.run(rule.name, rule, {
8485
},
8586
],
8687
},
88+
{
89+
options: [
90+
{
91+
fixable: false,
92+
},
93+
],
94+
code: 'test("test", { only: true }, () => {});',
95+
errors: [
96+
{
97+
column: 16,
98+
endColumn: 20,
99+
endLine: 1,
100+
line: 1,
101+
messageId: 'noFocusedTests',
102+
},
103+
],
104+
},
87105
{
88106
options: [
89107
{
@@ -214,6 +232,19 @@ ruleTester.run(rule.name, rule, {
214232
],
215233
output: 'test("test", () => {});',
216234
},
235+
{
236+
code: 'test("test", { only: true }, () => {});',
237+
errors: [
238+
{
239+
column: 16,
240+
endColumn: 20,
241+
endLine: 1,
242+
line: 1,
243+
messageId: 'noFocusedTests',
244+
},
245+
],
246+
output: 'test("test", { only: false }, () => {});',
247+
},
217248
{
218249
code: 'it.only.each([])("test", () => {});',
219250
errors: [

tests/warn-todo.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ruleTester.run(rule.name, rule, {
77
'it("foo", function () {})',
88
'it.concurrent("foo", function () {})',
99
'test("foo", function () {})',
10+
'test("foo", { todo: false }, function () {})',
1011
'test.concurrent("foo", function () {})',
1112
'describe.only("foo", function () {})',
1213
'it.only("foo", function () {})',
@@ -25,6 +26,10 @@ ruleTester.run(rule.name, rule, {
2526
code: 'test.todo("foo", function () {})',
2627
errors: [{ messageId: 'warnTodo', column: 6, line: 1 }],
2728
},
29+
{
30+
code: 'test("foo", { todo: true }, function () {})',
31+
errors: [{ messageId: 'warnTodo', column: 15, line: 1 }],
32+
},
2833
{
2934
code: 'describe.todo.each([])("foo", function () {})',
3035
errors: [{ messageId: 'warnTodo', column: 10, line: 1 }],

0 commit comments

Comments
 (0)