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.js
CHANGED
|
@@ -35,7 +35,7 @@ function getRawValue(node) {
|
|
|
35
35
|
return node.type === "Literal" ? node.raw : void 0;
|
|
36
36
|
}
|
|
37
37
|
function isIdentifier(node, name) {
|
|
38
|
-
return node.type === "Identifier" && (typeof name === "string" ? node.name === name : name.test(node.name));
|
|
38
|
+
return node.type === "Identifier" && (!name || (typeof name === "string" ? node.name === name : name.test(node.name)));
|
|
39
39
|
}
|
|
40
40
|
function isLiteral(node, type, value) {
|
|
41
41
|
return node.type === "Literal" && (value === void 0 ? typeof node.value === type : node.value === value);
|
|
@@ -48,8 +48,8 @@ function isStringLiteral(node, value) {
|
|
|
48
48
|
function isBooleanLiteral(node, value) {
|
|
49
49
|
return isLiteral(node, "boolean", value);
|
|
50
50
|
}
|
|
51
|
-
function isStringNode(node) {
|
|
52
|
-
return node && (isStringLiteral(node) || isTemplateLiteral(node));
|
|
51
|
+
function isStringNode(node, value) {
|
|
52
|
+
return node && (isStringLiteral(node, value) || isTemplateLiteral(node, value));
|
|
53
53
|
}
|
|
54
54
|
function isPropertyAccessor(node, name) {
|
|
55
55
|
return getStringValue(node.property) === name;
|
|
@@ -91,7 +91,12 @@ function findParent(node, type) {
|
|
|
91
91
|
function isTestCall(context, node, modifiers) {
|
|
92
92
|
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]);
|
|
93
93
|
}
|
|
94
|
-
var testHooks = /* @__PURE__ */ new Set([
|
|
94
|
+
var testHooks = /* @__PURE__ */ new Set([
|
|
95
|
+
"afterAll",
|
|
96
|
+
"afterEach",
|
|
97
|
+
"beforeAll",
|
|
98
|
+
"beforeEach"
|
|
99
|
+
]);
|
|
95
100
|
function isTestHook(context, node) {
|
|
96
101
|
return node.callee.type === "MemberExpression" && isTestIdentifier(context, node.callee.object) && testHooks.has(getStringValue(node.callee.property));
|
|
97
102
|
}
|
|
@@ -130,6 +135,7 @@ function isPageMethod(node, name) {
|
|
|
130
135
|
function isFunction(node) {
|
|
131
136
|
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
132
137
|
}
|
|
138
|
+
var equalityMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
133
139
|
|
|
134
140
|
// src/rules/expect-expect.ts
|
|
135
141
|
function isAssertionCall(context, node, assertFunctionNames) {
|
|
@@ -865,6 +871,178 @@ var no_get_by_title_default = {
|
|
|
865
871
|
}
|
|
866
872
|
};
|
|
867
873
|
|
|
874
|
+
// src/utils/parseFnCall.ts
|
|
875
|
+
var VALID_CHAINS = /* @__PURE__ */ new Set([
|
|
876
|
+
// Hooks
|
|
877
|
+
"afterAll",
|
|
878
|
+
"afterEach",
|
|
879
|
+
"beforeAll",
|
|
880
|
+
"beforeEach",
|
|
881
|
+
"test.afterAll",
|
|
882
|
+
"test.afterEach",
|
|
883
|
+
"test.beforeAll",
|
|
884
|
+
"test.beforeEach",
|
|
885
|
+
// Describe
|
|
886
|
+
"describe",
|
|
887
|
+
"describe.only",
|
|
888
|
+
"describe.skip",
|
|
889
|
+
"describe.fixme",
|
|
890
|
+
"describe.configure",
|
|
891
|
+
"test.describe",
|
|
892
|
+
"test.describe.only",
|
|
893
|
+
"test.describe.skip",
|
|
894
|
+
"test.describe.fixme",
|
|
895
|
+
"test.describe.configure",
|
|
896
|
+
// Test
|
|
897
|
+
"test",
|
|
898
|
+
"test.fail",
|
|
899
|
+
"text.fixme",
|
|
900
|
+
"test.only",
|
|
901
|
+
"test.skip",
|
|
902
|
+
"test.slow",
|
|
903
|
+
"test.step",
|
|
904
|
+
"test.use"
|
|
905
|
+
]);
|
|
906
|
+
var joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
907
|
+
var isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
|
|
908
|
+
function getNodeChain(node) {
|
|
909
|
+
if (isSupportedAccessor(node)) {
|
|
910
|
+
return [node];
|
|
911
|
+
}
|
|
912
|
+
switch (node.type) {
|
|
913
|
+
case "TaggedTemplateExpression":
|
|
914
|
+
return getNodeChain(node.tag);
|
|
915
|
+
case "MemberExpression":
|
|
916
|
+
return joinChains(getNodeChain(node.object), getNodeChain(node.property));
|
|
917
|
+
case "CallExpression":
|
|
918
|
+
return getNodeChain(node.callee);
|
|
919
|
+
}
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
var resolvePossibleAliasedGlobal = (context, global) => {
|
|
923
|
+
const globalAliases = context.settings.playwright?.globalAliases ?? {};
|
|
924
|
+
const alias = Object.entries(globalAliases).find(
|
|
925
|
+
([, aliases]) => aliases.includes(global)
|
|
926
|
+
);
|
|
927
|
+
return alias?.[0] ?? null;
|
|
928
|
+
};
|
|
929
|
+
var resolveToPlaywrightFn = (context, accessor) => {
|
|
930
|
+
const ident = getStringValue(accessor);
|
|
931
|
+
return {
|
|
932
|
+
local: ident,
|
|
933
|
+
original: resolvePossibleAliasedGlobal(context, ident),
|
|
934
|
+
type: "global"
|
|
935
|
+
};
|
|
936
|
+
};
|
|
937
|
+
function determinePlaywrightFnType(name) {
|
|
938
|
+
if (name === "expect")
|
|
939
|
+
return "expect";
|
|
940
|
+
if (name === "describe")
|
|
941
|
+
return "describe";
|
|
942
|
+
if (name === "test")
|
|
943
|
+
return "test";
|
|
944
|
+
if (testHooks.has(name))
|
|
945
|
+
return "hook";
|
|
946
|
+
return "unknown";
|
|
947
|
+
}
|
|
948
|
+
function parseFnCall(context, node) {
|
|
949
|
+
const chain = getNodeChain(node);
|
|
950
|
+
if (!chain?.length) {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
const [first, ...rest] = chain;
|
|
954
|
+
const resolved = resolveToPlaywrightFn(context, first);
|
|
955
|
+
if (!resolved)
|
|
956
|
+
return null;
|
|
957
|
+
let name = resolved.original ?? resolved.local;
|
|
958
|
+
const links = [name, ...rest.map((link) => getStringValue(link))];
|
|
959
|
+
if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
if (name === "test" && links.length > 1) {
|
|
963
|
+
const nextLinkName = links[1];
|
|
964
|
+
const nextLinkType = determinePlaywrightFnType(nextLinkName);
|
|
965
|
+
if (nextLinkType !== "unknown") {
|
|
966
|
+
name = nextLinkName;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const parsedFnCall = {
|
|
970
|
+
head: { ...resolved, node: first },
|
|
971
|
+
// every member node must have a member expression as their parent
|
|
972
|
+
// in order to be part of the call chain we're parsing
|
|
973
|
+
members: rest,
|
|
974
|
+
name
|
|
975
|
+
};
|
|
976
|
+
const type = determinePlaywrightFnType(name);
|
|
977
|
+
if (type === "expect") {
|
|
978
|
+
return {
|
|
979
|
+
...parsedFnCall,
|
|
980
|
+
args: [],
|
|
981
|
+
matcher: rest[rest.length - 1],
|
|
982
|
+
modifiers: rest.slice(0, rest.length - 1),
|
|
983
|
+
type
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
if (chain.slice(0, chain.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const parent = getParent(node);
|
|
990
|
+
if (parent?.type === "CallExpression" || parent?.type === "MemberExpression") {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
return { ...parsedFnCall, type };
|
|
994
|
+
}
|
|
995
|
+
var isTypeOfFnCall = (context, node, types) => {
|
|
996
|
+
const call = parseFnCall(context, node);
|
|
997
|
+
return call !== null && types.includes(call.type);
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// src/rules/no-hooks.ts
|
|
1001
|
+
var no_hooks_default = {
|
|
1002
|
+
create(context) {
|
|
1003
|
+
const options = {
|
|
1004
|
+
allow: [],
|
|
1005
|
+
...context.options?.[0] ?? {}
|
|
1006
|
+
};
|
|
1007
|
+
return {
|
|
1008
|
+
CallExpression(node) {
|
|
1009
|
+
const call = parseFnCall(context, node);
|
|
1010
|
+
if (call?.type === "hook" && !options.allow.includes(call.name)) {
|
|
1011
|
+
context.report({
|
|
1012
|
+
data: { hookName: call.name },
|
|
1013
|
+
messageId: "unexpectedHook",
|
|
1014
|
+
node
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
},
|
|
1020
|
+
meta: {
|
|
1021
|
+
docs: {
|
|
1022
|
+
category: "Best Practices",
|
|
1023
|
+
description: "Disallow setup and teardown hooks",
|
|
1024
|
+
recommended: false,
|
|
1025
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-hooks.md"
|
|
1026
|
+
},
|
|
1027
|
+
messages: {
|
|
1028
|
+
unexpectedHook: "Unexpected '{{ hookName }}' hook"
|
|
1029
|
+
},
|
|
1030
|
+
schema: [
|
|
1031
|
+
{
|
|
1032
|
+
additionalProperties: false,
|
|
1033
|
+
properties: {
|
|
1034
|
+
allow: {
|
|
1035
|
+
contains: ["beforeAll", "beforeEach", "afterAll", "afterEach"],
|
|
1036
|
+
type: "array"
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
type: "object"
|
|
1040
|
+
}
|
|
1041
|
+
],
|
|
1042
|
+
type: "suggestion"
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
868
1046
|
// src/rules/no-nested-step.ts
|
|
869
1047
|
function isStepCall(node) {
|
|
870
1048
|
const inner = node.type === "CallExpression" ? node.callee : node;
|
|
@@ -1237,7 +1415,7 @@ var getBlockType = (statement) => {
|
|
|
1237
1415
|
const func = getParent(statement);
|
|
1238
1416
|
if (!func) {
|
|
1239
1417
|
throw new Error(
|
|
1240
|
-
`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/
|
|
1418
|
+
`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/playwright-community/eslint-plugin-playwright`
|
|
1241
1419
|
);
|
|
1242
1420
|
}
|
|
1243
1421
|
if (func.type === "FunctionDeclaration") {
|
|
@@ -1245,7 +1423,7 @@ var getBlockType = (statement) => {
|
|
|
1245
1423
|
}
|
|
1246
1424
|
if (isFunction(func) && func.parent) {
|
|
1247
1425
|
const expr = func.parent;
|
|
1248
|
-
if (expr.type === "VariableDeclarator") {
|
|
1426
|
+
if (expr.type === "VariableDeclarator" || expr.type === "MethodDefinition") {
|
|
1249
1427
|
return "function";
|
|
1250
1428
|
}
|
|
1251
1429
|
if (expr.type === "CallExpression" && isDescribeCall(expr)) {
|
|
@@ -1294,6 +1472,9 @@ var no_standalone_expect_default = {
|
|
|
1294
1472
|
if (isTestCall(context, node)) {
|
|
1295
1473
|
callStack.push("test");
|
|
1296
1474
|
}
|
|
1475
|
+
if (isTestHook(context, node)) {
|
|
1476
|
+
callStack.push("hook");
|
|
1477
|
+
}
|
|
1297
1478
|
if (node.callee.type === "TaggedTemplateExpression") {
|
|
1298
1479
|
callStack.push("template");
|
|
1299
1480
|
}
|
|
@@ -1311,7 +1492,7 @@ var no_standalone_expect_default = {
|
|
|
1311
1492
|
category: "Best Practices",
|
|
1312
1493
|
description: "Disallow using `expect` outside of `test` blocks",
|
|
1313
1494
|
recommended: false,
|
|
1314
|
-
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/
|
|
1495
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md"
|
|
1315
1496
|
},
|
|
1316
1497
|
fixable: "code",
|
|
1317
1498
|
messages: {
|
|
@@ -1643,6 +1824,167 @@ var no_wait_for_timeout_default = {
|
|
|
1643
1824
|
}
|
|
1644
1825
|
};
|
|
1645
1826
|
|
|
1827
|
+
// src/rules/prefer-comparison-matcher.ts
|
|
1828
|
+
var isString = (node) => {
|
|
1829
|
+
return isStringLiteral(node) || node.type === "TemplateLiteral";
|
|
1830
|
+
};
|
|
1831
|
+
var isComparingToString = (expression) => {
|
|
1832
|
+
return isString(expression.left) || isString(expression.right);
|
|
1833
|
+
};
|
|
1834
|
+
var invertedOperators = {
|
|
1835
|
+
"<": ">=",
|
|
1836
|
+
"<=": ">",
|
|
1837
|
+
">": "<=",
|
|
1838
|
+
">=": "<"
|
|
1839
|
+
};
|
|
1840
|
+
var operatorMatcher = {
|
|
1841
|
+
"<": "toBeLessThan",
|
|
1842
|
+
"<=": "toBeLessThanOrEqual",
|
|
1843
|
+
">": "toBeGreaterThan",
|
|
1844
|
+
">=": "toBeGreaterThanOrEqual"
|
|
1845
|
+
};
|
|
1846
|
+
var determineMatcher = (operator, negated) => {
|
|
1847
|
+
const op = negated ? invertedOperators[operator] : operator;
|
|
1848
|
+
return operatorMatcher[op] ?? null;
|
|
1849
|
+
};
|
|
1850
|
+
var prefer_comparison_matcher_default = {
|
|
1851
|
+
create(context) {
|
|
1852
|
+
return {
|
|
1853
|
+
CallExpression(node) {
|
|
1854
|
+
const expectCall = parseExpectCall(context, node);
|
|
1855
|
+
if (!expectCall || expectCall.args.length === 0)
|
|
1856
|
+
return;
|
|
1857
|
+
const { args, matcher } = expectCall;
|
|
1858
|
+
const [comparison] = node.arguments;
|
|
1859
|
+
const expectCallEnd = node.range[1];
|
|
1860
|
+
const [matcherArg] = args;
|
|
1861
|
+
if (comparison?.type !== "BinaryExpression" || isComparingToString(comparison) || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
const hasNot = expectCall.modifiers.some(
|
|
1865
|
+
(node2) => getStringValue(node2) === "not"
|
|
1866
|
+
);
|
|
1867
|
+
const preferredMatcher = determineMatcher(
|
|
1868
|
+
comparison.operator,
|
|
1869
|
+
getRawValue(matcherArg) === hasNot.toString()
|
|
1870
|
+
);
|
|
1871
|
+
if (!preferredMatcher) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
context.report({
|
|
1875
|
+
data: { preferredMatcher },
|
|
1876
|
+
fix(fixer) {
|
|
1877
|
+
const [modifier] = expectCall.modifiers;
|
|
1878
|
+
const modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
|
|
1879
|
+
return [
|
|
1880
|
+
// Replace the comparison argument with the left-hand side of the comparison
|
|
1881
|
+
fixer.replaceText(
|
|
1882
|
+
comparison,
|
|
1883
|
+
context.sourceCode.getText(comparison.left)
|
|
1884
|
+
),
|
|
1885
|
+
// Replace the current matcher & modifier with the preferred matcher
|
|
1886
|
+
fixer.replaceTextRange(
|
|
1887
|
+
[expectCallEnd, getParent(matcher).range[1]],
|
|
1888
|
+
`${modifierText}.${preferredMatcher}`
|
|
1889
|
+
),
|
|
1890
|
+
// Replace the matcher argument with the right-hand side of the comparison
|
|
1891
|
+
fixer.replaceText(
|
|
1892
|
+
matcherArg,
|
|
1893
|
+
context.sourceCode.getText(comparison.right)
|
|
1894
|
+
)
|
|
1895
|
+
];
|
|
1896
|
+
},
|
|
1897
|
+
messageId: "useToBeComparison",
|
|
1898
|
+
node: matcher
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
},
|
|
1903
|
+
meta: {
|
|
1904
|
+
docs: {
|
|
1905
|
+
category: "Best Practices",
|
|
1906
|
+
description: "Suggest using the built-in comparison matchers",
|
|
1907
|
+
recommended: false
|
|
1908
|
+
},
|
|
1909
|
+
fixable: "code",
|
|
1910
|
+
messages: {
|
|
1911
|
+
useToBeComparison: "Prefer using `{{ preferredMatcher }}` instead"
|
|
1912
|
+
},
|
|
1913
|
+
type: "suggestion"
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
// src/rules/prefer-equality-matcher.ts
|
|
1918
|
+
var prefer_equality_matcher_default = {
|
|
1919
|
+
create(context) {
|
|
1920
|
+
return {
|
|
1921
|
+
CallExpression(node) {
|
|
1922
|
+
const expectCall = parseExpectCall(context, node);
|
|
1923
|
+
if (!expectCall || expectCall.args.length === 0)
|
|
1924
|
+
return;
|
|
1925
|
+
const { args, matcher } = expectCall;
|
|
1926
|
+
const [comparison] = node.arguments;
|
|
1927
|
+
const expectCallEnd = node.range[1];
|
|
1928
|
+
const [matcherArg] = args;
|
|
1929
|
+
if (comparison?.type !== "BinaryExpression" || comparison.operator !== "===" && comparison.operator !== "!==" || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
const matcherValue = getRawValue(matcherArg) === "true";
|
|
1933
|
+
const [modifier] = expectCall.modifiers;
|
|
1934
|
+
const hasNot = expectCall.modifiers.some(
|
|
1935
|
+
(node2) => getStringValue(node2) === "not"
|
|
1936
|
+
);
|
|
1937
|
+
const addNotModifier = (comparison.operator === "!==" ? !matcherValue : matcherValue) === hasNot;
|
|
1938
|
+
context.report({
|
|
1939
|
+
messageId: "useEqualityMatcher",
|
|
1940
|
+
node: matcher,
|
|
1941
|
+
suggest: [...equalityMatchers.keys()].map((equalityMatcher) => ({
|
|
1942
|
+
data: { matcher: equalityMatcher },
|
|
1943
|
+
fix(fixer) {
|
|
1944
|
+
let modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
|
|
1945
|
+
if (addNotModifier) {
|
|
1946
|
+
modifierText += `.not`;
|
|
1947
|
+
}
|
|
1948
|
+
return [
|
|
1949
|
+
// replace the comparison argument with the left-hand side of the comparison
|
|
1950
|
+
fixer.replaceText(
|
|
1951
|
+
comparison,
|
|
1952
|
+
context.sourceCode.getText(comparison.left)
|
|
1953
|
+
),
|
|
1954
|
+
// replace the current matcher & modifier with the preferred matcher
|
|
1955
|
+
fixer.replaceTextRange(
|
|
1956
|
+
[expectCallEnd, getParent(matcher).range[1]],
|
|
1957
|
+
`${modifierText}.${equalityMatcher}`
|
|
1958
|
+
),
|
|
1959
|
+
// replace the matcher argument with the right-hand side of the comparison
|
|
1960
|
+
fixer.replaceText(
|
|
1961
|
+
matcherArg,
|
|
1962
|
+
context.sourceCode.getText(comparison.right)
|
|
1963
|
+
)
|
|
1964
|
+
];
|
|
1965
|
+
},
|
|
1966
|
+
messageId: "suggestEqualityMatcher"
|
|
1967
|
+
}))
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
},
|
|
1972
|
+
meta: {
|
|
1973
|
+
docs: {
|
|
1974
|
+
category: "Best Practices",
|
|
1975
|
+
description: "Suggest using the built-in equality matchers",
|
|
1976
|
+
recommended: false,
|
|
1977
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md"
|
|
1978
|
+
},
|
|
1979
|
+
hasSuggestions: true,
|
|
1980
|
+
messages: {
|
|
1981
|
+
suggestEqualityMatcher: "Use `{{ matcher }}`",
|
|
1982
|
+
useEqualityMatcher: "Prefer using one of the equality matchers instead"
|
|
1983
|
+
},
|
|
1984
|
+
type: "suggestion"
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1646
1988
|
// src/rules/prefer-hooks-in-order.ts
|
|
1647
1989
|
var HooksOrder = ["beforeAll", "beforeEach", "afterEach", "afterAll"];
|
|
1648
1990
|
var prefer_hooks_in_order_default = {
|
|
@@ -1921,9 +2263,8 @@ var prefer_to_be_default = {
|
|
|
1921
2263
|
notModifier
|
|
1922
2264
|
);
|
|
1923
2265
|
}
|
|
1924
|
-
const argumentMatchers = ["toBe", "toEqual", "toStrictEqual"];
|
|
1925
2266
|
const firstArg = expectCall.args[0];
|
|
1926
|
-
if (!
|
|
2267
|
+
if (!equalityMatchers.has(expectCall.matcherName) || !firstArg) {
|
|
1927
2268
|
return;
|
|
1928
2269
|
}
|
|
1929
2270
|
if (firstArg.type === "Literal" && firstArg.value === null) {
|
|
@@ -1963,7 +2304,6 @@ var prefer_to_be_default = {
|
|
|
1963
2304
|
};
|
|
1964
2305
|
|
|
1965
2306
|
// src/rules/prefer-to-contain.ts
|
|
1966
|
-
var matchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
1967
2307
|
var isFixableIncludesCallExpression = (node) => node.type === "CallExpression" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "includes") && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement";
|
|
1968
2308
|
var prefer_to_contain_default = {
|
|
1969
2309
|
create(context) {
|
|
@@ -1975,7 +2315,7 @@ var prefer_to_contain_default = {
|
|
|
1975
2315
|
const { args, matcher, matcherName } = expectCall;
|
|
1976
2316
|
const [includesCall] = node.arguments;
|
|
1977
2317
|
const [matcherArg] = args;
|
|
1978
|
-
if (!includesCall || matcherArg.type === "SpreadElement" || !
|
|
2318
|
+
if (!includesCall || matcherArg.type === "SpreadElement" || !equalityMatchers.has(matcherName) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
|
|
1979
2319
|
return;
|
|
1980
2320
|
}
|
|
1981
2321
|
const notModifier = expectCall.modifiers.find(
|
|
@@ -2033,13 +2373,12 @@ var prefer_to_contain_default = {
|
|
|
2033
2373
|
};
|
|
2034
2374
|
|
|
2035
2375
|
// src/rules/prefer-to-have-count.ts
|
|
2036
|
-
var matchers2 = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
2037
2376
|
var prefer_to_have_count_default = {
|
|
2038
2377
|
create(context) {
|
|
2039
2378
|
return {
|
|
2040
2379
|
CallExpression(node) {
|
|
2041
2380
|
const expectCall = parseExpectCall(context, node);
|
|
2042
|
-
if (!expectCall || !
|
|
2381
|
+
if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
|
|
2043
2382
|
return;
|
|
2044
2383
|
}
|
|
2045
2384
|
const [argument] = node.arguments;
|
|
@@ -2089,13 +2428,12 @@ var prefer_to_have_count_default = {
|
|
|
2089
2428
|
};
|
|
2090
2429
|
|
|
2091
2430
|
// src/rules/prefer-to-have-length.ts
|
|
2092
|
-
var lengthMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
2093
2431
|
var prefer_to_have_length_default = {
|
|
2094
2432
|
create(context) {
|
|
2095
2433
|
return {
|
|
2096
2434
|
CallExpression(node) {
|
|
2097
2435
|
const expectCall = parseExpectCall(context, node);
|
|
2098
|
-
if (!expectCall || !
|
|
2436
|
+
if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
|
|
2099
2437
|
return;
|
|
2100
2438
|
}
|
|
2101
2439
|
const [argument] = node.arguments;
|
|
@@ -2290,6 +2628,86 @@ var prefer_web_first_assertions_default = {
|
|
|
2290
2628
|
}
|
|
2291
2629
|
};
|
|
2292
2630
|
|
|
2631
|
+
// src/rules/require-hook.ts
|
|
2632
|
+
var isNullOrUndefined = (node) => {
|
|
2633
|
+
return node.type === "Literal" && node.value === null || isIdentifier(node, "undefined");
|
|
2634
|
+
};
|
|
2635
|
+
var shouldBeInHook = (context, node, allowedFunctionCalls = []) => {
|
|
2636
|
+
switch (node.type) {
|
|
2637
|
+
case "ExpressionStatement":
|
|
2638
|
+
return shouldBeInHook(context, node.expression, allowedFunctionCalls);
|
|
2639
|
+
case "CallExpression":
|
|
2640
|
+
return !(parseFnCall(context, node) || allowedFunctionCalls.includes(getStringValue(node.callee)));
|
|
2641
|
+
case "VariableDeclaration": {
|
|
2642
|
+
if (node.kind === "const") {
|
|
2643
|
+
return false;
|
|
2644
|
+
}
|
|
2645
|
+
return node.declarations.some(
|
|
2646
|
+
({ init }) => init != null && !isNullOrUndefined(init)
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
default:
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2652
|
+
};
|
|
2653
|
+
var require_hook_default = {
|
|
2654
|
+
create(context) {
|
|
2655
|
+
const options = {
|
|
2656
|
+
allowedFunctionCalls: [],
|
|
2657
|
+
...context.options?.[0] ?? {}
|
|
2658
|
+
};
|
|
2659
|
+
const checkBlockBody = (body) => {
|
|
2660
|
+
for (const statement of body) {
|
|
2661
|
+
if (shouldBeInHook(context, statement, options.allowedFunctionCalls)) {
|
|
2662
|
+
context.report({
|
|
2663
|
+
messageId: "useHook",
|
|
2664
|
+
node: statement
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
return {
|
|
2670
|
+
CallExpression(node) {
|
|
2671
|
+
if (!isTypeOfFnCall(context, node, ["describe"]) || node.arguments.length < 2) {
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
const [, testFn] = node.arguments;
|
|
2675
|
+
if (!isFunction(testFn) || testFn.body.type !== "BlockStatement") {
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
checkBlockBody(testFn.body.body);
|
|
2679
|
+
},
|
|
2680
|
+
Program(program) {
|
|
2681
|
+
checkBlockBody(program.body);
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
},
|
|
2685
|
+
meta: {
|
|
2686
|
+
docs: {
|
|
2687
|
+
category: "Best Practices",
|
|
2688
|
+
description: "Require setup and teardown code to be within a hook",
|
|
2689
|
+
recommended: false,
|
|
2690
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-hook.md"
|
|
2691
|
+
},
|
|
2692
|
+
messages: {
|
|
2693
|
+
useHook: "This should be done within a hook"
|
|
2694
|
+
},
|
|
2695
|
+
schema: [
|
|
2696
|
+
{
|
|
2697
|
+
additionalProperties: false,
|
|
2698
|
+
properties: {
|
|
2699
|
+
allowedFunctionCalls: {
|
|
2700
|
+
items: { type: "string" },
|
|
2701
|
+
type: "array"
|
|
2702
|
+
}
|
|
2703
|
+
},
|
|
2704
|
+
type: "object"
|
|
2705
|
+
}
|
|
2706
|
+
],
|
|
2707
|
+
type: "suggestion"
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
|
|
2293
2711
|
// src/rules/require-soft-assertions.ts
|
|
2294
2712
|
var require_soft_assertions_default = {
|
|
2295
2713
|
create(context) {
|
|
@@ -2486,17 +2904,17 @@ var compileMatcherPattern = (matcherMaybeWithMessage) => {
|
|
|
2486
2904
|
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
|
|
2487
2905
|
return [new RegExp(matcher, "u"), message];
|
|
2488
2906
|
};
|
|
2489
|
-
var compileMatcherPatterns = (
|
|
2490
|
-
if (typeof
|
|
2491
|
-
const compiledMatcher = compileMatcherPattern(
|
|
2907
|
+
var compileMatcherPatterns = (matchers) => {
|
|
2908
|
+
if (typeof matchers === "string" || Array.isArray(matchers)) {
|
|
2909
|
+
const compiledMatcher = compileMatcherPattern(matchers);
|
|
2492
2910
|
return {
|
|
2493
2911
|
describe: compiledMatcher,
|
|
2494
2912
|
test: compiledMatcher
|
|
2495
2913
|
};
|
|
2496
2914
|
}
|
|
2497
2915
|
return {
|
|
2498
|
-
describe:
|
|
2499
|
-
test:
|
|
2916
|
+
describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null,
|
|
2917
|
+
test: matchers.test ? compileMatcherPattern(matchers.test) : null
|
|
2500
2918
|
};
|
|
2501
2919
|
};
|
|
2502
2920
|
var MatcherAndMessageSchema = {
|
|
@@ -2700,6 +3118,7 @@ var index = {
|
|
|
2700
3118
|
"no-focused-test": no_focused_test_default,
|
|
2701
3119
|
"no-force-option": no_force_option_default,
|
|
2702
3120
|
"no-get-by-title": no_get_by_title_default,
|
|
3121
|
+
"no-hooks": no_hooks_default,
|
|
2703
3122
|
"no-nested-step": no_nested_step_default,
|
|
2704
3123
|
"no-networkidle": no_networkidle_default,
|
|
2705
3124
|
"no-nth-methods": no_nth_methods_default,
|
|
@@ -2713,6 +3132,8 @@ var index = {
|
|
|
2713
3132
|
"no-useless-not": no_useless_not_default,
|
|
2714
3133
|
"no-wait-for-selector": no_wait_for_selector_default,
|
|
2715
3134
|
"no-wait-for-timeout": no_wait_for_timeout_default,
|
|
3135
|
+
"prefer-comparison-matcher": prefer_comparison_matcher_default,
|
|
3136
|
+
"prefer-equality-matcher": prefer_equality_matcher_default,
|
|
2716
3137
|
"prefer-hooks-in-order": prefer_hooks_in_order_default,
|
|
2717
3138
|
"prefer-hooks-on-top": prefer_hooks_on_top_default,
|
|
2718
3139
|
"prefer-lowercase-title": prefer_lowercase_title_default,
|
|
@@ -2722,6 +3143,7 @@ var index = {
|
|
|
2722
3143
|
"prefer-to-have-count": prefer_to_have_count_default,
|
|
2723
3144
|
"prefer-to-have-length": prefer_to_have_length_default,
|
|
2724
3145
|
"prefer-web-first-assertions": prefer_web_first_assertions_default,
|
|
3146
|
+
"require-hook": require_hook_default,
|
|
2725
3147
|
"require-soft-assertions": require_soft_assertions_default,
|
|
2726
3148
|
"require-top-level-describe": require_top_level_describe_default,
|
|
2727
3149
|
"valid-expect": valid_expect_default,
|