eslint-plugin-playwright 2.2.2 → 2.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 CHANGED
@@ -142,6 +142,7 @@ CLI option\
142
142
  | [no-slowed-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md) | Disallow usage of the `.slow` annotation | | | 💡 |
143
143
  | [no-standalone-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md) | Disallow using expect outside of `test` blocks | ✅ | | |
144
144
  | [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()` | ✅ | 🔧 | |
145
+ | [no-unused-locators](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md) | Disallow usage of page locators that are not used | ✅ | | |
145
146
  | [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 | ✅ | 🔧 | |
146
147
  | [no-useless-not](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md) | Disallow usage of `not` matchers when a specific matcher exists | ✅ | 🔧 | |
147
148
  | [no-wait-for-navigation](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-navigation.md) | Disallow usage of `page.waitForNavigation()` | ✅ | | 💡 |
package/dist/index.cjs CHANGED
@@ -21,28 +21,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
22
  mod
23
23
  ));
24
- var __accessCheck = (obj, member, msg) => {
25
- if (!member.has(obj))
26
- throw TypeError("Cannot " + msg);
27
- };
28
- var __privateGet = (obj, member, getter) => {
29
- __accessCheck(obj, member, "read from private field");
30
- return getter ? getter.call(obj) : member.get(obj);
31
- };
32
- var __privateAdd = (obj, member, value) => {
33
- if (member.has(obj))
34
- throw TypeError("Cannot add the same private member more than once");
35
- member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
36
- };
37
- var __privateSet = (obj, member, value, setter) => {
38
- __accessCheck(obj, member, "write to private field");
39
- setter ? setter.call(obj, value) : member.set(obj, value);
40
- return value;
41
- };
42
- var __privateMethod = (obj, member, method) => {
43
- __accessCheck(obj, member, "access private method");
44
- return method;
45
- };
46
24
 
47
25
  // src/index.ts
48
26
  var import_globals = __toESM(require("globals"), 1);
@@ -106,43 +84,38 @@ var VALID_CHAINS = /* @__PURE__ */ new Set([
106
84
  ]);
107
85
  var joinChains = (a, b) => a && b ? [...a, ...b] : null;
108
86
  var isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
109
- var _nodes, _leaves, _buildChain, buildChain_fn;
110
87
  var Chain = class {
88
+ #nodes = null;
89
+ #leaves = /* @__PURE__ */ new WeakSet();
111
90
  constructor(node) {
112
- __privateAdd(this, _buildChain);
113
- __privateAdd(this, _nodes, null);
114
- __privateAdd(this, _leaves, /* @__PURE__ */ new WeakSet());
115
- __privateSet(this, _nodes, __privateMethod(this, _buildChain, buildChain_fn).call(this, node));
91
+ this.#nodes = this.#buildChain(node);
116
92
  }
117
93
  isLeaf(node) {
118
- return __privateGet(this, _leaves).has(node);
94
+ return this.#leaves.has(node);
119
95
  }
120
96
  get nodes() {
121
- return __privateGet(this, _nodes);
97
+ return this.#nodes;
122
98
  }
123
- };
124
- _nodes = new WeakMap();
125
- _leaves = new WeakMap();
126
- _buildChain = new WeakSet();
127
- buildChain_fn = function(node, insideCall = false) {
128
- if (isSupportedAccessor(node)) {
129
- if (insideCall) {
130
- __privateGet(this, _leaves).add(node);
99
+ #buildChain(node, insideCall = false) {
100
+ if (isSupportedAccessor(node)) {
101
+ if (insideCall) {
102
+ this.#leaves.add(node);
103
+ }
104
+ return [node];
105
+ }
106
+ switch (node.type) {
107
+ case "TaggedTemplateExpression":
108
+ return this.#buildChain(node.tag);
109
+ case "MemberExpression":
110
+ return joinChains(
111
+ this.#buildChain(node.object),
112
+ this.#buildChain(node.property, insideCall)
113
+ );
114
+ case "CallExpression":
115
+ return this.#buildChain(node.callee, true);
116
+ default:
117
+ return null;
131
118
  }
132
- return [node];
133
- }
134
- switch (node.type) {
135
- case "TaggedTemplateExpression":
136
- return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.tag);
137
- case "MemberExpression":
138
- return joinChains(
139
- __privateMethod(this, _buildChain, buildChain_fn).call(this, node.object),
140
- __privateMethod(this, _buildChain, buildChain_fn).call(this, node.property, insideCall)
141
- );
142
- case "CallExpression":
143
- return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.callee, true);
144
- default:
145
- return null;
146
119
  }
147
120
  };
148
121
  var resolvePossibleAliasedGlobal = (context, global) => {
@@ -982,7 +955,7 @@ var no_duplicate_hooks_default = createRule({
982
955
  }
983
956
  const currentLayer = hookContexts[hookContexts.length - 1];
984
957
  const name = node.callee.type === "MemberExpression" ? getStringValue(node.callee.property) : "";
985
- currentLayer[name] || (currentLayer[name] = 0);
958
+ currentLayer[name] ||= 0;
986
959
  currentLayer[name] += 1;
987
960
  if (currentLayer[name] > 1) {
988
961
  context.report({
@@ -1856,6 +1829,35 @@ var no_unsafe_references_default = createRule({
1856
1829
  }
1857
1830
  });
1858
1831
 
1832
+ // src/rules/no-unused-locators.ts
1833
+ var LOCATOR_REGEX = /locator|getBy(Role|Text|Label|Placeholder|AltText|Title|TestId)/;
1834
+ var no_unused_locators_default = createRule({
1835
+ create(context) {
1836
+ return {
1837
+ CallExpression(node) {
1838
+ if (!isPageMethod(node, LOCATOR_REGEX)) {
1839
+ return;
1840
+ }
1841
+ if (node.parent.type === "ExpressionStatement" || node.parent.type === "AwaitExpression") {
1842
+ context.report({ messageId: "noUnusedLocator", node });
1843
+ }
1844
+ }
1845
+ };
1846
+ },
1847
+ meta: {
1848
+ docs: {
1849
+ category: "Possible Errors",
1850
+ description: `Disallow usage of page locators that are not used`,
1851
+ recommended: true,
1852
+ url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md"
1853
+ },
1854
+ messages: {
1855
+ noUnusedLocator: "Unused locator"
1856
+ },
1857
+ type: "problem"
1858
+ }
1859
+ });
1860
+
1859
1861
  // src/rules/no-useless-await.ts
1860
1862
  var locatorMethods = /* @__PURE__ */ new Set([
1861
1863
  "and",
@@ -3857,6 +3859,9 @@ var valid_test_tags_default = createRule({
3857
3859
  );
3858
3860
  }
3859
3861
  }
3862
+ const extractTagsFromTitle = (title) => {
3863
+ return title.match(/@[\S]+/g) || [];
3864
+ };
3860
3865
  const validateTag = (tag, node) => {
3861
3866
  if (!tag.startsWith("@")) {
3862
3867
  context.report({
@@ -3899,6 +3904,15 @@ var valid_test_tags_default = createRule({
3899
3904
  const { type } = call;
3900
3905
  if (type !== "test" && type !== "describe" && type !== "step")
3901
3906
  return;
3907
+ if (node.arguments.length > 0) {
3908
+ const titleArg = node.arguments[0];
3909
+ if (titleArg && titleArg.type === "Literal" && typeof titleArg.value === "string") {
3910
+ const titleTags = extractTagsFromTitle(titleArg.value);
3911
+ for (const tag of titleTags) {
3912
+ validateTag(tag, node);
3913
+ }
3914
+ }
3915
+ }
3902
3916
  if (node.arguments.length < 2)
3903
3917
  return;
3904
3918
  const optionsArg = node.arguments[1];
@@ -3938,7 +3952,7 @@ var valid_test_tags_default = createRule({
3938
3952
  },
3939
3953
  meta: {
3940
3954
  docs: {
3941
- description: "Enforce valid tag format in Playwright test blocks",
3955
+ description: "Enforce valid tag format in Playwright test blocks and titles",
3942
3956
  recommended: true
3943
3957
  },
3944
3958
  messages: {
@@ -4231,6 +4245,7 @@ var index = {
4231
4245
  "no-slowed-test": no_slowed_test_default,
4232
4246
  "no-standalone-expect": no_standalone_expect_default,
4233
4247
  "no-unsafe-references": no_unsafe_references_default,
4248
+ "no-unused-locators": no_unused_locators_default,
4234
4249
  "no-useless-await": no_useless_await_default,
4235
4250
  "no-useless-not": no_useless_not_default,
4236
4251
  "no-wait-for-navigation": no_wait_for_navigation_default,
@@ -4278,6 +4293,7 @@ var sharedConfig = {
4278
4293
  "playwright/no-skipped-test": "warn",
4279
4294
  "playwright/no-standalone-expect": "error",
4280
4295
  "playwright/no-unsafe-references": "error",
4296
+ "playwright/no-unused-locators": "error",
4281
4297
  "playwright/no-useless-await": "warn",
4282
4298
  "playwright/no-useless-not": "warn",
4283
4299
  "playwright/no-wait-for-navigation": "error",
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": "2.2.2",
4
+ "version": "2.3.0",
5
5
  "repository": "https://github.com/playwright-community/eslint-plugin-playwright",
6
6
  "author": "Mark Skelton <mark@mskelton.dev>",
7
7
  "contributors": [
@@ -9,7 +9,7 @@
9
9
  ],
10
10
  "license": "MIT",
11
11
  "engines": {
12
- "node": ">=16.6.0"
12
+ "node": ">=16.9.0"
13
13
  },
14
14
  "type": "module",
15
15
  "types": "./index.d.ts",
@@ -30,6 +30,6 @@
30
30
  "eslint": ">=8.40.0"
31
31
  },
32
32
  "dependencies": {
33
- "globals": "^13.23.0"
33
+ "globals": "^16.4.0"
34
34
  }
35
35
  }