eslint-plugin-playwright 1.1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -42
- package/dist/index.d.mts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +752 -18
- package/dist/index.mjs +846 -22
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -33,9 +33,12 @@ function isStringNode(node) {
|
|
|
33
33
|
function isPropertyAccessor(node, name) {
|
|
34
34
|
return getStringValue(node.property) === name;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function getTestNames(context) {
|
|
37
37
|
const aliases = context.settings.playwright?.globalAliases?.test ?? [];
|
|
38
|
-
|
|
38
|
+
return ["test", ...aliases];
|
|
39
|
+
}
|
|
40
|
+
function isTestIdentifier(context, node) {
|
|
41
|
+
const testNames = getTestNames(context);
|
|
39
42
|
const regex = new RegExp(`^(${testNames.join("|")})$`);
|
|
40
43
|
return isIdentifier(node, regex) || node.type === "MemberExpression" && isIdentifier(node.object, regex);
|
|
41
44
|
}
|
|
@@ -58,13 +61,33 @@ function findParent(node, type) {
|
|
|
58
61
|
return node.parent.type === type ? node.parent : findParent(node.parent, type);
|
|
59
62
|
}
|
|
60
63
|
function isTestCall(context, node, modifiers) {
|
|
61
|
-
return isTestIdentifier(context, node.callee) && !isDescribeCall(node) && (node.callee.type !== "MemberExpression" || !modifiers || modifiers?.includes(getStringValue(node.callee.property))) && node.arguments.length === 2 && [
|
|
62
|
-
node.arguments[1].type
|
|
63
|
-
);
|
|
64
|
+
return isTestIdentifier(context, node.callee) && !isDescribeCall(node) && (node.callee.type !== "MemberExpression" || !modifiers || modifiers?.includes(getStringValue(node.callee.property))) && node.arguments.length === 2 && isFunction(node.arguments[1]);
|
|
64
65
|
}
|
|
65
66
|
function isTestHook(context, node) {
|
|
66
67
|
return node.callee.type === "MemberExpression" && isTestIdentifier(context, node.callee.object) && testHooks.has(getStringValue(node.callee.property));
|
|
67
68
|
}
|
|
69
|
+
function parseFnCall(context, node) {
|
|
70
|
+
if (isTestCall(context, node)) {
|
|
71
|
+
return {
|
|
72
|
+
fn: node.arguments[1],
|
|
73
|
+
name: getStringValue(node.callee),
|
|
74
|
+
type: "test"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (node.callee.type === "MemberExpression" && isTestIdentifier(context, node.callee.object) && testHooks.has(getStringValue(node.callee.property))) {
|
|
78
|
+
return {
|
|
79
|
+
fn: node.arguments[0],
|
|
80
|
+
name: getStringValue(node.callee.property),
|
|
81
|
+
type: "hook"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (isDescribeCall(node)) {
|
|
85
|
+
return {
|
|
86
|
+
name: getStringValue(node.callee),
|
|
87
|
+
type: "describe"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
68
91
|
function getExpectType(context, node) {
|
|
69
92
|
const aliases = context.settings.playwright?.globalAliases?.expect ?? [];
|
|
70
93
|
const expectNames = ["expect", ...aliases];
|
|
@@ -99,7 +122,7 @@ function isPageMethod(node, name) {
|
|
|
99
122
|
function isFunction(node) {
|
|
100
123
|
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
101
124
|
}
|
|
102
|
-
var isTemplateLiteral, describeProperties, testHooks, expectSubCommands;
|
|
125
|
+
var isTemplateLiteral, describeProperties, testHooks, expectSubCommands, equalityMatchers;
|
|
103
126
|
var init_ast = __esm({
|
|
104
127
|
"src/utils/ast.ts"() {
|
|
105
128
|
"use strict";
|
|
@@ -114,6 +137,7 @@ var init_ast = __esm({
|
|
|
114
137
|
]);
|
|
115
138
|
testHooks = /* @__PURE__ */ new Set(["afterAll", "afterEach", "beforeAll", "beforeEach"]);
|
|
116
139
|
expectSubCommands = /* @__PURE__ */ new Set(["soft", "poll"]);
|
|
140
|
+
equalityMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
117
141
|
}
|
|
118
142
|
});
|
|
119
143
|
|
|
@@ -186,6 +210,76 @@ var init_expect_expect = __esm({
|
|
|
186
210
|
}
|
|
187
211
|
});
|
|
188
212
|
|
|
213
|
+
// src/rules/max-expects.ts
|
|
214
|
+
var max_expects_default;
|
|
215
|
+
var init_max_expects = __esm({
|
|
216
|
+
"src/rules/max-expects.ts"() {
|
|
217
|
+
"use strict";
|
|
218
|
+
init_ast();
|
|
219
|
+
max_expects_default = {
|
|
220
|
+
create(context) {
|
|
221
|
+
const options = {
|
|
222
|
+
max: 5,
|
|
223
|
+
...context.options?.[0] ?? {}
|
|
224
|
+
};
|
|
225
|
+
let count = 0;
|
|
226
|
+
const maybeResetCount = (node) => {
|
|
227
|
+
const parent = getParent(node);
|
|
228
|
+
const isTestFn = parent?.type !== "CallExpression" || isTestCall(context, parent);
|
|
229
|
+
if (isTestFn) {
|
|
230
|
+
count = 0;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
ArrowFunctionExpression: maybeResetCount,
|
|
235
|
+
"ArrowFunctionExpression:exit": maybeResetCount,
|
|
236
|
+
CallExpression(node) {
|
|
237
|
+
if (!getExpectType(context, node))
|
|
238
|
+
return;
|
|
239
|
+
count += 1;
|
|
240
|
+
if (count > options.max) {
|
|
241
|
+
context.report({
|
|
242
|
+
data: {
|
|
243
|
+
count: count.toString(),
|
|
244
|
+
max: options.max.toString()
|
|
245
|
+
},
|
|
246
|
+
messageId: "exceededMaxAssertion",
|
|
247
|
+
node
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
FunctionExpression: maybeResetCount,
|
|
252
|
+
"FunctionExpression:exit": maybeResetCount
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
meta: {
|
|
256
|
+
docs: {
|
|
257
|
+
category: "Best Practices",
|
|
258
|
+
description: "Enforces a maximum number assertion calls in a test body",
|
|
259
|
+
recommended: false,
|
|
260
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/max-expects.md"
|
|
261
|
+
},
|
|
262
|
+
messages: {
|
|
263
|
+
exceededMaxAssertion: "Too many assertion calls ({{ count }}) - maximum allowed is {{ max }}"
|
|
264
|
+
},
|
|
265
|
+
schema: [
|
|
266
|
+
{
|
|
267
|
+
additionalProperties: false,
|
|
268
|
+
properties: {
|
|
269
|
+
max: {
|
|
270
|
+
minimum: 1,
|
|
271
|
+
type: "integer"
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
type: "object"
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
type: "suggestion"
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
189
283
|
// src/rules/max-nested-describe.ts
|
|
190
284
|
var max_nested_describe_default;
|
|
191
285
|
var init_max_nested_describe = __esm({
|
|
@@ -407,6 +501,146 @@ var init_missing_playwright_await = __esm({
|
|
|
407
501
|
}
|
|
408
502
|
});
|
|
409
503
|
|
|
504
|
+
// src/rules/no-commented-out-tests.ts
|
|
505
|
+
function hasTests(context, node) {
|
|
506
|
+
const testNames = getTestNames(context);
|
|
507
|
+
const names = testNames.join("|");
|
|
508
|
+
const regex = new RegExp(
|
|
509
|
+
`^\\s*(${names}|describe)(\\.\\w+|\\[['"]\\w+['"]\\])?\\s*\\(`,
|
|
510
|
+
"mu"
|
|
511
|
+
);
|
|
512
|
+
return regex.test(node.value);
|
|
513
|
+
}
|
|
514
|
+
var no_commented_out_tests_default;
|
|
515
|
+
var init_no_commented_out_tests = __esm({
|
|
516
|
+
"src/rules/no-commented-out-tests.ts"() {
|
|
517
|
+
"use strict";
|
|
518
|
+
init_ast();
|
|
519
|
+
no_commented_out_tests_default = {
|
|
520
|
+
create(context) {
|
|
521
|
+
function checkNode(node) {
|
|
522
|
+
if (!hasTests(context, node))
|
|
523
|
+
return;
|
|
524
|
+
context.report({
|
|
525
|
+
messageId: "commentedTests",
|
|
526
|
+
node
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
Program() {
|
|
531
|
+
context.sourceCode.getAllComments().forEach(checkNode);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
},
|
|
535
|
+
meta: {
|
|
536
|
+
docs: {
|
|
537
|
+
category: "Best Practices",
|
|
538
|
+
description: "Disallow commented out tests",
|
|
539
|
+
recommended: true,
|
|
540
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-commented-out-tests.md"
|
|
541
|
+
},
|
|
542
|
+
messages: {
|
|
543
|
+
commentedTests: "Some tests seem to be commented"
|
|
544
|
+
},
|
|
545
|
+
type: "problem"
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// src/rules/no-conditional-expect.ts
|
|
552
|
+
var isCatchCall, getTestCallExpressionsFromDeclaredVariables, no_conditional_expect_default;
|
|
553
|
+
var init_no_conditional_expect = __esm({
|
|
554
|
+
"src/rules/no-conditional-expect.ts"() {
|
|
555
|
+
"use strict";
|
|
556
|
+
init_ast();
|
|
557
|
+
isCatchCall = (node) => node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "catch");
|
|
558
|
+
getTestCallExpressionsFromDeclaredVariables = (context, declaredVariables) => {
|
|
559
|
+
return declaredVariables.reduce(
|
|
560
|
+
(acc, { references }) => [
|
|
561
|
+
...acc,
|
|
562
|
+
...references.map(({ identifier }) => getParent(identifier)).filter(
|
|
563
|
+
(node) => node?.type === "CallExpression" && isTestCall(context, node)
|
|
564
|
+
)
|
|
565
|
+
],
|
|
566
|
+
[]
|
|
567
|
+
);
|
|
568
|
+
};
|
|
569
|
+
no_conditional_expect_default = {
|
|
570
|
+
create(context) {
|
|
571
|
+
let conditionalDepth = 0;
|
|
572
|
+
let inTestCase = false;
|
|
573
|
+
let inPromiseCatch = false;
|
|
574
|
+
const increaseConditionalDepth = () => inTestCase && conditionalDepth++;
|
|
575
|
+
const decreaseConditionalDepth = () => inTestCase && conditionalDepth--;
|
|
576
|
+
return {
|
|
577
|
+
CallExpression(node) {
|
|
578
|
+
if (isTestCall(context, node)) {
|
|
579
|
+
inTestCase = true;
|
|
580
|
+
}
|
|
581
|
+
if (isCatchCall(node)) {
|
|
582
|
+
inPromiseCatch = true;
|
|
583
|
+
}
|
|
584
|
+
const expectType = getExpectType(context, node);
|
|
585
|
+
if (inTestCase && expectType && conditionalDepth > 0) {
|
|
586
|
+
context.report({
|
|
587
|
+
messageId: "conditionalExpect",
|
|
588
|
+
node
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (inPromiseCatch && expectType) {
|
|
592
|
+
context.report({
|
|
593
|
+
messageId: "conditionalExpect",
|
|
594
|
+
node
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
"CallExpression:exit"(node) {
|
|
599
|
+
if (isTestCall(context, node)) {
|
|
600
|
+
inTestCase = false;
|
|
601
|
+
}
|
|
602
|
+
if (isCatchCall(node)) {
|
|
603
|
+
inPromiseCatch = false;
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
CatchClause: increaseConditionalDepth,
|
|
607
|
+
"CatchClause:exit": decreaseConditionalDepth,
|
|
608
|
+
ConditionalExpression: increaseConditionalDepth,
|
|
609
|
+
"ConditionalExpression:exit": decreaseConditionalDepth,
|
|
610
|
+
FunctionDeclaration(node) {
|
|
611
|
+
const declaredVariables = context.sourceCode.getDeclaredVariables(node);
|
|
612
|
+
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
|
|
613
|
+
context,
|
|
614
|
+
declaredVariables
|
|
615
|
+
);
|
|
616
|
+
if (testCallExpressions.length > 0) {
|
|
617
|
+
inTestCase = true;
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
IfStatement: increaseConditionalDepth,
|
|
621
|
+
"IfStatement:exit": decreaseConditionalDepth,
|
|
622
|
+
LogicalExpression: increaseConditionalDepth,
|
|
623
|
+
"LogicalExpression:exit": decreaseConditionalDepth,
|
|
624
|
+
SwitchStatement: increaseConditionalDepth,
|
|
625
|
+
"SwitchStatement:exit": decreaseConditionalDepth
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
meta: {
|
|
629
|
+
docs: {
|
|
630
|
+
category: "Best Practices",
|
|
631
|
+
description: "Disallow calling `expect` conditionally",
|
|
632
|
+
recommended: true,
|
|
633
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-expect.md"
|
|
634
|
+
},
|
|
635
|
+
messages: {
|
|
636
|
+
conditionalExpect: "Avoid calling `expect` conditionally`"
|
|
637
|
+
},
|
|
638
|
+
type: "problem"
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
410
644
|
// src/rules/no-conditional-in-test.ts
|
|
411
645
|
var no_conditional_in_test_default;
|
|
412
646
|
var init_no_conditional_in_test = __esm({
|
|
@@ -445,6 +679,58 @@ var init_no_conditional_in_test = __esm({
|
|
|
445
679
|
}
|
|
446
680
|
});
|
|
447
681
|
|
|
682
|
+
// src/rules/no-duplicate-hooks.ts
|
|
683
|
+
var no_duplicate_hooks_default;
|
|
684
|
+
var init_no_duplicate_hooks = __esm({
|
|
685
|
+
"src/rules/no-duplicate-hooks.ts"() {
|
|
686
|
+
"use strict";
|
|
687
|
+
init_ast();
|
|
688
|
+
no_duplicate_hooks_default = {
|
|
689
|
+
create(context) {
|
|
690
|
+
const hookContexts = [{}];
|
|
691
|
+
return {
|
|
692
|
+
CallExpression(node) {
|
|
693
|
+
if (isDescribeCall(node)) {
|
|
694
|
+
hookContexts.push({});
|
|
695
|
+
}
|
|
696
|
+
if (!isTestHook(context, node)) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const currentLayer = hookContexts[hookContexts.length - 1];
|
|
700
|
+
const name = node.callee.type === "MemberExpression" ? getStringValue(node.callee.property) : "";
|
|
701
|
+
currentLayer[name] || (currentLayer[name] = 0);
|
|
702
|
+
currentLayer[name] += 1;
|
|
703
|
+
if (currentLayer[name] > 1) {
|
|
704
|
+
context.report({
|
|
705
|
+
data: { hook: name },
|
|
706
|
+
messageId: "noDuplicateHook",
|
|
707
|
+
node
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
"CallExpression:exit"(node) {
|
|
712
|
+
if (isDescribeCall(node)) {
|
|
713
|
+
hookContexts.pop();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
},
|
|
718
|
+
meta: {
|
|
719
|
+
docs: {
|
|
720
|
+
category: "Best Practices",
|
|
721
|
+
description: "Disallow duplicate setup and teardown hooks",
|
|
722
|
+
recommended: false,
|
|
723
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-duplicate-hooks.md"
|
|
724
|
+
},
|
|
725
|
+
messages: {
|
|
726
|
+
noDuplicateHook: "Duplicate {{ hook }} in describe block"
|
|
727
|
+
},
|
|
728
|
+
type: "suggestion"
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
448
734
|
// src/rules/no-element-handle.ts
|
|
449
735
|
function getPropertyRange(node) {
|
|
450
736
|
return node.type === "Identifier" ? node.range : [node.range[0] + 1, node.range[1] - 1];
|
|
@@ -680,6 +966,59 @@ var init_no_get_by_title = __esm({
|
|
|
680
966
|
}
|
|
681
967
|
});
|
|
682
968
|
|
|
969
|
+
// src/rules/no-hooks.ts
|
|
970
|
+
var no_hooks_default;
|
|
971
|
+
var init_no_hooks = __esm({
|
|
972
|
+
"src/rules/no-hooks.ts"() {
|
|
973
|
+
"use strict";
|
|
974
|
+
init_ast();
|
|
975
|
+
no_hooks_default = {
|
|
976
|
+
create(context) {
|
|
977
|
+
const options = {
|
|
978
|
+
allow: [],
|
|
979
|
+
...context.options?.[0] ?? {}
|
|
980
|
+
};
|
|
981
|
+
return {
|
|
982
|
+
CallExpression(node) {
|
|
983
|
+
const call = parseFnCall(context, node);
|
|
984
|
+
if (call?.type === "hook" && !options.allow.includes(call.name)) {
|
|
985
|
+
context.report({
|
|
986
|
+
data: { hookName: call.name },
|
|
987
|
+
messageId: "unexpectedHook",
|
|
988
|
+
node
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
},
|
|
994
|
+
meta: {
|
|
995
|
+
docs: {
|
|
996
|
+
category: "Best Practices",
|
|
997
|
+
description: "Disallow setup and teardown hooks",
|
|
998
|
+
recommended: false,
|
|
999
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-hooks.md"
|
|
1000
|
+
},
|
|
1001
|
+
messages: {
|
|
1002
|
+
unexpectedHook: "Unexpected '{{ hookName }}' hook"
|
|
1003
|
+
},
|
|
1004
|
+
schema: [
|
|
1005
|
+
{
|
|
1006
|
+
additionalProperties: false,
|
|
1007
|
+
properties: {
|
|
1008
|
+
allow: {
|
|
1009
|
+
contains: ["beforeAll", "beforeEach", "afterAll", "afterEach"],
|
|
1010
|
+
type: "array"
|
|
1011
|
+
}
|
|
1012
|
+
},
|
|
1013
|
+
type: "object"
|
|
1014
|
+
}
|
|
1015
|
+
],
|
|
1016
|
+
type: "suggestion"
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
|
|
683
1022
|
// src/rules/no-nested-step.ts
|
|
684
1023
|
function isStepCall(node) {
|
|
685
1024
|
const inner = node.type === "CallExpression" ? node.callee : node;
|
|
@@ -1104,6 +1443,102 @@ var init_no_skipped_test = __esm({
|
|
|
1104
1443
|
}
|
|
1105
1444
|
});
|
|
1106
1445
|
|
|
1446
|
+
// src/rules/no-standalone-expect.ts
|
|
1447
|
+
var getBlockType, no_standalone_expect_default;
|
|
1448
|
+
var init_no_standalone_expect = __esm({
|
|
1449
|
+
"src/rules/no-standalone-expect.ts"() {
|
|
1450
|
+
"use strict";
|
|
1451
|
+
init_ast();
|
|
1452
|
+
getBlockType = (statement) => {
|
|
1453
|
+
const func = getParent(statement);
|
|
1454
|
+
if (!func) {
|
|
1455
|
+
throw new Error(
|
|
1456
|
+
`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/playwright-community/eslint-plugin-playwright`
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
if (func.type === "FunctionDeclaration") {
|
|
1460
|
+
return "function";
|
|
1461
|
+
}
|
|
1462
|
+
if (isFunction(func) && func.parent) {
|
|
1463
|
+
const expr = func.parent;
|
|
1464
|
+
if (expr.type === "VariableDeclarator" || expr.type === "MethodDefinition") {
|
|
1465
|
+
return "function";
|
|
1466
|
+
}
|
|
1467
|
+
if (expr.type === "CallExpression" && isDescribeCall(expr)) {
|
|
1468
|
+
return "describe";
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
return null;
|
|
1472
|
+
};
|
|
1473
|
+
no_standalone_expect_default = {
|
|
1474
|
+
create(context) {
|
|
1475
|
+
const callStack = [];
|
|
1476
|
+
return {
|
|
1477
|
+
ArrowFunctionExpression(node) {
|
|
1478
|
+
if (node.parent?.type !== "CallExpression") {
|
|
1479
|
+
callStack.push("arrow");
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
"ArrowFunctionExpression:exit"() {
|
|
1483
|
+
if (callStack[callStack.length - 1] === "arrow") {
|
|
1484
|
+
callStack.pop();
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
BlockStatement(statement) {
|
|
1488
|
+
const blockType = getBlockType(statement);
|
|
1489
|
+
if (blockType) {
|
|
1490
|
+
callStack.push(blockType);
|
|
1491
|
+
}
|
|
1492
|
+
},
|
|
1493
|
+
"BlockStatement:exit"(statement) {
|
|
1494
|
+
if (callStack[callStack.length - 1] === getBlockType(statement)) {
|
|
1495
|
+
callStack.pop();
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
CallExpression(node) {
|
|
1499
|
+
if (getExpectType(context, node)) {
|
|
1500
|
+
const parent = callStack.at(-1);
|
|
1501
|
+
if (!parent || parent === "describe") {
|
|
1502
|
+
const root = findParent(node, "CallExpression");
|
|
1503
|
+
context.report({
|
|
1504
|
+
messageId: "unexpectedExpect",
|
|
1505
|
+
node: root ?? node
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
if (isTestCall(context, node)) {
|
|
1511
|
+
callStack.push("test");
|
|
1512
|
+
}
|
|
1513
|
+
if (node.callee.type === "TaggedTemplateExpression") {
|
|
1514
|
+
callStack.push("template");
|
|
1515
|
+
}
|
|
1516
|
+
},
|
|
1517
|
+
"CallExpression:exit"(node) {
|
|
1518
|
+
const top = callStack[callStack.length - 1];
|
|
1519
|
+
if (top === "test" && isTestCall(context, node) && node.callee.type !== "MemberExpression" || top === "template" && node.callee.type === "TaggedTemplateExpression") {
|
|
1520
|
+
callStack.pop();
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
},
|
|
1525
|
+
meta: {
|
|
1526
|
+
docs: {
|
|
1527
|
+
category: "Best Practices",
|
|
1528
|
+
description: "Disallow using `expect` outside of `test` blocks",
|
|
1529
|
+
recommended: false,
|
|
1530
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md"
|
|
1531
|
+
},
|
|
1532
|
+
fixable: "code",
|
|
1533
|
+
messages: {
|
|
1534
|
+
unexpectedExpect: "Expect must be inside of a test block"
|
|
1535
|
+
},
|
|
1536
|
+
type: "suggestion"
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1107
1542
|
// src/utils/misc.ts
|
|
1108
1543
|
var getAmountData, truthy;
|
|
1109
1544
|
var init_misc = __esm({
|
|
@@ -1476,6 +1911,288 @@ var init_no_wait_for_timeout = __esm({
|
|
|
1476
1911
|
}
|
|
1477
1912
|
});
|
|
1478
1913
|
|
|
1914
|
+
// src/rules/prefer-comparison-matcher.ts
|
|
1915
|
+
var isString, isComparingToString, invertedOperators, operatorMatcher, determineMatcher, prefer_comparison_matcher_default;
|
|
1916
|
+
var init_prefer_comparison_matcher = __esm({
|
|
1917
|
+
"src/rules/prefer-comparison-matcher.ts"() {
|
|
1918
|
+
"use strict";
|
|
1919
|
+
init_ast();
|
|
1920
|
+
init_parseExpectCall();
|
|
1921
|
+
isString = (node) => {
|
|
1922
|
+
return isStringLiteral(node) || node.type === "TemplateLiteral";
|
|
1923
|
+
};
|
|
1924
|
+
isComparingToString = (expression) => {
|
|
1925
|
+
return isString(expression.left) || isString(expression.right);
|
|
1926
|
+
};
|
|
1927
|
+
invertedOperators = {
|
|
1928
|
+
"<": ">=",
|
|
1929
|
+
"<=": ">",
|
|
1930
|
+
">": "<=",
|
|
1931
|
+
">=": "<"
|
|
1932
|
+
};
|
|
1933
|
+
operatorMatcher = {
|
|
1934
|
+
"<": "toBeLessThan",
|
|
1935
|
+
"<=": "toBeLessThanOrEqual",
|
|
1936
|
+
">": "toBeGreaterThan",
|
|
1937
|
+
">=": "toBeGreaterThanOrEqual"
|
|
1938
|
+
};
|
|
1939
|
+
determineMatcher = (operator, negated) => {
|
|
1940
|
+
const op = negated ? invertedOperators[operator] : operator;
|
|
1941
|
+
return operatorMatcher[op] ?? null;
|
|
1942
|
+
};
|
|
1943
|
+
prefer_comparison_matcher_default = {
|
|
1944
|
+
create(context) {
|
|
1945
|
+
return {
|
|
1946
|
+
CallExpression(node) {
|
|
1947
|
+
const expectCall = parseExpectCall(context, node);
|
|
1948
|
+
if (!expectCall || expectCall.args.length === 0)
|
|
1949
|
+
return;
|
|
1950
|
+
const { args, matcher } = expectCall;
|
|
1951
|
+
const [comparison] = node.arguments;
|
|
1952
|
+
const expectCallEnd = node.range[1];
|
|
1953
|
+
const [matcherArg] = args;
|
|
1954
|
+
if (comparison?.type !== "BinaryExpression" || isComparingToString(comparison) || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
const hasNot = expectCall.modifiers.some(
|
|
1958
|
+
(node2) => getStringValue(node2) === "not"
|
|
1959
|
+
);
|
|
1960
|
+
const preferredMatcher = determineMatcher(
|
|
1961
|
+
comparison.operator,
|
|
1962
|
+
getRawValue(matcherArg) === hasNot.toString()
|
|
1963
|
+
);
|
|
1964
|
+
if (!preferredMatcher) {
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
context.report({
|
|
1968
|
+
data: { preferredMatcher },
|
|
1969
|
+
fix(fixer) {
|
|
1970
|
+
const [modifier] = expectCall.modifiers;
|
|
1971
|
+
const modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
|
|
1972
|
+
return [
|
|
1973
|
+
// Replace the comparison argument with the left-hand side of the comparison
|
|
1974
|
+
fixer.replaceText(
|
|
1975
|
+
comparison,
|
|
1976
|
+
context.sourceCode.getText(comparison.left)
|
|
1977
|
+
),
|
|
1978
|
+
// Replace the current matcher & modifier with the preferred matcher
|
|
1979
|
+
fixer.replaceTextRange(
|
|
1980
|
+
[expectCallEnd, getParent(matcher).range[1]],
|
|
1981
|
+
`${modifierText}.${preferredMatcher}`
|
|
1982
|
+
),
|
|
1983
|
+
// Replace the matcher argument with the right-hand side of the comparison
|
|
1984
|
+
fixer.replaceText(
|
|
1985
|
+
matcherArg,
|
|
1986
|
+
context.sourceCode.getText(comparison.right)
|
|
1987
|
+
)
|
|
1988
|
+
];
|
|
1989
|
+
},
|
|
1990
|
+
messageId: "useToBeComparison",
|
|
1991
|
+
node: matcher
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
},
|
|
1996
|
+
meta: {
|
|
1997
|
+
docs: {
|
|
1998
|
+
category: "Best Practices",
|
|
1999
|
+
description: "Suggest using the built-in comparison matchers",
|
|
2000
|
+
recommended: false
|
|
2001
|
+
},
|
|
2002
|
+
fixable: "code",
|
|
2003
|
+
messages: {
|
|
2004
|
+
useToBeComparison: "Prefer using `{{ preferredMatcher }}` instead"
|
|
2005
|
+
},
|
|
2006
|
+
type: "suggestion"
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
});
|
|
2011
|
+
|
|
2012
|
+
// src/rules/prefer-equality-matcher.ts
|
|
2013
|
+
var prefer_equality_matcher_default;
|
|
2014
|
+
var init_prefer_equality_matcher = __esm({
|
|
2015
|
+
"src/rules/prefer-equality-matcher.ts"() {
|
|
2016
|
+
"use strict";
|
|
2017
|
+
init_ast();
|
|
2018
|
+
init_parseExpectCall();
|
|
2019
|
+
prefer_equality_matcher_default = {
|
|
2020
|
+
create(context) {
|
|
2021
|
+
return {
|
|
2022
|
+
CallExpression(node) {
|
|
2023
|
+
const expectCall = parseExpectCall(context, node);
|
|
2024
|
+
if (!expectCall || expectCall.args.length === 0)
|
|
2025
|
+
return;
|
|
2026
|
+
const { args, matcher } = expectCall;
|
|
2027
|
+
const [comparison] = node.arguments;
|
|
2028
|
+
const expectCallEnd = node.range[1];
|
|
2029
|
+
const [matcherArg] = args;
|
|
2030
|
+
if (comparison?.type !== "BinaryExpression" || comparison.operator !== "===" && comparison.operator !== "!==" || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
const matcherValue = getRawValue(matcherArg) === "true";
|
|
2034
|
+
const [modifier] = expectCall.modifiers;
|
|
2035
|
+
const hasNot = expectCall.modifiers.some(
|
|
2036
|
+
(node2) => getStringValue(node2) === "not"
|
|
2037
|
+
);
|
|
2038
|
+
const addNotModifier = (comparison.operator === "!==" ? !matcherValue : matcherValue) === hasNot;
|
|
2039
|
+
context.report({
|
|
2040
|
+
messageId: "useEqualityMatcher",
|
|
2041
|
+
node: matcher,
|
|
2042
|
+
suggest: [...equalityMatchers.keys()].map((equalityMatcher) => ({
|
|
2043
|
+
data: { matcher: equalityMatcher },
|
|
2044
|
+
fix(fixer) {
|
|
2045
|
+
let modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
|
|
2046
|
+
if (addNotModifier) {
|
|
2047
|
+
modifierText += `.not`;
|
|
2048
|
+
}
|
|
2049
|
+
return [
|
|
2050
|
+
// replace the comparison argument with the left-hand side of the comparison
|
|
2051
|
+
fixer.replaceText(
|
|
2052
|
+
comparison,
|
|
2053
|
+
context.sourceCode.getText(comparison.left)
|
|
2054
|
+
),
|
|
2055
|
+
// replace the current matcher & modifier with the preferred matcher
|
|
2056
|
+
fixer.replaceTextRange(
|
|
2057
|
+
[expectCallEnd, getParent(matcher).range[1]],
|
|
2058
|
+
`${modifierText}.${equalityMatcher}`
|
|
2059
|
+
),
|
|
2060
|
+
// replace the matcher argument with the right-hand side of the comparison
|
|
2061
|
+
fixer.replaceText(
|
|
2062
|
+
matcherArg,
|
|
2063
|
+
context.sourceCode.getText(comparison.right)
|
|
2064
|
+
)
|
|
2065
|
+
];
|
|
2066
|
+
},
|
|
2067
|
+
messageId: "suggestEqualityMatcher"
|
|
2068
|
+
}))
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
},
|
|
2073
|
+
meta: {
|
|
2074
|
+
docs: {
|
|
2075
|
+
category: "Best Practices",
|
|
2076
|
+
description: "Suggest using the built-in equality matchers",
|
|
2077
|
+
recommended: false,
|
|
2078
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md"
|
|
2079
|
+
},
|
|
2080
|
+
hasSuggestions: true,
|
|
2081
|
+
messages: {
|
|
2082
|
+
suggestEqualityMatcher: "Use `{{ matcher }}`",
|
|
2083
|
+
useEqualityMatcher: "Prefer using one of the equality matchers instead"
|
|
2084
|
+
},
|
|
2085
|
+
type: "suggestion"
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
|
|
2091
|
+
// src/rules/prefer-hooks-in-order.ts
|
|
2092
|
+
var HooksOrder, prefer_hooks_in_order_default;
|
|
2093
|
+
var init_prefer_hooks_in_order = __esm({
|
|
2094
|
+
"src/rules/prefer-hooks-in-order.ts"() {
|
|
2095
|
+
"use strict";
|
|
2096
|
+
init_ast();
|
|
2097
|
+
HooksOrder = ["beforeAll", "beforeEach", "afterEach", "afterAll"];
|
|
2098
|
+
prefer_hooks_in_order_default = {
|
|
2099
|
+
create(context) {
|
|
2100
|
+
let previousHookIndex = -1;
|
|
2101
|
+
let inHook = false;
|
|
2102
|
+
return {
|
|
2103
|
+
CallExpression(node) {
|
|
2104
|
+
if (inHook)
|
|
2105
|
+
return;
|
|
2106
|
+
if (!isTestHook(context, node)) {
|
|
2107
|
+
previousHookIndex = -1;
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
inHook = true;
|
|
2111
|
+
const currentHook = node.callee.type === "MemberExpression" ? getStringValue(node.callee.property) : "";
|
|
2112
|
+
const currentHookIndex = HooksOrder.indexOf(currentHook);
|
|
2113
|
+
if (currentHookIndex < previousHookIndex) {
|
|
2114
|
+
return context.report({
|
|
2115
|
+
data: {
|
|
2116
|
+
currentHook,
|
|
2117
|
+
previousHook: HooksOrder[previousHookIndex]
|
|
2118
|
+
},
|
|
2119
|
+
messageId: "reorderHooks",
|
|
2120
|
+
node
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
previousHookIndex = currentHookIndex;
|
|
2124
|
+
},
|
|
2125
|
+
"CallExpression:exit"(node) {
|
|
2126
|
+
if (isTestHook(context, node)) {
|
|
2127
|
+
inHook = false;
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
if (inHook) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
previousHookIndex = -1;
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
},
|
|
2137
|
+
meta: {
|
|
2138
|
+
docs: {
|
|
2139
|
+
category: "Best Practices",
|
|
2140
|
+
description: "Prefer having hooks in a consistent order",
|
|
2141
|
+
recommended: false,
|
|
2142
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md"
|
|
2143
|
+
},
|
|
2144
|
+
messages: {
|
|
2145
|
+
reorderHooks: "`{{ currentHook }}` hooks should be before any `{{ previousHook }}` hooks"
|
|
2146
|
+
},
|
|
2147
|
+
type: "suggestion"
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
// src/rules/prefer-hooks-on-top.ts
|
|
2154
|
+
var prefer_hooks_on_top_default;
|
|
2155
|
+
var init_prefer_hooks_on_top = __esm({
|
|
2156
|
+
"src/rules/prefer-hooks-on-top.ts"() {
|
|
2157
|
+
"use strict";
|
|
2158
|
+
init_ast();
|
|
2159
|
+
prefer_hooks_on_top_default = {
|
|
2160
|
+
create(context) {
|
|
2161
|
+
const stack = [false];
|
|
2162
|
+
return {
|
|
2163
|
+
CallExpression(node) {
|
|
2164
|
+
if (isTestCall(context, node)) {
|
|
2165
|
+
stack[stack.length - 1] = true;
|
|
2166
|
+
}
|
|
2167
|
+
if (stack.at(-1) && isTestHook(context, node)) {
|
|
2168
|
+
context.report({
|
|
2169
|
+
messageId: "noHookOnTop",
|
|
2170
|
+
node
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
stack.push(false);
|
|
2174
|
+
},
|
|
2175
|
+
"CallExpression:exit"() {
|
|
2176
|
+
stack.pop();
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
},
|
|
2180
|
+
meta: {
|
|
2181
|
+
docs: {
|
|
2182
|
+
category: "Best Practices",
|
|
2183
|
+
description: "Suggest having hooks before any test cases",
|
|
2184
|
+
recommended: false,
|
|
2185
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md"
|
|
2186
|
+
},
|
|
2187
|
+
messages: {
|
|
2188
|
+
noHookOnTop: "Hooks should come before test cases"
|
|
2189
|
+
},
|
|
2190
|
+
type: "suggestion"
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
});
|
|
2195
|
+
|
|
1479
2196
|
// src/rules/prefer-lowercase-title.ts
|
|
1480
2197
|
var prefer_lowercase_title_default;
|
|
1481
2198
|
var init_prefer_lowercase_title = __esm({
|
|
@@ -1685,9 +2402,8 @@ var init_prefer_to_be = __esm({
|
|
|
1685
2402
|
notModifier
|
|
1686
2403
|
);
|
|
1687
2404
|
}
|
|
1688
|
-
const argumentMatchers = ["toBe", "toEqual", "toStrictEqual"];
|
|
1689
2405
|
const firstArg = expectCall.args[0];
|
|
1690
|
-
if (!
|
|
2406
|
+
if (!equalityMatchers.has(expectCall.matcherName) || !firstArg) {
|
|
1691
2407
|
return;
|
|
1692
2408
|
}
|
|
1693
2409
|
if (firstArg.type === "Literal" && firstArg.value === null) {
|
|
@@ -1729,13 +2445,12 @@ var init_prefer_to_be = __esm({
|
|
|
1729
2445
|
});
|
|
1730
2446
|
|
|
1731
2447
|
// src/rules/prefer-to-contain.ts
|
|
1732
|
-
var
|
|
2448
|
+
var isFixableIncludesCallExpression, prefer_to_contain_default;
|
|
1733
2449
|
var init_prefer_to_contain = __esm({
|
|
1734
2450
|
"src/rules/prefer-to-contain.ts"() {
|
|
1735
2451
|
"use strict";
|
|
1736
2452
|
init_ast();
|
|
1737
2453
|
init_parseExpectCall();
|
|
1738
|
-
matchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
1739
2454
|
isFixableIncludesCallExpression = (node) => node.type === "CallExpression" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "includes") && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement";
|
|
1740
2455
|
prefer_to_contain_default = {
|
|
1741
2456
|
create(context) {
|
|
@@ -1747,7 +2462,7 @@ var init_prefer_to_contain = __esm({
|
|
|
1747
2462
|
const { args, matcher, matcherName } = expectCall;
|
|
1748
2463
|
const [includesCall] = node.arguments;
|
|
1749
2464
|
const [matcherArg] = args;
|
|
1750
|
-
if (!includesCall || matcherArg.type === "SpreadElement" || !
|
|
2465
|
+
if (!includesCall || matcherArg.type === "SpreadElement" || !equalityMatchers.has(matcherName) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
|
|
1751
2466
|
return;
|
|
1752
2467
|
}
|
|
1753
2468
|
const notModifier = expectCall.modifiers.find(
|
|
@@ -1807,20 +2522,19 @@ var init_prefer_to_contain = __esm({
|
|
|
1807
2522
|
});
|
|
1808
2523
|
|
|
1809
2524
|
// src/rules/prefer-to-have-count.ts
|
|
1810
|
-
var
|
|
2525
|
+
var prefer_to_have_count_default;
|
|
1811
2526
|
var init_prefer_to_have_count = __esm({
|
|
1812
2527
|
"src/rules/prefer-to-have-count.ts"() {
|
|
1813
2528
|
"use strict";
|
|
1814
2529
|
init_ast();
|
|
1815
2530
|
init_fixer();
|
|
1816
2531
|
init_parseExpectCall();
|
|
1817
|
-
matchers2 = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
1818
2532
|
prefer_to_have_count_default = {
|
|
1819
2533
|
create(context) {
|
|
1820
2534
|
return {
|
|
1821
2535
|
CallExpression(node) {
|
|
1822
2536
|
const expectCall = parseExpectCall(context, node);
|
|
1823
|
-
if (!expectCall || !
|
|
2537
|
+
if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
|
|
1824
2538
|
return;
|
|
1825
2539
|
}
|
|
1826
2540
|
const [argument] = node.arguments;
|
|
@@ -1872,20 +2586,19 @@ var init_prefer_to_have_count = __esm({
|
|
|
1872
2586
|
});
|
|
1873
2587
|
|
|
1874
2588
|
// src/rules/prefer-to-have-length.ts
|
|
1875
|
-
var
|
|
2589
|
+
var prefer_to_have_length_default;
|
|
1876
2590
|
var init_prefer_to_have_length = __esm({
|
|
1877
2591
|
"src/rules/prefer-to-have-length.ts"() {
|
|
1878
2592
|
"use strict";
|
|
1879
2593
|
init_ast();
|
|
1880
2594
|
init_fixer();
|
|
1881
2595
|
init_parseExpectCall();
|
|
1882
|
-
lengthMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
1883
2596
|
prefer_to_have_length_default = {
|
|
1884
2597
|
create(context) {
|
|
1885
2598
|
return {
|
|
1886
2599
|
CallExpression(node) {
|
|
1887
2600
|
const expectCall = parseExpectCall(context, node);
|
|
1888
|
-
if (!expectCall || !
|
|
2601
|
+
if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
|
|
1889
2602
|
return;
|
|
1890
2603
|
}
|
|
1891
2604
|
const [argument] = node.arguments;
|
|
@@ -2090,6 +2803,93 @@ var init_prefer_web_first_assertions = __esm({
|
|
|
2090
2803
|
}
|
|
2091
2804
|
});
|
|
2092
2805
|
|
|
2806
|
+
// src/rules/require-hook.ts
|
|
2807
|
+
var isNullOrUndefined, shouldBeInHook, require_hook_default;
|
|
2808
|
+
var init_require_hook = __esm({
|
|
2809
|
+
"src/rules/require-hook.ts"() {
|
|
2810
|
+
"use strict";
|
|
2811
|
+
init_ast();
|
|
2812
|
+
isNullOrUndefined = (node) => {
|
|
2813
|
+
return node.type === "Literal" && node.value === null || isIdentifier(node, "undefined");
|
|
2814
|
+
};
|
|
2815
|
+
shouldBeInHook = (context, node, allowedFunctionCalls = []) => {
|
|
2816
|
+
switch (node.type) {
|
|
2817
|
+
case "ExpressionStatement":
|
|
2818
|
+
return shouldBeInHook(context, node.expression, allowedFunctionCalls);
|
|
2819
|
+
case "CallExpression":
|
|
2820
|
+
return !(parseFnCall(context, node) || allowedFunctionCalls.includes(getStringValue(node.callee)));
|
|
2821
|
+
case "VariableDeclaration": {
|
|
2822
|
+
if (node.kind === "const") {
|
|
2823
|
+
return false;
|
|
2824
|
+
}
|
|
2825
|
+
return node.declarations.some(
|
|
2826
|
+
({ init }) => init != null && !isNullOrUndefined(init)
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
default:
|
|
2830
|
+
return false;
|
|
2831
|
+
}
|
|
2832
|
+
};
|
|
2833
|
+
require_hook_default = {
|
|
2834
|
+
create(context) {
|
|
2835
|
+
const options = {
|
|
2836
|
+
allowedFunctionCalls: [],
|
|
2837
|
+
...context.options?.[0] ?? {}
|
|
2838
|
+
};
|
|
2839
|
+
const checkBlockBody = (body) => {
|
|
2840
|
+
for (const statement of body) {
|
|
2841
|
+
if (shouldBeInHook(context, statement, options.allowedFunctionCalls)) {
|
|
2842
|
+
context.report({
|
|
2843
|
+
messageId: "useHook",
|
|
2844
|
+
node: statement
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
return {
|
|
2850
|
+
CallExpression(node) {
|
|
2851
|
+
if (!isDescribeCall(node) || node.arguments.length < 2) {
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
const [, testFn] = node.arguments;
|
|
2855
|
+
if (!isFunction(testFn) || testFn.body.type !== "BlockStatement") {
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
checkBlockBody(testFn.body.body);
|
|
2859
|
+
},
|
|
2860
|
+
Program(program) {
|
|
2861
|
+
checkBlockBody(program.body);
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2864
|
+
},
|
|
2865
|
+
meta: {
|
|
2866
|
+
docs: {
|
|
2867
|
+
category: "Best Practices",
|
|
2868
|
+
description: "Require setup and teardown code to be within a hook",
|
|
2869
|
+
recommended: false,
|
|
2870
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-hook.md"
|
|
2871
|
+
},
|
|
2872
|
+
messages: {
|
|
2873
|
+
useHook: "This should be done within a hook"
|
|
2874
|
+
},
|
|
2875
|
+
schema: [
|
|
2876
|
+
{
|
|
2877
|
+
additionalProperties: false,
|
|
2878
|
+
properties: {
|
|
2879
|
+
allowedFunctionCalls: {
|
|
2880
|
+
items: { type: "string" },
|
|
2881
|
+
type: "array"
|
|
2882
|
+
}
|
|
2883
|
+
},
|
|
2884
|
+
type: "object"
|
|
2885
|
+
}
|
|
2886
|
+
],
|
|
2887
|
+
type: "suggestion"
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
});
|
|
2892
|
+
|
|
2093
2893
|
// src/rules/require-soft-assertions.ts
|
|
2094
2894
|
var require_soft_assertions_default;
|
|
2095
2895
|
var init_require_soft_assertions = __esm({
|
|
@@ -2315,17 +3115,17 @@ var init_valid_title = __esm({
|
|
|
2315
3115
|
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
|
|
2316
3116
|
return [new RegExp(matcher, "u"), message];
|
|
2317
3117
|
};
|
|
2318
|
-
compileMatcherPatterns = (
|
|
2319
|
-
if (typeof
|
|
2320
|
-
const compiledMatcher = compileMatcherPattern(
|
|
3118
|
+
compileMatcherPatterns = (matchers) => {
|
|
3119
|
+
if (typeof matchers === "string" || Array.isArray(matchers)) {
|
|
3120
|
+
const compiledMatcher = compileMatcherPattern(matchers);
|
|
2321
3121
|
return {
|
|
2322
3122
|
describe: compiledMatcher,
|
|
2323
3123
|
test: compiledMatcher
|
|
2324
3124
|
};
|
|
2325
3125
|
}
|
|
2326
3126
|
return {
|
|
2327
|
-
describe:
|
|
2328
|
-
test:
|
|
3127
|
+
describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null,
|
|
3128
|
+
test: matchers.test ? compileMatcherPattern(matchers.test) : null
|
|
2329
3129
|
};
|
|
2330
3130
|
};
|
|
2331
3131
|
MatcherAndMessageSchema = {
|
|
@@ -2519,14 +3319,19 @@ import globals from "globals";
|
|
|
2519
3319
|
var require_src = __commonJS({
|
|
2520
3320
|
"src/index.ts"(exports, module) {
|
|
2521
3321
|
init_expect_expect();
|
|
3322
|
+
init_max_expects();
|
|
2522
3323
|
init_max_nested_describe();
|
|
2523
3324
|
init_missing_playwright_await();
|
|
3325
|
+
init_no_commented_out_tests();
|
|
3326
|
+
init_no_conditional_expect();
|
|
2524
3327
|
init_no_conditional_in_test();
|
|
3328
|
+
init_no_duplicate_hooks();
|
|
2525
3329
|
init_no_element_handle();
|
|
2526
3330
|
init_no_eval();
|
|
2527
3331
|
init_no_focused_test();
|
|
2528
3332
|
init_no_force_option();
|
|
2529
3333
|
init_no_get_by_title();
|
|
3334
|
+
init_no_hooks();
|
|
2530
3335
|
init_no_nested_step();
|
|
2531
3336
|
init_no_networkidle();
|
|
2532
3337
|
init_no_nth_methods();
|
|
@@ -2534,11 +3339,16 @@ var require_src = __commonJS({
|
|
|
2534
3339
|
init_no_raw_locators();
|
|
2535
3340
|
init_no_restricted_matchers();
|
|
2536
3341
|
init_no_skipped_test();
|
|
3342
|
+
init_no_standalone_expect();
|
|
2537
3343
|
init_no_unsafe_references();
|
|
2538
3344
|
init_no_useless_await();
|
|
2539
3345
|
init_no_useless_not();
|
|
2540
3346
|
init_no_wait_for_selector();
|
|
2541
3347
|
init_no_wait_for_timeout();
|
|
3348
|
+
init_prefer_comparison_matcher();
|
|
3349
|
+
init_prefer_equality_matcher();
|
|
3350
|
+
init_prefer_hooks_in_order();
|
|
3351
|
+
init_prefer_hooks_on_top();
|
|
2542
3352
|
init_prefer_lowercase_title();
|
|
2543
3353
|
init_prefer_strict_equal();
|
|
2544
3354
|
init_prefer_to_be();
|
|
@@ -2546,6 +3356,7 @@ var require_src = __commonJS({
|
|
|
2546
3356
|
init_prefer_to_have_count();
|
|
2547
3357
|
init_prefer_to_have_length();
|
|
2548
3358
|
init_prefer_web_first_assertions();
|
|
3359
|
+
init_require_hook();
|
|
2549
3360
|
init_require_soft_assertions();
|
|
2550
3361
|
init_require_top_level_describe();
|
|
2551
3362
|
init_valid_expect();
|
|
@@ -2554,14 +3365,19 @@ var require_src = __commonJS({
|
|
|
2554
3365
|
configs: {},
|
|
2555
3366
|
rules: {
|
|
2556
3367
|
"expect-expect": expect_expect_default,
|
|
3368
|
+
"max-expects": max_expects_default,
|
|
2557
3369
|
"max-nested-describe": max_nested_describe_default,
|
|
2558
3370
|
"missing-playwright-await": missing_playwright_await_default,
|
|
3371
|
+
"no-commented-out-tests": no_commented_out_tests_default,
|
|
3372
|
+
"no-conditional-expect": no_conditional_expect_default,
|
|
2559
3373
|
"no-conditional-in-test": no_conditional_in_test_default,
|
|
3374
|
+
"no-duplicate-hooks": no_duplicate_hooks_default,
|
|
2560
3375
|
"no-element-handle": no_element_handle_default,
|
|
2561
3376
|
"no-eval": no_eval_default,
|
|
2562
3377
|
"no-focused-test": no_focused_test_default,
|
|
2563
3378
|
"no-force-option": no_force_option_default,
|
|
2564
3379
|
"no-get-by-title": no_get_by_title_default,
|
|
3380
|
+
"no-hooks": no_hooks_default,
|
|
2565
3381
|
"no-nested-step": no_nested_step_default,
|
|
2566
3382
|
"no-networkidle": no_networkidle_default,
|
|
2567
3383
|
"no-nth-methods": no_nth_methods_default,
|
|
@@ -2569,11 +3385,16 @@ var require_src = __commonJS({
|
|
|
2569
3385
|
"no-raw-locators": no_raw_locators_default,
|
|
2570
3386
|
"no-restricted-matchers": no_restricted_matchers_default,
|
|
2571
3387
|
"no-skipped-test": no_skipped_test_default,
|
|
3388
|
+
"no-standalone-expect": no_standalone_expect_default,
|
|
2572
3389
|
"no-unsafe-references": no_unsafe_references_default,
|
|
2573
3390
|
"no-useless-await": no_useless_await_default,
|
|
2574
3391
|
"no-useless-not": no_useless_not_default,
|
|
2575
3392
|
"no-wait-for-selector": no_wait_for_selector_default,
|
|
2576
3393
|
"no-wait-for-timeout": no_wait_for_timeout_default,
|
|
3394
|
+
"prefer-comparison-matcher": prefer_comparison_matcher_default,
|
|
3395
|
+
"prefer-equality-matcher": prefer_equality_matcher_default,
|
|
3396
|
+
"prefer-hooks-in-order": prefer_hooks_in_order_default,
|
|
3397
|
+
"prefer-hooks-on-top": prefer_hooks_on_top_default,
|
|
2577
3398
|
"prefer-lowercase-title": prefer_lowercase_title_default,
|
|
2578
3399
|
"prefer-strict-equal": prefer_strict_equal_default,
|
|
2579
3400
|
"prefer-to-be": prefer_to_be_default,
|
|
@@ -2581,6 +3402,7 @@ var require_src = __commonJS({
|
|
|
2581
3402
|
"prefer-to-have-count": prefer_to_have_count_default,
|
|
2582
3403
|
"prefer-to-have-length": prefer_to_have_length_default,
|
|
2583
3404
|
"prefer-web-first-assertions": prefer_web_first_assertions_default,
|
|
3405
|
+
"require-hook": require_hook_default,
|
|
2584
3406
|
"require-soft-assertions": require_soft_assertions_default,
|
|
2585
3407
|
"require-top-level-describe": require_top_level_describe_default,
|
|
2586
3408
|
"valid-expect": valid_expect_default,
|
|
@@ -2593,6 +3415,7 @@ var require_src = __commonJS({
|
|
|
2593
3415
|
"playwright/expect-expect": "warn",
|
|
2594
3416
|
"playwright/max-nested-describe": "warn",
|
|
2595
3417
|
"playwright/missing-playwright-await": "error",
|
|
3418
|
+
"playwright/no-conditional-expect": "warn",
|
|
2596
3419
|
"playwright/no-conditional-in-test": "warn",
|
|
2597
3420
|
"playwright/no-element-handle": "warn",
|
|
2598
3421
|
"playwright/no-eval": "warn",
|
|
@@ -2602,6 +3425,7 @@ var require_src = __commonJS({
|
|
|
2602
3425
|
"playwright/no-networkidle": "error",
|
|
2603
3426
|
"playwright/no-page-pause": "warn",
|
|
2604
3427
|
"playwright/no-skipped-test": "warn",
|
|
3428
|
+
"playwright/no-standalone-expect": "error",
|
|
2605
3429
|
"playwright/no-unsafe-references": "error",
|
|
2606
3430
|
"playwright/no-useless-await": "warn",
|
|
2607
3431
|
"playwright/no-useless-not": "warn",
|