eslint-plugin-playwright 1.2.0 → 1.3.1
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 -49
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +442 -20
- package/dist/index.mjs +488 -24
- package/package.json +5 -7
package/dist/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ function getRawValue(node) {
|
|
|
16
16
|
return node.type === "Literal" ? node.raw : void 0;
|
|
17
17
|
}
|
|
18
18
|
function isIdentifier(node, name) {
|
|
19
|
-
return node.type === "Identifier" && (typeof name === "string" ? node.name === name : name.test(node.name));
|
|
19
|
+
return node.type === "Identifier" && (!name || (typeof name === "string" ? node.name === name : name.test(node.name)));
|
|
20
20
|
}
|
|
21
21
|
function isLiteral(node, type, value) {
|
|
22
22
|
return node.type === "Literal" && (value === void 0 ? typeof node.value === type : node.value === value);
|
|
@@ -27,8 +27,8 @@ function isStringLiteral(node, value) {
|
|
|
27
27
|
function isBooleanLiteral(node, value) {
|
|
28
28
|
return isLiteral(node, "boolean", value);
|
|
29
29
|
}
|
|
30
|
-
function isStringNode(node) {
|
|
31
|
-
return node && (isStringLiteral(node) || isTemplateLiteral(node));
|
|
30
|
+
function isStringNode(node, value) {
|
|
31
|
+
return node && (isStringLiteral(node, value) || isTemplateLiteral(node, value));
|
|
32
32
|
}
|
|
33
33
|
function isPropertyAccessor(node, name) {
|
|
34
34
|
return getStringValue(node.property) === name;
|
|
@@ -100,7 +100,7 @@ function isPageMethod(node, name) {
|
|
|
100
100
|
function isFunction(node) {
|
|
101
101
|
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
102
102
|
}
|
|
103
|
-
var isTemplateLiteral, describeProperties, testHooks, expectSubCommands;
|
|
103
|
+
var isTemplateLiteral, describeProperties, testHooks, expectSubCommands, equalityMatchers;
|
|
104
104
|
var init_ast = __esm({
|
|
105
105
|
"src/utils/ast.ts"() {
|
|
106
106
|
"use strict";
|
|
@@ -113,8 +113,14 @@ var init_ast = __esm({
|
|
|
113
113
|
"skip",
|
|
114
114
|
"fixme"
|
|
115
115
|
]);
|
|
116
|
-
testHooks = /* @__PURE__ */ new Set([
|
|
116
|
+
testHooks = /* @__PURE__ */ new Set([
|
|
117
|
+
"afterAll",
|
|
118
|
+
"afterEach",
|
|
119
|
+
"beforeAll",
|
|
120
|
+
"beforeEach"
|
|
121
|
+
]);
|
|
117
122
|
expectSubCommands = /* @__PURE__ */ new Set(["soft", "poll"]);
|
|
123
|
+
equalityMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
118
124
|
}
|
|
119
125
|
});
|
|
120
126
|
|
|
@@ -943,6 +949,192 @@ var init_no_get_by_title = __esm({
|
|
|
943
949
|
}
|
|
944
950
|
});
|
|
945
951
|
|
|
952
|
+
// src/utils/parseFnCall.ts
|
|
953
|
+
function getNodeChain(node) {
|
|
954
|
+
if (isSupportedAccessor(node)) {
|
|
955
|
+
return [node];
|
|
956
|
+
}
|
|
957
|
+
switch (node.type) {
|
|
958
|
+
case "TaggedTemplateExpression":
|
|
959
|
+
return getNodeChain(node.tag);
|
|
960
|
+
case "MemberExpression":
|
|
961
|
+
return joinChains(getNodeChain(node.object), getNodeChain(node.property));
|
|
962
|
+
case "CallExpression":
|
|
963
|
+
return getNodeChain(node.callee);
|
|
964
|
+
}
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
function determinePlaywrightFnType(name) {
|
|
968
|
+
if (name === "expect")
|
|
969
|
+
return "expect";
|
|
970
|
+
if (name === "describe")
|
|
971
|
+
return "describe";
|
|
972
|
+
if (name === "test")
|
|
973
|
+
return "test";
|
|
974
|
+
if (testHooks.has(name))
|
|
975
|
+
return "hook";
|
|
976
|
+
return "unknown";
|
|
977
|
+
}
|
|
978
|
+
function parseFnCall(context, node) {
|
|
979
|
+
const chain = getNodeChain(node);
|
|
980
|
+
if (!chain?.length) {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
const [first, ...rest] = chain;
|
|
984
|
+
const resolved = resolveToPlaywrightFn(context, first);
|
|
985
|
+
if (!resolved)
|
|
986
|
+
return null;
|
|
987
|
+
let name = resolved.original ?? resolved.local;
|
|
988
|
+
const links = [name, ...rest.map((link) => getStringValue(link))];
|
|
989
|
+
if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
if (name === "test" && links.length > 1) {
|
|
993
|
+
const nextLinkName = links[1];
|
|
994
|
+
const nextLinkType = determinePlaywrightFnType(nextLinkName);
|
|
995
|
+
if (nextLinkType !== "unknown") {
|
|
996
|
+
name = nextLinkName;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const parsedFnCall = {
|
|
1000
|
+
head: { ...resolved, node: first },
|
|
1001
|
+
// every member node must have a member expression as their parent
|
|
1002
|
+
// in order to be part of the call chain we're parsing
|
|
1003
|
+
members: rest,
|
|
1004
|
+
name
|
|
1005
|
+
};
|
|
1006
|
+
const type = determinePlaywrightFnType(name);
|
|
1007
|
+
if (type === "expect") {
|
|
1008
|
+
return {
|
|
1009
|
+
...parsedFnCall,
|
|
1010
|
+
args: [],
|
|
1011
|
+
matcher: rest[rest.length - 1],
|
|
1012
|
+
modifiers: rest.slice(0, rest.length - 1),
|
|
1013
|
+
type
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
if (chain.slice(0, chain.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const parent = getParent(node);
|
|
1020
|
+
if (parent?.type === "CallExpression" || parent?.type === "MemberExpression") {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
return { ...parsedFnCall, type };
|
|
1024
|
+
}
|
|
1025
|
+
var VALID_CHAINS, joinChains, isSupportedAccessor, resolvePossibleAliasedGlobal, resolveToPlaywrightFn, isTypeOfFnCall;
|
|
1026
|
+
var init_parseFnCall = __esm({
|
|
1027
|
+
"src/utils/parseFnCall.ts"() {
|
|
1028
|
+
"use strict";
|
|
1029
|
+
init_ast();
|
|
1030
|
+
VALID_CHAINS = /* @__PURE__ */ new Set([
|
|
1031
|
+
// Hooks
|
|
1032
|
+
"afterAll",
|
|
1033
|
+
"afterEach",
|
|
1034
|
+
"beforeAll",
|
|
1035
|
+
"beforeEach",
|
|
1036
|
+
"test.afterAll",
|
|
1037
|
+
"test.afterEach",
|
|
1038
|
+
"test.beforeAll",
|
|
1039
|
+
"test.beforeEach",
|
|
1040
|
+
// Describe
|
|
1041
|
+
"describe",
|
|
1042
|
+
"describe.only",
|
|
1043
|
+
"describe.skip",
|
|
1044
|
+
"describe.fixme",
|
|
1045
|
+
"describe.configure",
|
|
1046
|
+
"test.describe",
|
|
1047
|
+
"test.describe.only",
|
|
1048
|
+
"test.describe.skip",
|
|
1049
|
+
"test.describe.fixme",
|
|
1050
|
+
"test.describe.configure",
|
|
1051
|
+
// Test
|
|
1052
|
+
"test",
|
|
1053
|
+
"test.fail",
|
|
1054
|
+
"text.fixme",
|
|
1055
|
+
"test.only",
|
|
1056
|
+
"test.skip",
|
|
1057
|
+
"test.slow",
|
|
1058
|
+
"test.step",
|
|
1059
|
+
"test.use"
|
|
1060
|
+
]);
|
|
1061
|
+
joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
1062
|
+
isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
|
|
1063
|
+
resolvePossibleAliasedGlobal = (context, global) => {
|
|
1064
|
+
const globalAliases = context.settings.playwright?.globalAliases ?? {};
|
|
1065
|
+
const alias = Object.entries(globalAliases).find(
|
|
1066
|
+
([, aliases]) => aliases.includes(global)
|
|
1067
|
+
);
|
|
1068
|
+
return alias?.[0] ?? null;
|
|
1069
|
+
};
|
|
1070
|
+
resolveToPlaywrightFn = (context, accessor) => {
|
|
1071
|
+
const ident = getStringValue(accessor);
|
|
1072
|
+
return {
|
|
1073
|
+
local: ident,
|
|
1074
|
+
original: resolvePossibleAliasedGlobal(context, ident),
|
|
1075
|
+
type: "global"
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1078
|
+
isTypeOfFnCall = (context, node, types) => {
|
|
1079
|
+
const call = parseFnCall(context, node);
|
|
1080
|
+
return call !== null && types.includes(call.type);
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
// src/rules/no-hooks.ts
|
|
1086
|
+
var no_hooks_default;
|
|
1087
|
+
var init_no_hooks = __esm({
|
|
1088
|
+
"src/rules/no-hooks.ts"() {
|
|
1089
|
+
"use strict";
|
|
1090
|
+
init_parseFnCall();
|
|
1091
|
+
no_hooks_default = {
|
|
1092
|
+
create(context) {
|
|
1093
|
+
const options = {
|
|
1094
|
+
allow: [],
|
|
1095
|
+
...context.options?.[0] ?? {}
|
|
1096
|
+
};
|
|
1097
|
+
return {
|
|
1098
|
+
CallExpression(node) {
|
|
1099
|
+
const call = parseFnCall(context, node);
|
|
1100
|
+
if (call?.type === "hook" && !options.allow.includes(call.name)) {
|
|
1101
|
+
context.report({
|
|
1102
|
+
data: { hookName: call.name },
|
|
1103
|
+
messageId: "unexpectedHook",
|
|
1104
|
+
node
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
},
|
|
1110
|
+
meta: {
|
|
1111
|
+
docs: {
|
|
1112
|
+
category: "Best Practices",
|
|
1113
|
+
description: "Disallow setup and teardown hooks",
|
|
1114
|
+
recommended: false,
|
|
1115
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-hooks.md"
|
|
1116
|
+
},
|
|
1117
|
+
messages: {
|
|
1118
|
+
unexpectedHook: "Unexpected '{{ hookName }}' hook"
|
|
1119
|
+
},
|
|
1120
|
+
schema: [
|
|
1121
|
+
{
|
|
1122
|
+
additionalProperties: false,
|
|
1123
|
+
properties: {
|
|
1124
|
+
allow: {
|
|
1125
|
+
contains: ["beforeAll", "beforeEach", "afterAll", "afterEach"],
|
|
1126
|
+
type: "array"
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
type: "object"
|
|
1130
|
+
}
|
|
1131
|
+
],
|
|
1132
|
+
type: "suggestion"
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
|
|
946
1138
|
// src/rules/no-nested-step.ts
|
|
947
1139
|
function isStepCall(node) {
|
|
948
1140
|
const inner = node.type === "CallExpression" ? node.callee : node;
|
|
@@ -1377,7 +1569,7 @@ var init_no_standalone_expect = __esm({
|
|
|
1377
1569
|
const func = getParent(statement);
|
|
1378
1570
|
if (!func) {
|
|
1379
1571
|
throw new Error(
|
|
1380
|
-
`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/
|
|
1572
|
+
`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/playwright-community/eslint-plugin-playwright`
|
|
1381
1573
|
);
|
|
1382
1574
|
}
|
|
1383
1575
|
if (func.type === "FunctionDeclaration") {
|
|
@@ -1385,7 +1577,7 @@ var init_no_standalone_expect = __esm({
|
|
|
1385
1577
|
}
|
|
1386
1578
|
if (isFunction(func) && func.parent) {
|
|
1387
1579
|
const expr = func.parent;
|
|
1388
|
-
if (expr.type === "VariableDeclarator") {
|
|
1580
|
+
if (expr.type === "VariableDeclarator" || expr.type === "MethodDefinition") {
|
|
1389
1581
|
return "function";
|
|
1390
1582
|
}
|
|
1391
1583
|
if (expr.type === "CallExpression" && isDescribeCall(expr)) {
|
|
@@ -1434,6 +1626,9 @@ var init_no_standalone_expect = __esm({
|
|
|
1434
1626
|
if (isTestCall(context, node)) {
|
|
1435
1627
|
callStack.push("test");
|
|
1436
1628
|
}
|
|
1629
|
+
if (isTestHook(context, node)) {
|
|
1630
|
+
callStack.push("hook");
|
|
1631
|
+
}
|
|
1437
1632
|
if (node.callee.type === "TaggedTemplateExpression") {
|
|
1438
1633
|
callStack.push("template");
|
|
1439
1634
|
}
|
|
@@ -1451,7 +1646,7 @@ var init_no_standalone_expect = __esm({
|
|
|
1451
1646
|
category: "Best Practices",
|
|
1452
1647
|
description: "Disallow using `expect` outside of `test` blocks",
|
|
1453
1648
|
recommended: false,
|
|
1454
|
-
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/
|
|
1649
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md"
|
|
1455
1650
|
},
|
|
1456
1651
|
fixable: "code",
|
|
1457
1652
|
messages: {
|
|
@@ -1835,6 +2030,183 @@ var init_no_wait_for_timeout = __esm({
|
|
|
1835
2030
|
}
|
|
1836
2031
|
});
|
|
1837
2032
|
|
|
2033
|
+
// src/rules/prefer-comparison-matcher.ts
|
|
2034
|
+
var isString, isComparingToString, invertedOperators, operatorMatcher, determineMatcher, prefer_comparison_matcher_default;
|
|
2035
|
+
var init_prefer_comparison_matcher = __esm({
|
|
2036
|
+
"src/rules/prefer-comparison-matcher.ts"() {
|
|
2037
|
+
"use strict";
|
|
2038
|
+
init_ast();
|
|
2039
|
+
init_parseExpectCall();
|
|
2040
|
+
isString = (node) => {
|
|
2041
|
+
return isStringLiteral(node) || node.type === "TemplateLiteral";
|
|
2042
|
+
};
|
|
2043
|
+
isComparingToString = (expression) => {
|
|
2044
|
+
return isString(expression.left) || isString(expression.right);
|
|
2045
|
+
};
|
|
2046
|
+
invertedOperators = {
|
|
2047
|
+
"<": ">=",
|
|
2048
|
+
"<=": ">",
|
|
2049
|
+
">": "<=",
|
|
2050
|
+
">=": "<"
|
|
2051
|
+
};
|
|
2052
|
+
operatorMatcher = {
|
|
2053
|
+
"<": "toBeLessThan",
|
|
2054
|
+
"<=": "toBeLessThanOrEqual",
|
|
2055
|
+
">": "toBeGreaterThan",
|
|
2056
|
+
">=": "toBeGreaterThanOrEqual"
|
|
2057
|
+
};
|
|
2058
|
+
determineMatcher = (operator, negated) => {
|
|
2059
|
+
const op = negated ? invertedOperators[operator] : operator;
|
|
2060
|
+
return operatorMatcher[op] ?? null;
|
|
2061
|
+
};
|
|
2062
|
+
prefer_comparison_matcher_default = {
|
|
2063
|
+
create(context) {
|
|
2064
|
+
return {
|
|
2065
|
+
CallExpression(node) {
|
|
2066
|
+
const expectCall = parseExpectCall(context, node);
|
|
2067
|
+
if (!expectCall || expectCall.args.length === 0)
|
|
2068
|
+
return;
|
|
2069
|
+
const { args, matcher } = expectCall;
|
|
2070
|
+
const [comparison] = node.arguments;
|
|
2071
|
+
const expectCallEnd = node.range[1];
|
|
2072
|
+
const [matcherArg] = args;
|
|
2073
|
+
if (comparison?.type !== "BinaryExpression" || isComparingToString(comparison) || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
const hasNot = expectCall.modifiers.some(
|
|
2077
|
+
(node2) => getStringValue(node2) === "not"
|
|
2078
|
+
);
|
|
2079
|
+
const preferredMatcher = determineMatcher(
|
|
2080
|
+
comparison.operator,
|
|
2081
|
+
getRawValue(matcherArg) === hasNot.toString()
|
|
2082
|
+
);
|
|
2083
|
+
if (!preferredMatcher) {
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
context.report({
|
|
2087
|
+
data: { preferredMatcher },
|
|
2088
|
+
fix(fixer) {
|
|
2089
|
+
const [modifier] = expectCall.modifiers;
|
|
2090
|
+
const modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
|
|
2091
|
+
return [
|
|
2092
|
+
// Replace the comparison argument with the left-hand side of the comparison
|
|
2093
|
+
fixer.replaceText(
|
|
2094
|
+
comparison,
|
|
2095
|
+
context.sourceCode.getText(comparison.left)
|
|
2096
|
+
),
|
|
2097
|
+
// Replace the current matcher & modifier with the preferred matcher
|
|
2098
|
+
fixer.replaceTextRange(
|
|
2099
|
+
[expectCallEnd, getParent(matcher).range[1]],
|
|
2100
|
+
`${modifierText}.${preferredMatcher}`
|
|
2101
|
+
),
|
|
2102
|
+
// Replace the matcher argument with the right-hand side of the comparison
|
|
2103
|
+
fixer.replaceText(
|
|
2104
|
+
matcherArg,
|
|
2105
|
+
context.sourceCode.getText(comparison.right)
|
|
2106
|
+
)
|
|
2107
|
+
];
|
|
2108
|
+
},
|
|
2109
|
+
messageId: "useToBeComparison",
|
|
2110
|
+
node: matcher
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
},
|
|
2115
|
+
meta: {
|
|
2116
|
+
docs: {
|
|
2117
|
+
category: "Best Practices",
|
|
2118
|
+
description: "Suggest using the built-in comparison matchers",
|
|
2119
|
+
recommended: false
|
|
2120
|
+
},
|
|
2121
|
+
fixable: "code",
|
|
2122
|
+
messages: {
|
|
2123
|
+
useToBeComparison: "Prefer using `{{ preferredMatcher }}` instead"
|
|
2124
|
+
},
|
|
2125
|
+
type: "suggestion"
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
// src/rules/prefer-equality-matcher.ts
|
|
2132
|
+
var prefer_equality_matcher_default;
|
|
2133
|
+
var init_prefer_equality_matcher = __esm({
|
|
2134
|
+
"src/rules/prefer-equality-matcher.ts"() {
|
|
2135
|
+
"use strict";
|
|
2136
|
+
init_ast();
|
|
2137
|
+
init_parseExpectCall();
|
|
2138
|
+
prefer_equality_matcher_default = {
|
|
2139
|
+
create(context) {
|
|
2140
|
+
return {
|
|
2141
|
+
CallExpression(node) {
|
|
2142
|
+
const expectCall = parseExpectCall(context, node);
|
|
2143
|
+
if (!expectCall || expectCall.args.length === 0)
|
|
2144
|
+
return;
|
|
2145
|
+
const { args, matcher } = expectCall;
|
|
2146
|
+
const [comparison] = node.arguments;
|
|
2147
|
+
const expectCallEnd = node.range[1];
|
|
2148
|
+
const [matcherArg] = args;
|
|
2149
|
+
if (comparison?.type !== "BinaryExpression" || comparison.operator !== "===" && comparison.operator !== "!==" || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const matcherValue = getRawValue(matcherArg) === "true";
|
|
2153
|
+
const [modifier] = expectCall.modifiers;
|
|
2154
|
+
const hasNot = expectCall.modifiers.some(
|
|
2155
|
+
(node2) => getStringValue(node2) === "not"
|
|
2156
|
+
);
|
|
2157
|
+
const addNotModifier = (comparison.operator === "!==" ? !matcherValue : matcherValue) === hasNot;
|
|
2158
|
+
context.report({
|
|
2159
|
+
messageId: "useEqualityMatcher",
|
|
2160
|
+
node: matcher,
|
|
2161
|
+
suggest: [...equalityMatchers.keys()].map((equalityMatcher) => ({
|
|
2162
|
+
data: { matcher: equalityMatcher },
|
|
2163
|
+
fix(fixer) {
|
|
2164
|
+
let modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
|
|
2165
|
+
if (addNotModifier) {
|
|
2166
|
+
modifierText += `.not`;
|
|
2167
|
+
}
|
|
2168
|
+
return [
|
|
2169
|
+
// replace the comparison argument with the left-hand side of the comparison
|
|
2170
|
+
fixer.replaceText(
|
|
2171
|
+
comparison,
|
|
2172
|
+
context.sourceCode.getText(comparison.left)
|
|
2173
|
+
),
|
|
2174
|
+
// replace the current matcher & modifier with the preferred matcher
|
|
2175
|
+
fixer.replaceTextRange(
|
|
2176
|
+
[expectCallEnd, getParent(matcher).range[1]],
|
|
2177
|
+
`${modifierText}.${equalityMatcher}`
|
|
2178
|
+
),
|
|
2179
|
+
// replace the matcher argument with the right-hand side of the comparison
|
|
2180
|
+
fixer.replaceText(
|
|
2181
|
+
matcherArg,
|
|
2182
|
+
context.sourceCode.getText(comparison.right)
|
|
2183
|
+
)
|
|
2184
|
+
];
|
|
2185
|
+
},
|
|
2186
|
+
messageId: "suggestEqualityMatcher"
|
|
2187
|
+
}))
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
},
|
|
2192
|
+
meta: {
|
|
2193
|
+
docs: {
|
|
2194
|
+
category: "Best Practices",
|
|
2195
|
+
description: "Suggest using the built-in equality matchers",
|
|
2196
|
+
recommended: false,
|
|
2197
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md"
|
|
2198
|
+
},
|
|
2199
|
+
hasSuggestions: true,
|
|
2200
|
+
messages: {
|
|
2201
|
+
suggestEqualityMatcher: "Use `{{ matcher }}`",
|
|
2202
|
+
useEqualityMatcher: "Prefer using one of the equality matchers instead"
|
|
2203
|
+
},
|
|
2204
|
+
type: "suggestion"
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
|
|
1838
2210
|
// src/rules/prefer-hooks-in-order.ts
|
|
1839
2211
|
var HooksOrder, prefer_hooks_in_order_default;
|
|
1840
2212
|
var init_prefer_hooks_in_order = __esm({
|
|
@@ -2149,9 +2521,8 @@ var init_prefer_to_be = __esm({
|
|
|
2149
2521
|
notModifier
|
|
2150
2522
|
);
|
|
2151
2523
|
}
|
|
2152
|
-
const argumentMatchers = ["toBe", "toEqual", "toStrictEqual"];
|
|
2153
2524
|
const firstArg = expectCall.args[0];
|
|
2154
|
-
if (!
|
|
2525
|
+
if (!equalityMatchers.has(expectCall.matcherName) || !firstArg) {
|
|
2155
2526
|
return;
|
|
2156
2527
|
}
|
|
2157
2528
|
if (firstArg.type === "Literal" && firstArg.value === null) {
|
|
@@ -2193,13 +2564,12 @@ var init_prefer_to_be = __esm({
|
|
|
2193
2564
|
});
|
|
2194
2565
|
|
|
2195
2566
|
// src/rules/prefer-to-contain.ts
|
|
2196
|
-
var
|
|
2567
|
+
var isFixableIncludesCallExpression, prefer_to_contain_default;
|
|
2197
2568
|
var init_prefer_to_contain = __esm({
|
|
2198
2569
|
"src/rules/prefer-to-contain.ts"() {
|
|
2199
2570
|
"use strict";
|
|
2200
2571
|
init_ast();
|
|
2201
2572
|
init_parseExpectCall();
|
|
2202
|
-
matchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
2203
2573
|
isFixableIncludesCallExpression = (node) => node.type === "CallExpression" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "includes") && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement";
|
|
2204
2574
|
prefer_to_contain_default = {
|
|
2205
2575
|
create(context) {
|
|
@@ -2211,7 +2581,7 @@ var init_prefer_to_contain = __esm({
|
|
|
2211
2581
|
const { args, matcher, matcherName } = expectCall;
|
|
2212
2582
|
const [includesCall] = node.arguments;
|
|
2213
2583
|
const [matcherArg] = args;
|
|
2214
|
-
if (!includesCall || matcherArg.type === "SpreadElement" || !
|
|
2584
|
+
if (!includesCall || matcherArg.type === "SpreadElement" || !equalityMatchers.has(matcherName) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
|
|
2215
2585
|
return;
|
|
2216
2586
|
}
|
|
2217
2587
|
const notModifier = expectCall.modifiers.find(
|
|
@@ -2271,20 +2641,19 @@ var init_prefer_to_contain = __esm({
|
|
|
2271
2641
|
});
|
|
2272
2642
|
|
|
2273
2643
|
// src/rules/prefer-to-have-count.ts
|
|
2274
|
-
var
|
|
2644
|
+
var prefer_to_have_count_default;
|
|
2275
2645
|
var init_prefer_to_have_count = __esm({
|
|
2276
2646
|
"src/rules/prefer-to-have-count.ts"() {
|
|
2277
2647
|
"use strict";
|
|
2278
2648
|
init_ast();
|
|
2279
2649
|
init_fixer();
|
|
2280
2650
|
init_parseExpectCall();
|
|
2281
|
-
matchers2 = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
2282
2651
|
prefer_to_have_count_default = {
|
|
2283
2652
|
create(context) {
|
|
2284
2653
|
return {
|
|
2285
2654
|
CallExpression(node) {
|
|
2286
2655
|
const expectCall = parseExpectCall(context, node);
|
|
2287
|
-
if (!expectCall || !
|
|
2656
|
+
if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
|
|
2288
2657
|
return;
|
|
2289
2658
|
}
|
|
2290
2659
|
const [argument] = node.arguments;
|
|
@@ -2336,20 +2705,19 @@ var init_prefer_to_have_count = __esm({
|
|
|
2336
2705
|
});
|
|
2337
2706
|
|
|
2338
2707
|
// src/rules/prefer-to-have-length.ts
|
|
2339
|
-
var
|
|
2708
|
+
var prefer_to_have_length_default;
|
|
2340
2709
|
var init_prefer_to_have_length = __esm({
|
|
2341
2710
|
"src/rules/prefer-to-have-length.ts"() {
|
|
2342
2711
|
"use strict";
|
|
2343
2712
|
init_ast();
|
|
2344
2713
|
init_fixer();
|
|
2345
2714
|
init_parseExpectCall();
|
|
2346
|
-
lengthMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
2347
2715
|
prefer_to_have_length_default = {
|
|
2348
2716
|
create(context) {
|
|
2349
2717
|
return {
|
|
2350
2718
|
CallExpression(node) {
|
|
2351
2719
|
const expectCall = parseExpectCall(context, node);
|
|
2352
|
-
if (!expectCall || !
|
|
2720
|
+
if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
|
|
2353
2721
|
return;
|
|
2354
2722
|
}
|
|
2355
2723
|
const [argument] = node.arguments;
|
|
@@ -2554,6 +2922,94 @@ var init_prefer_web_first_assertions = __esm({
|
|
|
2554
2922
|
}
|
|
2555
2923
|
});
|
|
2556
2924
|
|
|
2925
|
+
// src/rules/require-hook.ts
|
|
2926
|
+
var isNullOrUndefined, shouldBeInHook, require_hook_default;
|
|
2927
|
+
var init_require_hook = __esm({
|
|
2928
|
+
"src/rules/require-hook.ts"() {
|
|
2929
|
+
"use strict";
|
|
2930
|
+
init_ast();
|
|
2931
|
+
init_parseFnCall();
|
|
2932
|
+
isNullOrUndefined = (node) => {
|
|
2933
|
+
return node.type === "Literal" && node.value === null || isIdentifier(node, "undefined");
|
|
2934
|
+
};
|
|
2935
|
+
shouldBeInHook = (context, node, allowedFunctionCalls = []) => {
|
|
2936
|
+
switch (node.type) {
|
|
2937
|
+
case "ExpressionStatement":
|
|
2938
|
+
return shouldBeInHook(context, node.expression, allowedFunctionCalls);
|
|
2939
|
+
case "CallExpression":
|
|
2940
|
+
return !(parseFnCall(context, node) || allowedFunctionCalls.includes(getStringValue(node.callee)));
|
|
2941
|
+
case "VariableDeclaration": {
|
|
2942
|
+
if (node.kind === "const") {
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
return node.declarations.some(
|
|
2946
|
+
({ init }) => init != null && !isNullOrUndefined(init)
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
default:
|
|
2950
|
+
return false;
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
require_hook_default = {
|
|
2954
|
+
create(context) {
|
|
2955
|
+
const options = {
|
|
2956
|
+
allowedFunctionCalls: [],
|
|
2957
|
+
...context.options?.[0] ?? {}
|
|
2958
|
+
};
|
|
2959
|
+
const checkBlockBody = (body) => {
|
|
2960
|
+
for (const statement of body) {
|
|
2961
|
+
if (shouldBeInHook(context, statement, options.allowedFunctionCalls)) {
|
|
2962
|
+
context.report({
|
|
2963
|
+
messageId: "useHook",
|
|
2964
|
+
node: statement
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
return {
|
|
2970
|
+
CallExpression(node) {
|
|
2971
|
+
if (!isTypeOfFnCall(context, node, ["describe"]) || node.arguments.length < 2) {
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
const [, testFn] = node.arguments;
|
|
2975
|
+
if (!isFunction(testFn) || testFn.body.type !== "BlockStatement") {
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
checkBlockBody(testFn.body.body);
|
|
2979
|
+
},
|
|
2980
|
+
Program(program) {
|
|
2981
|
+
checkBlockBody(program.body);
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
},
|
|
2985
|
+
meta: {
|
|
2986
|
+
docs: {
|
|
2987
|
+
category: "Best Practices",
|
|
2988
|
+
description: "Require setup and teardown code to be within a hook",
|
|
2989
|
+
recommended: false,
|
|
2990
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-hook.md"
|
|
2991
|
+
},
|
|
2992
|
+
messages: {
|
|
2993
|
+
useHook: "This should be done within a hook"
|
|
2994
|
+
},
|
|
2995
|
+
schema: [
|
|
2996
|
+
{
|
|
2997
|
+
additionalProperties: false,
|
|
2998
|
+
properties: {
|
|
2999
|
+
allowedFunctionCalls: {
|
|
3000
|
+
items: { type: "string" },
|
|
3001
|
+
type: "array"
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
type: "object"
|
|
3005
|
+
}
|
|
3006
|
+
],
|
|
3007
|
+
type: "suggestion"
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
|
|
2557
3013
|
// src/rules/require-soft-assertions.ts
|
|
2558
3014
|
var require_soft_assertions_default;
|
|
2559
3015
|
var init_require_soft_assertions = __esm({
|
|
@@ -2779,17 +3235,17 @@ var init_valid_title = __esm({
|
|
|
2779
3235
|
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
|
|
2780
3236
|
return [new RegExp(matcher, "u"), message];
|
|
2781
3237
|
};
|
|
2782
|
-
compileMatcherPatterns = (
|
|
2783
|
-
if (typeof
|
|
2784
|
-
const compiledMatcher = compileMatcherPattern(
|
|
3238
|
+
compileMatcherPatterns = (matchers) => {
|
|
3239
|
+
if (typeof matchers === "string" || Array.isArray(matchers)) {
|
|
3240
|
+
const compiledMatcher = compileMatcherPattern(matchers);
|
|
2785
3241
|
return {
|
|
2786
3242
|
describe: compiledMatcher,
|
|
2787
3243
|
test: compiledMatcher
|
|
2788
3244
|
};
|
|
2789
3245
|
}
|
|
2790
3246
|
return {
|
|
2791
|
-
describe:
|
|
2792
|
-
test:
|
|
3247
|
+
describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null,
|
|
3248
|
+
test: matchers.test ? compileMatcherPattern(matchers.test) : null
|
|
2793
3249
|
};
|
|
2794
3250
|
};
|
|
2795
3251
|
MatcherAndMessageSchema = {
|
|
@@ -2995,6 +3451,7 @@ var require_src = __commonJS({
|
|
|
2995
3451
|
init_no_focused_test();
|
|
2996
3452
|
init_no_force_option();
|
|
2997
3453
|
init_no_get_by_title();
|
|
3454
|
+
init_no_hooks();
|
|
2998
3455
|
init_no_nested_step();
|
|
2999
3456
|
init_no_networkidle();
|
|
3000
3457
|
init_no_nth_methods();
|
|
@@ -3008,6 +3465,8 @@ var require_src = __commonJS({
|
|
|
3008
3465
|
init_no_useless_not();
|
|
3009
3466
|
init_no_wait_for_selector();
|
|
3010
3467
|
init_no_wait_for_timeout();
|
|
3468
|
+
init_prefer_comparison_matcher();
|
|
3469
|
+
init_prefer_equality_matcher();
|
|
3011
3470
|
init_prefer_hooks_in_order();
|
|
3012
3471
|
init_prefer_hooks_on_top();
|
|
3013
3472
|
init_prefer_lowercase_title();
|
|
@@ -3017,6 +3476,7 @@ var require_src = __commonJS({
|
|
|
3017
3476
|
init_prefer_to_have_count();
|
|
3018
3477
|
init_prefer_to_have_length();
|
|
3019
3478
|
init_prefer_web_first_assertions();
|
|
3479
|
+
init_require_hook();
|
|
3020
3480
|
init_require_soft_assertions();
|
|
3021
3481
|
init_require_top_level_describe();
|
|
3022
3482
|
init_valid_expect();
|
|
@@ -3037,6 +3497,7 @@ var require_src = __commonJS({
|
|
|
3037
3497
|
"no-focused-test": no_focused_test_default,
|
|
3038
3498
|
"no-force-option": no_force_option_default,
|
|
3039
3499
|
"no-get-by-title": no_get_by_title_default,
|
|
3500
|
+
"no-hooks": no_hooks_default,
|
|
3040
3501
|
"no-nested-step": no_nested_step_default,
|
|
3041
3502
|
"no-networkidle": no_networkidle_default,
|
|
3042
3503
|
"no-nth-methods": no_nth_methods_default,
|
|
@@ -3050,6 +3511,8 @@ var require_src = __commonJS({
|
|
|
3050
3511
|
"no-useless-not": no_useless_not_default,
|
|
3051
3512
|
"no-wait-for-selector": no_wait_for_selector_default,
|
|
3052
3513
|
"no-wait-for-timeout": no_wait_for_timeout_default,
|
|
3514
|
+
"prefer-comparison-matcher": prefer_comparison_matcher_default,
|
|
3515
|
+
"prefer-equality-matcher": prefer_equality_matcher_default,
|
|
3053
3516
|
"prefer-hooks-in-order": prefer_hooks_in_order_default,
|
|
3054
3517
|
"prefer-hooks-on-top": prefer_hooks_on_top_default,
|
|
3055
3518
|
"prefer-lowercase-title": prefer_lowercase_title_default,
|
|
@@ -3059,6 +3522,7 @@ var require_src = __commonJS({
|
|
|
3059
3522
|
"prefer-to-have-count": prefer_to_have_count_default,
|
|
3060
3523
|
"prefer-to-have-length": prefer_to_have_length_default,
|
|
3061
3524
|
"prefer-web-first-assertions": prefer_web_first_assertions_default,
|
|
3525
|
+
"require-hook": require_hook_default,
|
|
3062
3526
|
"require-soft-assertions": require_soft_assertions_default,
|
|
3063
3527
|
"require-top-level-describe": require_top_level_describe_default,
|
|
3064
3528
|
"valid-expect": valid_expect_default,
|