Skip to content

Commit 58abd11

Browse files
feat(eslint-plugin-jest): add no-disabled-tests (#574)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent d7cf187 commit 58abd11

File tree

10 files changed

+602
-14
lines changed

10 files changed

+602
-14
lines changed

go.work.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632
1414
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
1515
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
1616
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
17+
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
1718
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
1819
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
1920
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
@@ -25,6 +26,7 @@ golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
2526
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
2627
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
2728
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
29+
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
2830
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
2931
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
3032
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
@@ -38,7 +40,12 @@ golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488 h1:3doPGa+Gg4snce233aC
3840
golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw=
3941
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
4042
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
43+
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
4144
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
45+
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
46+
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
47+
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
48+
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
4249
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
4350
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
4451
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=

internal/plugins/jest/all.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package jest
22

33
import (
4+
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/no_disabled_tests"
45
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/no_hooks"
56
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/valid_describe_callback"
67
"github.com/web-infra-dev/rslint/internal/rule"
78
)
89

910
func GetAllRules() []rule.Rule {
1011
return []rule.Rule{
12+
no_disabled_tests.NoDisabledTestsRule,
1113
no_hooks.NoHooksRule,
1214
valid_describe_callback.ValidDescribeCallbackRule,
1315
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package jest
22

33
import (
4+
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/no_disabled_tests"
45
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/valid_describe_callback"
56
"github.com/web-infra-dev/rslint/internal/rule"
67
)
78

89
func GetRecommendedRules() []rule.Rule {
910
return []rule.Rule{
11+
no_disabled_tests.NoDisabledTestsRule,
1012
valid_describe_callback.ValidDescribeCallbackRule,
1113
}
1214
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package no_disabled_tests
2+
3+
import (
4+
"slices"
5+
"strings"
6+
7+
"github.com/microsoft/typescript-go/shim/ast"
8+
"github.com/web-infra-dev/rslint/internal/plugins/jest/utils"
9+
"github.com/web-infra-dev/rslint/internal/rule"
10+
)
11+
12+
// Message Builder
13+
14+
func buildErrorMissingFunctionMessage() rule.RuleMessage {
15+
return rule.RuleMessage{
16+
Id: "missingFunction",
17+
Description: "Test is missing function argument",
18+
}
19+
}
20+
21+
func buildErrorSkippedTestMessage() rule.RuleMessage {
22+
return rule.RuleMessage{
23+
Id: "skippedTest",
24+
Description: "Tests should not be skipped",
25+
}
26+
}
27+
28+
func isPendingCall(node *ast.Node, ctx rule.RuleContext) bool {
29+
if node == nil || node.Kind != ast.KindCallExpression {
30+
return false
31+
}
32+
33+
callExpr := node.AsCallExpression()
34+
if callExpr == nil || callExpr.Expression == nil || callExpr.Expression.Kind != ast.KindIdentifier {
35+
return false
36+
}
37+
38+
identifier := callExpr.Expression.AsIdentifier()
39+
if identifier == nil || identifier.Text != "pending" {
40+
return false
41+
}
42+
43+
if ctx.TypeChecker == nil {
44+
return true
45+
}
46+
47+
symbol := ctx.TypeChecker.GetSymbolAtLocation(callExpr.Expression)
48+
if symbol == nil {
49+
return true
50+
}
51+
52+
for _, decl := range symbol.Declarations {
53+
if decl == nil {
54+
continue
55+
}
56+
if decl.Kind != ast.KindImportSpecifier {
57+
return false
58+
}
59+
60+
importDecl := utils.FindImportDeclaration(decl)
61+
if importDecl == nil || importDecl.ModuleSpecifier == nil {
62+
return false
63+
}
64+
65+
return importDecl.ModuleSpecifier.Text() == "@jest/globals"
66+
}
67+
68+
return true
69+
}
70+
71+
var NoDisabledTestsRule = rule.Rule{
72+
Name: "jest/no-disabled-tests",
73+
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
74+
return rule.RuleListeners{
75+
ast.KindCallExpression: func(node *ast.Node) {
76+
if isPendingCall(node, ctx) {
77+
ctx.ReportNode(node, buildErrorSkippedTestMessage())
78+
return
79+
}
80+
81+
jestFnCall := utils.ParseJestFnCall(node, ctx)
82+
if jestFnCall == nil ||
83+
jestFnCall.Kind != utils.JestFnTypeDescribe &&
84+
jestFnCall.Kind != utils.JestFnTypeTest {
85+
return
86+
}
87+
88+
if strings.HasPrefix(jestFnCall.Name, "x") ||
89+
slices.Contains(jestFnCall.Members, "skip") {
90+
ctx.ReportNode(node, buildErrorSkippedTestMessage())
91+
}
92+
93+
if jestFnCall.Kind == utils.JestFnTypeTest {
94+
if len(node.Arguments()) < 2 && !slices.Contains(jestFnCall.Members, "todo") {
95+
ctx.ReportNode(node, buildErrorMissingFunctionMessage())
96+
}
97+
}
98+
},
99+
}
100+
},
101+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# jest/no-disabled-tests
2+
3+
## Rule Details
4+
5+
Disallow disabled or incomplete Jest tests. This rule reports skipped suites/tests via `.skip` and `x*` aliases, disallows `pending()` in test bodies, and flags `it()` / `test()` calls that omit the callback function (except `test.todo(...)`). It helps prevent accidentally committing tests that are skipped or not actually executed.
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```javascript
10+
describe.skip('foo', () => {});
11+
it.skip('foo', () => {});
12+
test.skip('foo', () => {});
13+
14+
describe['skip']('bar', () => {});
15+
it['skip']('bar', () => {});
16+
test['skip']('bar', () => {});
17+
18+
xdescribe('foo', () => {});
19+
xit('foo', () => {});
20+
xtest('foo', () => {});
21+
22+
it('bar');
23+
test('bar');
24+
25+
it('foo', () => {
26+
pending();
27+
});
28+
```
29+
30+
Examples of **correct** code for this rule:
31+
32+
```javascript
33+
describe('foo', () => {});
34+
it('foo', () => {});
35+
test('foo', () => {});
36+
37+
describe.only('bar', () => {});
38+
it.only('bar', () => {});
39+
test.only('bar', () => {});
40+
```
41+
42+
## Limitations
43+
44+
The plugin looks at the literal function names within test code, so will not catch more complex examples of disabled tests, such as:
45+
46+
```javascript
47+
const testSkip = test.skip;
48+
testSkip('skipped test', () => {});
49+
50+
const myTest = test;
51+
myTest('does not have function body');
52+
```
53+
54+
## Original Documentation
55+
56+
- [jest/no-disabled-tests](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-disabled-tests.md)

0 commit comments

Comments
 (0)