eslint-plugin-playwright 1.0.1 → 1.1.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 +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +123 -14
- package/dist/index.mjs +142 -20
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -131,6 +131,7 @@ command line option.\
|
|
|
131
131
|
| ✔ | | | [no-networkidle](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-networkidle.md) | Disallow usage of the `networkidle` option |
|
|
132
132
|
| | | | [no-nth-methods](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-nth-methods.md) | Disallow usage of `first()`, `last()`, and `nth()` methods |
|
|
133
133
|
| ✔ | | | [no-page-pause](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md) | Disallow using `page.pause()` |
|
|
134
|
+
| ✔ | 🔧 | | [no-unsafe-references](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md) | Prevent unsafe variable references in `page.evaluate()` |
|
|
134
135
|
| | 🔧 | | [no-get-by-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-get-by-title.md) | Disallow using `getByTitle()` |
|
|
135
136
|
| | | | [no-raw-locators](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-raw-locators.md) | Disallow using raw locators |
|
|
136
137
|
| ✔ | 🔧 | | [no-useless-await](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-await.md) | Disallow unnecessary `await`s for Playwright methods |
|
package/dist/index.d.mts
CHANGED
|
@@ -26,6 +26,7 @@ declare const _default: {
|
|
|
26
26
|
'no-raw-locators': eslint.Rule.RuleModule;
|
|
27
27
|
'no-restricted-matchers': eslint.Rule.RuleModule;
|
|
28
28
|
'no-skipped-test': eslint.Rule.RuleModule;
|
|
29
|
+
'no-unsafe-references': eslint.Rule.RuleModule;
|
|
29
30
|
'no-useless-await': eslint.Rule.RuleModule;
|
|
30
31
|
'no-useless-not': eslint.Rule.RuleModule;
|
|
31
32
|
'no-wait-for-selector': eslint.Rule.RuleModule;
|
|
@@ -76,6 +77,7 @@ declare const _default: {
|
|
|
76
77
|
'no-raw-locators': eslint.Rule.RuleModule;
|
|
77
78
|
'no-restricted-matchers': eslint.Rule.RuleModule;
|
|
78
79
|
'no-skipped-test': eslint.Rule.RuleModule;
|
|
80
|
+
'no-unsafe-references': eslint.Rule.RuleModule;
|
|
79
81
|
'no-useless-await': eslint.Rule.RuleModule;
|
|
80
82
|
'no-useless-not': eslint.Rule.RuleModule;
|
|
81
83
|
'no-wait-for-selector': eslint.Rule.RuleModule;
|
|
@@ -108,6 +110,7 @@ declare const _default: {
|
|
|
108
110
|
'playwright/no-networkidle': string;
|
|
109
111
|
'playwright/no-page-pause': string;
|
|
110
112
|
'playwright/no-skipped-test': string;
|
|
113
|
+
'playwright/no-unsafe-references': string;
|
|
111
114
|
'playwright/no-useless-await': string;
|
|
112
115
|
'playwright/no-useless-not': string;
|
|
113
116
|
'playwright/no-wait-for-selector': string;
|
|
@@ -158,6 +161,7 @@ declare const _default: {
|
|
|
158
161
|
'playwright/no-networkidle': string;
|
|
159
162
|
'playwright/no-page-pause': string;
|
|
160
163
|
'playwright/no-skipped-test': string;
|
|
164
|
+
'playwright/no-unsafe-references': string;
|
|
161
165
|
'playwright/no-useless-await': string;
|
|
162
166
|
'playwright/no-useless-not': string;
|
|
163
167
|
'playwright/no-wait-for-selector': string;
|
|
@@ -186,6 +190,7 @@ declare const _default: {
|
|
|
186
190
|
'playwright/no-networkidle': string;
|
|
187
191
|
'playwright/no-page-pause': string;
|
|
188
192
|
'playwright/no-skipped-test': string;
|
|
193
|
+
'playwright/no-unsafe-references': string;
|
|
189
194
|
'playwright/no-useless-await': string;
|
|
190
195
|
'playwright/no-useless-not': string;
|
|
191
196
|
'playwright/no-wait-for-selector': string;
|
|
@@ -213,6 +218,7 @@ declare const _default: {
|
|
|
213
218
|
'no-raw-locators': eslint.Rule.RuleModule;
|
|
214
219
|
'no-restricted-matchers': eslint.Rule.RuleModule;
|
|
215
220
|
'no-skipped-test': eslint.Rule.RuleModule;
|
|
221
|
+
'no-unsafe-references': eslint.Rule.RuleModule;
|
|
216
222
|
'no-useless-await': eslint.Rule.RuleModule;
|
|
217
223
|
'no-useless-not': eslint.Rule.RuleModule;
|
|
218
224
|
'no-wait-for-selector': eslint.Rule.RuleModule;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ declare const _default: {
|
|
|
26
26
|
'no-raw-locators': eslint.Rule.RuleModule;
|
|
27
27
|
'no-restricted-matchers': eslint.Rule.RuleModule;
|
|
28
28
|
'no-skipped-test': eslint.Rule.RuleModule;
|
|
29
|
+
'no-unsafe-references': eslint.Rule.RuleModule;
|
|
29
30
|
'no-useless-await': eslint.Rule.RuleModule;
|
|
30
31
|
'no-useless-not': eslint.Rule.RuleModule;
|
|
31
32
|
'no-wait-for-selector': eslint.Rule.RuleModule;
|
|
@@ -76,6 +77,7 @@ declare const _default: {
|
|
|
76
77
|
'no-raw-locators': eslint.Rule.RuleModule;
|
|
77
78
|
'no-restricted-matchers': eslint.Rule.RuleModule;
|
|
78
79
|
'no-skipped-test': eslint.Rule.RuleModule;
|
|
80
|
+
'no-unsafe-references': eslint.Rule.RuleModule;
|
|
79
81
|
'no-useless-await': eslint.Rule.RuleModule;
|
|
80
82
|
'no-useless-not': eslint.Rule.RuleModule;
|
|
81
83
|
'no-wait-for-selector': eslint.Rule.RuleModule;
|
|
@@ -108,6 +110,7 @@ declare const _default: {
|
|
|
108
110
|
'playwright/no-networkidle': string;
|
|
109
111
|
'playwright/no-page-pause': string;
|
|
110
112
|
'playwright/no-skipped-test': string;
|
|
113
|
+
'playwright/no-unsafe-references': string;
|
|
111
114
|
'playwright/no-useless-await': string;
|
|
112
115
|
'playwright/no-useless-not': string;
|
|
113
116
|
'playwright/no-wait-for-selector': string;
|
|
@@ -158,6 +161,7 @@ declare const _default: {
|
|
|
158
161
|
'playwright/no-networkidle': string;
|
|
159
162
|
'playwright/no-page-pause': string;
|
|
160
163
|
'playwright/no-skipped-test': string;
|
|
164
|
+
'playwright/no-unsafe-references': string;
|
|
161
165
|
'playwright/no-useless-await': string;
|
|
162
166
|
'playwright/no-useless-not': string;
|
|
163
167
|
'playwright/no-wait-for-selector': string;
|
|
@@ -186,6 +190,7 @@ declare const _default: {
|
|
|
186
190
|
'playwright/no-networkidle': string;
|
|
187
191
|
'playwright/no-page-pause': string;
|
|
188
192
|
'playwright/no-skipped-test': string;
|
|
193
|
+
'playwright/no-unsafe-references': string;
|
|
189
194
|
'playwright/no-useless-await': string;
|
|
190
195
|
'playwright/no-useless-not': string;
|
|
191
196
|
'playwright/no-wait-for-selector': string;
|
|
@@ -213,6 +218,7 @@ declare const _default: {
|
|
|
213
218
|
'no-raw-locators': eslint.Rule.RuleModule;
|
|
214
219
|
'no-restricted-matchers': eslint.Rule.RuleModule;
|
|
215
220
|
'no-skipped-test': eslint.Rule.RuleModule;
|
|
221
|
+
'no-unsafe-references': eslint.Rule.RuleModule;
|
|
216
222
|
'no-useless-await': eslint.Rule.RuleModule;
|
|
217
223
|
'no-useless-not': eslint.Rule.RuleModule;
|
|
218
224
|
'no-wait-for-selector': eslint.Rule.RuleModule;
|
package/dist/index.js
CHANGED
|
@@ -123,6 +123,19 @@ function dig(node, identifier) {
|
|
|
123
123
|
function isPageMethod(node, name) {
|
|
124
124
|
return node.callee.type === "MemberExpression" && dig(node.callee.object, /(^(page|frame)|(Page|Frame)$)/) && isPropertyAccessor(node.callee, name);
|
|
125
125
|
}
|
|
126
|
+
function isFunction(node) {
|
|
127
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/utils/misc.ts
|
|
131
|
+
var getAmountData = (amount) => ({
|
|
132
|
+
amount: amount.toString(),
|
|
133
|
+
s: amount === 1 ? "" : "s"
|
|
134
|
+
});
|
|
135
|
+
function getSourceCode(context) {
|
|
136
|
+
return context.sourceCode ?? context.getSourceCode();
|
|
137
|
+
}
|
|
138
|
+
var truthy = Boolean;
|
|
126
139
|
|
|
127
140
|
// src/rules/expect-expect.ts
|
|
128
141
|
function isAssertionCall(context, node, assertFunctionNames) {
|
|
@@ -134,7 +147,7 @@ var expect_expect_default = {
|
|
|
134
147
|
assertFunctionNames: [],
|
|
135
148
|
...context.options?.[0] ?? {}
|
|
136
149
|
};
|
|
137
|
-
const sourceCode =
|
|
150
|
+
const sourceCode = getSourceCode(context);
|
|
138
151
|
const unchecked = [];
|
|
139
152
|
function checkExpressions(nodes) {
|
|
140
153
|
for (const node of nodes) {
|
|
@@ -323,7 +336,7 @@ function getCallType(context, node, awaitableMatchers) {
|
|
|
323
336
|
}
|
|
324
337
|
var missing_playwright_await_default = {
|
|
325
338
|
create(context) {
|
|
326
|
-
const sourceCode =
|
|
339
|
+
const sourceCode = getSourceCode(context);
|
|
327
340
|
const options = context.options[0] || {};
|
|
328
341
|
const awaitableMatchers = /* @__PURE__ */ new Set([
|
|
329
342
|
...expectPlaywrightMatchers,
|
|
@@ -556,7 +569,7 @@ var no_focused_test_default = {
|
|
|
556
569
|
|
|
557
570
|
// src/rules/no-force-option.ts
|
|
558
571
|
function isForceOptionEnabled(node) {
|
|
559
|
-
const arg = node.arguments
|
|
572
|
+
const arg = node.arguments.at(-1);
|
|
560
573
|
return arg?.type === "ObjectExpression" && arg.properties.find(
|
|
561
574
|
(property) => property.type === "Property" && getStringValue(property.key) === "force" && isBooleanLiteral(property.value, true)
|
|
562
575
|
);
|
|
@@ -901,7 +914,7 @@ var no_restricted_matchers_default = {
|
|
|
901
914
|
context.report({
|
|
902
915
|
data: { message: message ?? "", restriction },
|
|
903
916
|
loc: {
|
|
904
|
-
end: chain
|
|
917
|
+
end: chain.at(-1).loc.end,
|
|
905
918
|
start: chain[0].loc.start
|
|
906
919
|
},
|
|
907
920
|
messageId: message ? "restrictedWithMessage" : "restricted"
|
|
@@ -993,6 +1006,93 @@ var no_skipped_test_default = {
|
|
|
993
1006
|
}
|
|
994
1007
|
};
|
|
995
1008
|
|
|
1009
|
+
// src/rules/no-unsafe-references.ts
|
|
1010
|
+
function collectVariables(scope) {
|
|
1011
|
+
if (!scope)
|
|
1012
|
+
return [];
|
|
1013
|
+
return [
|
|
1014
|
+
...collectVariables(scope.upper),
|
|
1015
|
+
...scope.variables.map((ref) => ref.name)
|
|
1016
|
+
];
|
|
1017
|
+
}
|
|
1018
|
+
function addArgument(fixer, node, refs) {
|
|
1019
|
+
if (!node.arguments.length)
|
|
1020
|
+
return;
|
|
1021
|
+
if (node.arguments.length === 1) {
|
|
1022
|
+
return fixer.insertTextAfter(node.arguments[0], `, [${refs}]`);
|
|
1023
|
+
}
|
|
1024
|
+
const arr = node.arguments.at(-1);
|
|
1025
|
+
if (!arr || arr.type !== "ArrayExpression")
|
|
1026
|
+
return;
|
|
1027
|
+
const lastItem = arr.elements.at(-1);
|
|
1028
|
+
return lastItem ? fixer.insertTextAfter(lastItem, `, ${refs}`) : fixer.replaceText(arr, `[${refs}]`);
|
|
1029
|
+
}
|
|
1030
|
+
function getParen(sourceCode, node) {
|
|
1031
|
+
let token = sourceCode.getFirstToken(node);
|
|
1032
|
+
while (token && token.value !== "(") {
|
|
1033
|
+
token = sourceCode.getTokenAfter(token);
|
|
1034
|
+
}
|
|
1035
|
+
return token;
|
|
1036
|
+
}
|
|
1037
|
+
function addParam(sourceCode, fixer, node, refs) {
|
|
1038
|
+
const lastParam = node.params.at(-1);
|
|
1039
|
+
if (lastParam) {
|
|
1040
|
+
return fixer.insertTextAfter(lastParam, `, ${refs}`);
|
|
1041
|
+
}
|
|
1042
|
+
const token = getParen(sourceCode, node);
|
|
1043
|
+
return token ? fixer.insertTextAfter(token, `[${refs}]`) : null;
|
|
1044
|
+
}
|
|
1045
|
+
var no_unsafe_references_default = {
|
|
1046
|
+
create(context) {
|
|
1047
|
+
return {
|
|
1048
|
+
CallExpression(node) {
|
|
1049
|
+
if (!isPageMethod(node, "evaluate"))
|
|
1050
|
+
return;
|
|
1051
|
+
const [fn] = node.arguments;
|
|
1052
|
+
if (!fn || !isFunction(fn))
|
|
1053
|
+
return;
|
|
1054
|
+
const sourceCode = getSourceCode(context);
|
|
1055
|
+
const { through, upper } = sourceCode.getScope(fn.body);
|
|
1056
|
+
const allRefs = new Set(collectVariables(upper));
|
|
1057
|
+
through.filter((ref) => allRefs.has(ref.identifier.name)).forEach((ref, i, arr) => {
|
|
1058
|
+
const descriptor = {
|
|
1059
|
+
data: { variable: ref.identifier.name },
|
|
1060
|
+
messageId: "noUnsafeReference",
|
|
1061
|
+
node: ref.identifier
|
|
1062
|
+
};
|
|
1063
|
+
if (i !== 0) {
|
|
1064
|
+
context.report(descriptor);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
context.report({
|
|
1068
|
+
...descriptor,
|
|
1069
|
+
fix(fixer) {
|
|
1070
|
+
const refs = arr.map((ref2) => ref2.identifier.name).join(", ");
|
|
1071
|
+
return [
|
|
1072
|
+
addArgument(fixer, node, refs),
|
|
1073
|
+
addParam(sourceCode, fixer, fn, refs)
|
|
1074
|
+
].filter(truthy);
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
},
|
|
1081
|
+
meta: {
|
|
1082
|
+
docs: {
|
|
1083
|
+
category: "Possible Errors",
|
|
1084
|
+
description: "Prevent unsafe variable references in page.evaluate()",
|
|
1085
|
+
recommended: true,
|
|
1086
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md"
|
|
1087
|
+
},
|
|
1088
|
+
fixable: "code",
|
|
1089
|
+
messages: {
|
|
1090
|
+
noUnsafeReference: 'Unsafe reference to variable "{{ variable }}" in page.evaluate()'
|
|
1091
|
+
},
|
|
1092
|
+
type: "problem"
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
|
|
996
1096
|
// src/rules/no-useless-await.ts
|
|
997
1097
|
var locatorMethods = /* @__PURE__ */ new Set([
|
|
998
1098
|
"and",
|
|
@@ -1465,7 +1565,7 @@ var prefer_to_contain_default = {
|
|
|
1465
1565
|
);
|
|
1466
1566
|
context.report({
|
|
1467
1567
|
fix(fixer) {
|
|
1468
|
-
const sourceCode =
|
|
1568
|
+
const sourceCode = getSourceCode(context);
|
|
1469
1569
|
const addNotModifier = matcherArg.type === "Literal" && matcherArg.value === !!notModifier;
|
|
1470
1570
|
const fixes = [
|
|
1471
1571
|
// remove the "includes" call entirely
|
|
@@ -1661,6 +1761,19 @@ var supportedMatchers = /* @__PURE__ */ new Set([
|
|
|
1661
1761
|
"toBeTruthy",
|
|
1662
1762
|
"toBeFalsy"
|
|
1663
1763
|
]);
|
|
1764
|
+
function dereference(context, node) {
|
|
1765
|
+
if (node.type !== "Identifier") {
|
|
1766
|
+
return node;
|
|
1767
|
+
}
|
|
1768
|
+
const sourceCode = getSourceCode(context);
|
|
1769
|
+
const scope = sourceCode.getScope(node);
|
|
1770
|
+
for (const ref of scope.references) {
|
|
1771
|
+
const refParent = ref.identifier.parent;
|
|
1772
|
+
if (refParent.type === "VariableDeclarator") {
|
|
1773
|
+
return refParent.init;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1664
1777
|
var prefer_web_first_assertions_default = {
|
|
1665
1778
|
create(context) {
|
|
1666
1779
|
return {
|
|
@@ -1668,8 +1781,8 @@ var prefer_web_first_assertions_default = {
|
|
|
1668
1781
|
const expectCall = parseExpectCall(context, node);
|
|
1669
1782
|
if (!expectCall)
|
|
1670
1783
|
return;
|
|
1671
|
-
const
|
|
1672
|
-
if (arg.type !== "AwaitExpression" || arg.argument.type !== "CallExpression" || arg.argument.callee.type !== "MemberExpression") {
|
|
1784
|
+
const arg = dereference(context, node.arguments[0]);
|
|
1785
|
+
if (!arg || arg.type !== "AwaitExpression" || arg.argument.type !== "CallExpression" || arg.argument.callee.type !== "MemberExpression") {
|
|
1673
1786
|
return;
|
|
1674
1787
|
}
|
|
1675
1788
|
if (!supportedMatchers.has(expectCall.matcherName))
|
|
@@ -1693,7 +1806,7 @@ var prefer_web_first_assertions_default = {
|
|
|
1693
1806
|
},
|
|
1694
1807
|
fix: (fixer) => {
|
|
1695
1808
|
const methodArgs = arg.argument.type === "CallExpression" ? arg.argument.arguments : [];
|
|
1696
|
-
const methodEnd = methodArgs.length ? methodArgs
|
|
1809
|
+
const methodEnd = methodArgs.length ? methodArgs.at(-1).range[1] + 1 : callee.property.range[1] + 2;
|
|
1697
1810
|
const fixes = [
|
|
1698
1811
|
// Add await to the expect call
|
|
1699
1812
|
fixer.insertTextBefore(node, "await "),
|
|
@@ -1791,12 +1904,6 @@ var require_soft_assertions_default = {
|
|
|
1791
1904
|
}
|
|
1792
1905
|
};
|
|
1793
1906
|
|
|
1794
|
-
// src/utils/misc.ts
|
|
1795
|
-
var getAmountData = (amount) => ({
|
|
1796
|
-
amount: amount.toString(),
|
|
1797
|
-
s: amount === 1 ? "" : "s"
|
|
1798
|
-
});
|
|
1799
|
-
|
|
1800
1907
|
// src/rules/require-top-level-describe.ts
|
|
1801
1908
|
var require_top_level_describe_default = {
|
|
1802
1909
|
create(context) {
|
|
@@ -2180,6 +2287,7 @@ var index = {
|
|
|
2180
2287
|
"no-raw-locators": no_raw_locators_default,
|
|
2181
2288
|
"no-restricted-matchers": no_restricted_matchers_default,
|
|
2182
2289
|
"no-skipped-test": no_skipped_test_default,
|
|
2290
|
+
"no-unsafe-references": no_unsafe_references_default,
|
|
2183
2291
|
"no-useless-await": no_useless_await_default,
|
|
2184
2292
|
"no-useless-not": no_useless_not_default,
|
|
2185
2293
|
"no-wait-for-selector": no_wait_for_selector_default,
|
|
@@ -2212,6 +2320,7 @@ var sharedConfig = {
|
|
|
2212
2320
|
"playwright/no-networkidle": "error",
|
|
2213
2321
|
"playwright/no-page-pause": "warn",
|
|
2214
2322
|
"playwright/no-skipped-test": "warn",
|
|
2323
|
+
"playwright/no-unsafe-references": "error",
|
|
2215
2324
|
"playwright/no-useless-await": "warn",
|
|
2216
2325
|
"playwright/no-useless-not": "warn",
|
|
2217
2326
|
"playwright/no-wait-for-selector": "warn",
|
package/dist/index.mjs
CHANGED
|
@@ -93,6 +93,9 @@ function dig(node, identifier) {
|
|
|
93
93
|
function isPageMethod(node, name) {
|
|
94
94
|
return node.callee.type === "MemberExpression" && dig(node.callee.object, /(^(page|frame)|(Page|Frame)$)/) && isPropertyAccessor(node.callee, name);
|
|
95
95
|
}
|
|
96
|
+
function isFunction(node) {
|
|
97
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
98
|
+
}
|
|
96
99
|
var isTemplateLiteral, describeProperties, testHooks, expectSubCommands;
|
|
97
100
|
var init_ast = __esm({
|
|
98
101
|
"src/utils/ast.ts"() {
|
|
@@ -111,6 +114,22 @@ var init_ast = __esm({
|
|
|
111
114
|
}
|
|
112
115
|
});
|
|
113
116
|
|
|
117
|
+
// src/utils/misc.ts
|
|
118
|
+
function getSourceCode(context) {
|
|
119
|
+
return context.sourceCode ?? context.getSourceCode();
|
|
120
|
+
}
|
|
121
|
+
var getAmountData, truthy;
|
|
122
|
+
var init_misc = __esm({
|
|
123
|
+
"src/utils/misc.ts"() {
|
|
124
|
+
"use strict";
|
|
125
|
+
getAmountData = (amount) => ({
|
|
126
|
+
amount: amount.toString(),
|
|
127
|
+
s: amount === 1 ? "" : "s"
|
|
128
|
+
});
|
|
129
|
+
truthy = Boolean;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
114
133
|
// src/rules/expect-expect.ts
|
|
115
134
|
function isAssertionCall(context, node, assertFunctionNames) {
|
|
116
135
|
return isExpectCall(context, node) || assertFunctionNames.find((name) => dig(node.callee, name));
|
|
@@ -120,13 +139,14 @@ var init_expect_expect = __esm({
|
|
|
120
139
|
"src/rules/expect-expect.ts"() {
|
|
121
140
|
"use strict";
|
|
122
141
|
init_ast();
|
|
142
|
+
init_misc();
|
|
123
143
|
expect_expect_default = {
|
|
124
144
|
create(context) {
|
|
125
145
|
const options = {
|
|
126
146
|
assertFunctionNames: [],
|
|
127
147
|
...context.options?.[0] ?? {}
|
|
128
148
|
};
|
|
129
|
-
const sourceCode =
|
|
149
|
+
const sourceCode = getSourceCode(context);
|
|
130
150
|
const unchecked = [];
|
|
131
151
|
function checkExpressions(nodes) {
|
|
132
152
|
for (const node of nodes) {
|
|
@@ -275,6 +295,7 @@ var init_missing_playwright_await = __esm({
|
|
|
275
295
|
"src/rules/missing-playwright-await.ts"() {
|
|
276
296
|
"use strict";
|
|
277
297
|
init_ast();
|
|
298
|
+
init_misc();
|
|
278
299
|
validTypes = /* @__PURE__ */ new Set([
|
|
279
300
|
"AwaitExpression",
|
|
280
301
|
"ReturnStatement",
|
|
@@ -329,7 +350,7 @@ var init_missing_playwright_await = __esm({
|
|
|
329
350
|
];
|
|
330
351
|
missing_playwright_await_default = {
|
|
331
352
|
create(context) {
|
|
332
|
-
const sourceCode =
|
|
353
|
+
const sourceCode = getSourceCode(context);
|
|
333
354
|
const options = context.options[0] || {};
|
|
334
355
|
const awaitableMatchers = /* @__PURE__ */ new Set([
|
|
335
356
|
...expectPlaywrightMatchers,
|
|
@@ -592,7 +613,7 @@ var init_no_focused_test = __esm({
|
|
|
592
613
|
|
|
593
614
|
// src/rules/no-force-option.ts
|
|
594
615
|
function isForceOptionEnabled(node) {
|
|
595
|
-
const arg = node.arguments
|
|
616
|
+
const arg = node.arguments.at(-1);
|
|
596
617
|
return arg?.type === "ObjectExpression" && arg.properties.find(
|
|
597
618
|
(property) => property.type === "Property" && getStringValue(property.key) === "force" && isBooleanLiteral(property.value, true)
|
|
598
619
|
);
|
|
@@ -999,7 +1020,7 @@ var init_no_restricted_matchers = __esm({
|
|
|
999
1020
|
context.report({
|
|
1000
1021
|
data: { message: message ?? "", restriction },
|
|
1001
1022
|
loc: {
|
|
1002
|
-
end: chain
|
|
1023
|
+
end: chain.at(-1).loc.end,
|
|
1003
1024
|
start: chain[0].loc.start
|
|
1004
1025
|
},
|
|
1005
1026
|
messageId: message ? "restrictedWithMessage" : "restricted"
|
|
@@ -1100,6 +1121,101 @@ var init_no_skipped_test = __esm({
|
|
|
1100
1121
|
}
|
|
1101
1122
|
});
|
|
1102
1123
|
|
|
1124
|
+
// src/rules/no-unsafe-references.ts
|
|
1125
|
+
function collectVariables(scope) {
|
|
1126
|
+
if (!scope)
|
|
1127
|
+
return [];
|
|
1128
|
+
return [
|
|
1129
|
+
...collectVariables(scope.upper),
|
|
1130
|
+
...scope.variables.map((ref) => ref.name)
|
|
1131
|
+
];
|
|
1132
|
+
}
|
|
1133
|
+
function addArgument(fixer, node, refs) {
|
|
1134
|
+
if (!node.arguments.length)
|
|
1135
|
+
return;
|
|
1136
|
+
if (node.arguments.length === 1) {
|
|
1137
|
+
return fixer.insertTextAfter(node.arguments[0], `, [${refs}]`);
|
|
1138
|
+
}
|
|
1139
|
+
const arr = node.arguments.at(-1);
|
|
1140
|
+
if (!arr || arr.type !== "ArrayExpression")
|
|
1141
|
+
return;
|
|
1142
|
+
const lastItem = arr.elements.at(-1);
|
|
1143
|
+
return lastItem ? fixer.insertTextAfter(lastItem, `, ${refs}`) : fixer.replaceText(arr, `[${refs}]`);
|
|
1144
|
+
}
|
|
1145
|
+
function getParen(sourceCode, node) {
|
|
1146
|
+
let token = sourceCode.getFirstToken(node);
|
|
1147
|
+
while (token && token.value !== "(") {
|
|
1148
|
+
token = sourceCode.getTokenAfter(token);
|
|
1149
|
+
}
|
|
1150
|
+
return token;
|
|
1151
|
+
}
|
|
1152
|
+
function addParam(sourceCode, fixer, node, refs) {
|
|
1153
|
+
const lastParam = node.params.at(-1);
|
|
1154
|
+
if (lastParam) {
|
|
1155
|
+
return fixer.insertTextAfter(lastParam, `, ${refs}`);
|
|
1156
|
+
}
|
|
1157
|
+
const token = getParen(sourceCode, node);
|
|
1158
|
+
return token ? fixer.insertTextAfter(token, `[${refs}]`) : null;
|
|
1159
|
+
}
|
|
1160
|
+
var no_unsafe_references_default;
|
|
1161
|
+
var init_no_unsafe_references = __esm({
|
|
1162
|
+
"src/rules/no-unsafe-references.ts"() {
|
|
1163
|
+
"use strict";
|
|
1164
|
+
init_ast();
|
|
1165
|
+
init_misc();
|
|
1166
|
+
no_unsafe_references_default = {
|
|
1167
|
+
create(context) {
|
|
1168
|
+
return {
|
|
1169
|
+
CallExpression(node) {
|
|
1170
|
+
if (!isPageMethod(node, "evaluate"))
|
|
1171
|
+
return;
|
|
1172
|
+
const [fn] = node.arguments;
|
|
1173
|
+
if (!fn || !isFunction(fn))
|
|
1174
|
+
return;
|
|
1175
|
+
const sourceCode = getSourceCode(context);
|
|
1176
|
+
const { through, upper } = sourceCode.getScope(fn.body);
|
|
1177
|
+
const allRefs = new Set(collectVariables(upper));
|
|
1178
|
+
through.filter((ref) => allRefs.has(ref.identifier.name)).forEach((ref, i, arr) => {
|
|
1179
|
+
const descriptor = {
|
|
1180
|
+
data: { variable: ref.identifier.name },
|
|
1181
|
+
messageId: "noUnsafeReference",
|
|
1182
|
+
node: ref.identifier
|
|
1183
|
+
};
|
|
1184
|
+
if (i !== 0) {
|
|
1185
|
+
context.report(descriptor);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
context.report({
|
|
1189
|
+
...descriptor,
|
|
1190
|
+
fix(fixer) {
|
|
1191
|
+
const refs = arr.map((ref2) => ref2.identifier.name).join(", ");
|
|
1192
|
+
return [
|
|
1193
|
+
addArgument(fixer, node, refs),
|
|
1194
|
+
addParam(sourceCode, fixer, fn, refs)
|
|
1195
|
+
].filter(truthy);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
},
|
|
1202
|
+
meta: {
|
|
1203
|
+
docs: {
|
|
1204
|
+
category: "Possible Errors",
|
|
1205
|
+
description: "Prevent unsafe variable references in page.evaluate()",
|
|
1206
|
+
recommended: true,
|
|
1207
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md"
|
|
1208
|
+
},
|
|
1209
|
+
fixable: "code",
|
|
1210
|
+
messages: {
|
|
1211
|
+
noUnsafeReference: 'Unsafe reference to variable "{{ variable }}" in page.evaluate()'
|
|
1212
|
+
},
|
|
1213
|
+
type: "problem"
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1103
1219
|
// src/rules/no-useless-await.ts
|
|
1104
1220
|
function isSupportedMethod(node) {
|
|
1105
1221
|
if (node.callee.type !== "MemberExpression")
|
|
@@ -1617,6 +1733,7 @@ var init_prefer_to_contain = __esm({
|
|
|
1617
1733
|
"src/rules/prefer-to-contain.ts"() {
|
|
1618
1734
|
"use strict";
|
|
1619
1735
|
init_ast();
|
|
1736
|
+
init_misc();
|
|
1620
1737
|
init_parseExpectCall();
|
|
1621
1738
|
matchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
1622
1739
|
isFixableIncludesCallExpression = (node) => node.type === "CallExpression" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "includes") && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement";
|
|
@@ -1638,7 +1755,7 @@ var init_prefer_to_contain = __esm({
|
|
|
1638
1755
|
);
|
|
1639
1756
|
context.report({
|
|
1640
1757
|
fix(fixer) {
|
|
1641
|
-
const sourceCode =
|
|
1758
|
+
const sourceCode = getSourceCode(context);
|
|
1642
1759
|
const addNotModifier = matcherArg.type === "Literal" && matcherArg.value === !!notModifier;
|
|
1643
1760
|
const fixes = [
|
|
1644
1761
|
// remove the "includes" call entirely
|
|
@@ -1813,11 +1930,25 @@ var init_prefer_to_have_length = __esm({
|
|
|
1813
1930
|
});
|
|
1814
1931
|
|
|
1815
1932
|
// src/rules/prefer-web-first-assertions.ts
|
|
1933
|
+
function dereference(context, node) {
|
|
1934
|
+
if (node.type !== "Identifier") {
|
|
1935
|
+
return node;
|
|
1936
|
+
}
|
|
1937
|
+
const sourceCode = getSourceCode(context);
|
|
1938
|
+
const scope = sourceCode.getScope(node);
|
|
1939
|
+
for (const ref of scope.references) {
|
|
1940
|
+
const refParent = ref.identifier.parent;
|
|
1941
|
+
if (refParent.type === "VariableDeclarator") {
|
|
1942
|
+
return refParent.init;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1816
1946
|
var methods3, supportedMatchers, prefer_web_first_assertions_default;
|
|
1817
1947
|
var init_prefer_web_first_assertions = __esm({
|
|
1818
1948
|
"src/rules/prefer-web-first-assertions.ts"() {
|
|
1819
1949
|
"use strict";
|
|
1820
1950
|
init_ast();
|
|
1951
|
+
init_misc();
|
|
1821
1952
|
init_parseExpectCall();
|
|
1822
1953
|
methods3 = {
|
|
1823
1954
|
getAttribute: {
|
|
@@ -1867,8 +1998,8 @@ var init_prefer_web_first_assertions = __esm({
|
|
|
1867
1998
|
const expectCall = parseExpectCall(context, node);
|
|
1868
1999
|
if (!expectCall)
|
|
1869
2000
|
return;
|
|
1870
|
-
const
|
|
1871
|
-
if (arg.type !== "AwaitExpression" || arg.argument.type !== "CallExpression" || arg.argument.callee.type !== "MemberExpression") {
|
|
2001
|
+
const arg = dereference(context, node.arguments[0]);
|
|
2002
|
+
if (!arg || arg.type !== "AwaitExpression" || arg.argument.type !== "CallExpression" || arg.argument.callee.type !== "MemberExpression") {
|
|
1872
2003
|
return;
|
|
1873
2004
|
}
|
|
1874
2005
|
if (!supportedMatchers.has(expectCall.matcherName))
|
|
@@ -1892,7 +2023,7 @@ var init_prefer_web_first_assertions = __esm({
|
|
|
1892
2023
|
},
|
|
1893
2024
|
fix: (fixer) => {
|
|
1894
2025
|
const methodArgs = arg.argument.type === "CallExpression" ? arg.argument.arguments : [];
|
|
1895
|
-
const methodEnd = methodArgs.length ? methodArgs
|
|
2026
|
+
const methodEnd = methodArgs.length ? methodArgs.at(-1).range[1] + 1 : callee.property.range[1] + 2;
|
|
1896
2027
|
const fixes = [
|
|
1897
2028
|
// Add await to the expect call
|
|
1898
2029
|
fixer.insertTextBefore(node, "await "),
|
|
@@ -1999,18 +2130,6 @@ var init_require_soft_assertions = __esm({
|
|
|
1999
2130
|
}
|
|
2000
2131
|
});
|
|
2001
2132
|
|
|
2002
|
-
// src/utils/misc.ts
|
|
2003
|
-
var getAmountData;
|
|
2004
|
-
var init_misc = __esm({
|
|
2005
|
-
"src/utils/misc.ts"() {
|
|
2006
|
-
"use strict";
|
|
2007
|
-
getAmountData = (amount) => ({
|
|
2008
|
-
amount: amount.toString(),
|
|
2009
|
-
s: amount === 1 ? "" : "s"
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
});
|
|
2013
|
-
|
|
2014
2133
|
// src/rules/require-top-level-describe.ts
|
|
2015
2134
|
var require_top_level_describe_default;
|
|
2016
2135
|
var init_require_top_level_describe = __esm({
|
|
@@ -2418,6 +2537,7 @@ var require_src = __commonJS({
|
|
|
2418
2537
|
init_no_raw_locators();
|
|
2419
2538
|
init_no_restricted_matchers();
|
|
2420
2539
|
init_no_skipped_test();
|
|
2540
|
+
init_no_unsafe_references();
|
|
2421
2541
|
init_no_useless_await();
|
|
2422
2542
|
init_no_useless_not();
|
|
2423
2543
|
init_no_wait_for_selector();
|
|
@@ -2452,6 +2572,7 @@ var require_src = __commonJS({
|
|
|
2452
2572
|
"no-raw-locators": no_raw_locators_default,
|
|
2453
2573
|
"no-restricted-matchers": no_restricted_matchers_default,
|
|
2454
2574
|
"no-skipped-test": no_skipped_test_default,
|
|
2575
|
+
"no-unsafe-references": no_unsafe_references_default,
|
|
2455
2576
|
"no-useless-await": no_useless_await_default,
|
|
2456
2577
|
"no-useless-not": no_useless_not_default,
|
|
2457
2578
|
"no-wait-for-selector": no_wait_for_selector_default,
|
|
@@ -2484,6 +2605,7 @@ var require_src = __commonJS({
|
|
|
2484
2605
|
"playwright/no-networkidle": "error",
|
|
2485
2606
|
"playwright/no-page-pause": "warn",
|
|
2486
2607
|
"playwright/no-skipped-test": "warn",
|
|
2608
|
+
"playwright/no-unsafe-references": "error",
|
|
2487
2609
|
"playwright/no-useless-await": "warn",
|
|
2488
2610
|
"playwright/no-useless-not": "warn",
|
|
2489
2611
|
"playwright/no-wait-for-selector": "warn",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-playwright",
|
|
3
3
|
"description": "ESLint plugin for Playwright testing.",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"repository": "https://github.com/playwright-community/eslint-plugin-playwright",
|
|
6
6
|
"author": "Mark Skelton <mark@mskelton.dev>",
|
|
7
7
|
"packageManager": "pnpm@8.12.0",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"workspaces": [
|
|
13
13
|
"examples"
|
|
14
14
|
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=16.6.0"
|
|
17
|
+
},
|
|
15
18
|
"types": "./dist/index.d.ts",
|
|
16
19
|
"exports": {
|
|
17
20
|
"import": {
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
"devDependencies": {
|
|
38
41
|
"@jest/globals": "^29.7.0",
|
|
39
42
|
"@mskelton/eslint-config": "^8.4.0",
|
|
43
|
+
"@mskelton/semantic-release-config": "^1.0.1",
|
|
40
44
|
"@types/eslint": "^8.44.3",
|
|
41
45
|
"@types/estree": "^1.0.2",
|
|
42
46
|
"@types/node": "^20.11.17",
|
|
@@ -47,7 +51,8 @@
|
|
|
47
51
|
"eslint-plugin-sort": "^2.10.0",
|
|
48
52
|
"jest": "^29.7.0",
|
|
49
53
|
"prettier": "^3.0.3",
|
|
50
|
-
"
|
|
54
|
+
"prettier-plugin-jsdoc": "^1.3.0",
|
|
55
|
+
"semantic-release": "^23.0.2",
|
|
51
56
|
"ts-jest": "^29.1.1",
|
|
52
57
|
"tsup": "^8.0.1",
|
|
53
58
|
"typescript": "^5.2.2"
|