eslint-plugin-playwright 1.3.0 → 1.4.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/dist/index.mjs CHANGED
@@ -6,6 +6,244 @@ var __commonJS = (cb, mod) => function __require() {
6
6
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7
7
  };
8
8
 
9
+ // src/utils/parseFnCall.ts
10
+ function getNodeChain(node) {
11
+ if (isSupportedAccessor(node)) {
12
+ return [node];
13
+ }
14
+ switch (node.type) {
15
+ case "TaggedTemplateExpression":
16
+ return getNodeChain(node.tag);
17
+ case "MemberExpression":
18
+ return joinChains(getNodeChain(node.object), getNodeChain(node.property));
19
+ case "CallExpression":
20
+ return getNodeChain(node.callee);
21
+ }
22
+ return null;
23
+ }
24
+ function determinePlaywrightFnType(name) {
25
+ if (name === "step")
26
+ return "step";
27
+ if (name === "expect")
28
+ return "expect";
29
+ if (name === "describe")
30
+ return "describe";
31
+ if (name === "test")
32
+ return "test";
33
+ if (testHooks.has(name))
34
+ return "hook";
35
+ return "unknown";
36
+ }
37
+ function getExpectArguments(call) {
38
+ return findParent(call.head.node, "CallExpression")?.arguments ?? [];
39
+ }
40
+ function parse(context, node) {
41
+ const chain = getNodeChain(node);
42
+ if (!chain?.length) {
43
+ return null;
44
+ }
45
+ const [first, ...rest] = chain;
46
+ const resolved = resolveToPlaywrightFn(context, first);
47
+ if (!resolved)
48
+ return null;
49
+ let name = resolved.original ?? resolved.local;
50
+ const links = [name, ...rest.map((link) => getStringValue(link))];
51
+ if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
52
+ return null;
53
+ }
54
+ if (name === "test" && links.length > 1) {
55
+ const nextLinkName = links[1];
56
+ const nextLinkType = determinePlaywrightFnType(nextLinkName);
57
+ if (nextLinkType !== "unknown") {
58
+ name = nextLinkName;
59
+ }
60
+ }
61
+ const parsedFnCall = {
62
+ head: { ...resolved, node: first },
63
+ // every member node must have a member expression as their parent
64
+ // in order to be part of the call chain we're parsing
65
+ members: rest,
66
+ name
67
+ };
68
+ const type = determinePlaywrightFnType(name);
69
+ if (type === "expect") {
70
+ const result = parseExpectCall(parsedFnCall);
71
+ if (typeof result === "string" && findTopMostCallExpression(node) !== node) {
72
+ return null;
73
+ }
74
+ if (result === "matcher-not-found") {
75
+ if (getParent(node)?.type === "MemberExpression") {
76
+ return "matcher-not-called";
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+ if (chain.slice(0, chain.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
82
+ return null;
83
+ }
84
+ const parent = getParent(node);
85
+ if (parent?.type === "CallExpression" || parent?.type === "MemberExpression") {
86
+ return null;
87
+ }
88
+ return { ...parsedFnCall, type };
89
+ }
90
+ function parseFnCallWithReason(context, node) {
91
+ if (cache.has(node)) {
92
+ return cache.get(node);
93
+ }
94
+ const call = parse(context, node);
95
+ cache.set(node, call);
96
+ return call;
97
+ }
98
+ function parseFnCall(context, node) {
99
+ const call = parseFnCallWithReason(context, node);
100
+ return typeof call === "string" ? null : call;
101
+ }
102
+ var testHooks, VALID_CHAINS, joinChains, isSupportedAccessor, resolvePossibleAliasedGlobal, resolveToPlaywrightFn, modifiers, findModifiersAndMatcher, parseExpectCall, findTopMostCallExpression, cache, isTypeOfFnCall;
103
+ var init_parseFnCall = __esm({
104
+ "src/utils/parseFnCall.ts"() {
105
+ "use strict";
106
+ init_ast();
107
+ testHooks = /* @__PURE__ */ new Set(["afterAll", "afterEach", "beforeAll", "beforeEach"]);
108
+ VALID_CHAINS = /* @__PURE__ */ new Set([
109
+ // Hooks
110
+ "afterAll",
111
+ "afterEach",
112
+ "beforeAll",
113
+ "beforeEach",
114
+ "test.afterAll",
115
+ "test.afterEach",
116
+ "test.beforeAll",
117
+ "test.beforeEach",
118
+ // Describe
119
+ "describe",
120
+ "describe.only",
121
+ "describe.skip",
122
+ "describe.fixme",
123
+ "describe.fixme.only",
124
+ "describe.configure",
125
+ "describe.serial",
126
+ "describe.serial.only",
127
+ "describe.serial.skip",
128
+ "describe.serial.fixme",
129
+ "describe.serial.fixme.only",
130
+ "describe.parallel",
131
+ "describe.parallel.only",
132
+ "describe.parallel.skip",
133
+ "describe.parallel.fixme",
134
+ "describe.parallel.fixme.only",
135
+ "test.describe",
136
+ "test.describe.only",
137
+ "test.describe.skip",
138
+ "test.describe.fixme",
139
+ "test.describe.fixme.only",
140
+ "test.describe.configure",
141
+ "test.describe.serial",
142
+ "test.describe.serial.only",
143
+ "test.describe.serial.skip",
144
+ "test.describe.serial.fixme",
145
+ "test.describe.serial.fixme.only",
146
+ "test.describe.parallel",
147
+ "test.describe.parallel.only",
148
+ "test.describe.parallel.skip",
149
+ "test.describe.parallel.fixme",
150
+ "test.describe.parallel.fixme.only",
151
+ // Test
152
+ "test",
153
+ "test.fail",
154
+ "test.fixme",
155
+ "test.only",
156
+ "test.skip",
157
+ "test.step",
158
+ "test.slow",
159
+ "test.use"
160
+ ]);
161
+ joinChains = (a, b) => a && b ? [...a, ...b] : null;
162
+ isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
163
+ resolvePossibleAliasedGlobal = (context, global) => {
164
+ const globalAliases = context.settings.playwright?.globalAliases ?? {};
165
+ const alias = Object.entries(globalAliases).find(
166
+ ([, aliases]) => aliases.includes(global)
167
+ );
168
+ return alias?.[0] ?? null;
169
+ };
170
+ resolveToPlaywrightFn = (context, accessor) => {
171
+ const ident = getStringValue(accessor);
172
+ const resolved = /(^expect|Expect)$/.test(ident) ? "expect" : ident;
173
+ return {
174
+ // eslint-disable-next-line sort/object-properties
175
+ original: resolvePossibleAliasedGlobal(context, resolved),
176
+ local: resolved
177
+ };
178
+ };
179
+ modifiers = /* @__PURE__ */ new Set(["not", "resolves", "rejects"]);
180
+ findModifiersAndMatcher = (members) => {
181
+ const modifiers2 = [];
182
+ for (const member of members) {
183
+ const name = getStringValue(member);
184
+ if (name === "soft" || name === "poll") {
185
+ if (modifiers2.length > 0) {
186
+ return "modifier-unknown";
187
+ }
188
+ } else if (name === "resolves" || name === "rejects") {
189
+ const lastModifier = getStringValue(modifiers2.at(-1));
190
+ if (lastModifier && lastModifier !== "soft" && lastModifier !== "poll") {
191
+ return "modifier-unknown";
192
+ }
193
+ } else if (name !== "not") {
194
+ if (member.parent?.type === "MemberExpression" && member.parent.parent?.type === "CallExpression") {
195
+ return {
196
+ matcher: member,
197
+ matcherArgs: member.parent.parent.arguments,
198
+ matcherName: name,
199
+ modifiers: modifiers2
200
+ };
201
+ }
202
+ return "modifier-unknown";
203
+ }
204
+ modifiers2.push(member);
205
+ }
206
+ return "matcher-not-found";
207
+ };
208
+ parseExpectCall = (call) => {
209
+ const modifiersAndMatcher = findModifiersAndMatcher(call.members);
210
+ if (typeof modifiersAndMatcher === "string") {
211
+ return modifiersAndMatcher;
212
+ }
213
+ return {
214
+ ...call,
215
+ args: getExpectArguments(call),
216
+ type: "expect",
217
+ ...modifiersAndMatcher
218
+ };
219
+ };
220
+ findTopMostCallExpression = (node) => {
221
+ let top = node;
222
+ let parent = getParent(node);
223
+ let child = node;
224
+ while (parent) {
225
+ if (parent.type === "CallExpression" && parent.callee === child) {
226
+ top = parent;
227
+ node = parent;
228
+ parent = getParent(parent);
229
+ continue;
230
+ }
231
+ if (parent.type !== "MemberExpression") {
232
+ break;
233
+ }
234
+ child = parent;
235
+ parent = getParent(parent);
236
+ }
237
+ return top;
238
+ };
239
+ cache = /* @__PURE__ */ new WeakMap();
240
+ isTypeOfFnCall = (context, node, types) => {
241
+ const call = parseFnCall(context, node);
242
+ return call !== null && types.includes(call.type);
243
+ };
244
+ }
245
+ });
246
+
9
247
  // src/utils/ast.ts
10
248
  function getStringValue(node) {
11
249
  if (!node)
@@ -16,7 +254,7 @@ function getRawValue(node) {
16
254
  return node.type === "Literal" ? node.raw : void 0;
17
255
  }
18
256
  function isIdentifier(node, name) {
19
- return node.type === "Identifier" && (typeof name === "string" ? node.name === name : name.test(node.name));
257
+ return node.type === "Identifier" && (!name || (typeof name === "string" ? node.name === name : name.test(node.name)));
20
258
  }
21
259
  function isLiteral(node, type, value) {
22
260
  return node.type === "Literal" && (value === void 0 ? typeof node.value === type : node.value === value);
@@ -27,91 +265,20 @@ function isStringLiteral(node, value) {
27
265
  function isBooleanLiteral(node, value) {
28
266
  return isLiteral(node, "boolean", value);
29
267
  }
30
- function isStringNode(node) {
31
- return node && (isStringLiteral(node) || isTemplateLiteral(node));
268
+ function isStringNode(node, value) {
269
+ return node && (isStringLiteral(node, value) || isTemplateLiteral(node, value));
32
270
  }
33
271
  function isPropertyAccessor(node, name) {
34
272
  return getStringValue(node.property) === name;
35
273
  }
36
- function getTestNames(context) {
37
- const aliases = context.settings.playwright?.globalAliases?.test ?? [];
38
- return ["test", ...aliases];
39
- }
40
- function isTestIdentifier(context, node) {
41
- const testNames = getTestNames(context);
42
- const regex = new RegExp(`^(${testNames.join("|")})$`);
43
- return isIdentifier(node, regex) || node.type === "MemberExpression" && isIdentifier(node.object, regex);
44
- }
45
- function isDescribeCall(node) {
46
- const inner = node.type === "CallExpression" ? node.callee : node;
47
- if (isIdentifier(inner, "describe")) {
48
- return true;
49
- }
50
- if (inner.type !== "MemberExpression") {
51
- return false;
52
- }
53
- return isPropertyAccessor(inner, "describe") ? true : describeProperties.has(getStringValue(inner.property)) ? isDescribeCall(inner.object) : false;
54
- }
55
274
  function getParent(node) {
56
275
  return node.parent;
57
276
  }
58
277
  function findParent(node, type) {
59
- if (!node.parent)
278
+ const parent = node.parent;
279
+ if (!parent)
60
280
  return;
61
- return node.parent.type === type ? node.parent : findParent(node.parent, type);
62
- }
63
- function isTestCall(context, node, modifiers) {
64
- return isTestIdentifier(context, node.callee) && !isDescribeCall(node) && (node.callee.type !== "MemberExpression" || !modifiers || modifiers?.includes(getStringValue(node.callee.property))) && node.arguments.length === 2 && isFunction(node.arguments[1]);
65
- }
66
- function isTestHook(context, node) {
67
- return node.callee.type === "MemberExpression" && isTestIdentifier(context, node.callee.object) && testHooks.has(getStringValue(node.callee.property));
68
- }
69
- function parseFnCall(context, node) {
70
- if (isTestCall(context, node)) {
71
- return {
72
- fn: node.arguments[1],
73
- name: getStringValue(node.callee),
74
- type: "test"
75
- };
76
- }
77
- if (node.callee.type === "MemberExpression" && isTestIdentifier(context, node.callee.object) && testHooks.has(getStringValue(node.callee.property))) {
78
- return {
79
- fn: node.arguments[0],
80
- name: getStringValue(node.callee.property),
81
- type: "hook"
82
- };
83
- }
84
- if (isDescribeCall(node)) {
85
- return {
86
- name: getStringValue(node.callee),
87
- type: "describe"
88
- };
89
- }
90
- }
91
- function getExpectType(context, node) {
92
- const aliases = context.settings.playwright?.globalAliases?.expect ?? [];
93
- const expectNames = ["expect", ...aliases];
94
- const regex = new RegExp(`(^(${expectNames.join("|")})|Expect)$`);
95
- if (isIdentifier(node.callee, regex)) {
96
- return "standalone";
97
- }
98
- if (node.callee.type === "MemberExpression" && // TODO: Maybe
99
- isIdentifier(node.callee.object, "expect")) {
100
- const type = getStringValue(node.callee.property);
101
- return expectSubCommands.has(type) ? type : void 0;
102
- }
103
- }
104
- function isExpectCall(context, node) {
105
- return !!getExpectType(context, node);
106
- }
107
- function getMatchers(node, chain = []) {
108
- if (node.parent.type === "MemberExpression" && node.parent.object === node) {
109
- return getMatchers(node.parent, [
110
- ...chain,
111
- node.parent.property
112
- ]);
113
- }
114
- return chain;
281
+ return parent.type === type ? parent : findParent(parent, type);
115
282
  }
116
283
  function dig(node, identifier) {
117
284
  return node.type === "MemberExpression" ? dig(node.property, identifier) : node.type === "CallExpression" ? dig(node.callee, identifier) : node.type === "Identifier" ? isIdentifier(node, identifier) : false;
@@ -120,36 +287,42 @@ function isPageMethod(node, name) {
120
287
  return node.callee.type === "MemberExpression" && dig(node.callee.object, /(^(page|frame)|(Page|Frame)$)/) && isPropertyAccessor(node.callee, name);
121
288
  }
122
289
  function isFunction(node) {
123
- return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
290
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
291
+ }
292
+ function getNodeName(node) {
293
+ if (isSupportedAccessor(node)) {
294
+ return getStringValue(node);
295
+ }
296
+ switch (node.type) {
297
+ case "TaggedTemplateExpression":
298
+ return getNodeName(node.tag);
299
+ case "MemberExpression":
300
+ return joinNames(getNodeName(node.object), getNodeName(node.property));
301
+ case "NewExpression":
302
+ case "CallExpression":
303
+ return getNodeName(node.callee);
304
+ }
305
+ return null;
124
306
  }
125
- var isTemplateLiteral, describeProperties, testHooks, expectSubCommands, equalityMatchers;
307
+ var isTemplateLiteral, equalityMatchers, joinNames;
126
308
  var init_ast = __esm({
127
309
  "src/utils/ast.ts"() {
128
310
  "use strict";
311
+ init_parseFnCall();
129
312
  isTemplateLiteral = (node, value) => node.type === "TemplateLiteral" && node.quasis.length === 1 && // bail out if not simple
130
313
  (value === void 0 || node.quasis[0].value.raw === value);
131
- describeProperties = /* @__PURE__ */ new Set([
132
- "parallel",
133
- "serial",
134
- "only",
135
- "skip",
136
- "fixme"
137
- ]);
138
- testHooks = /* @__PURE__ */ new Set(["afterAll", "afterEach", "beforeAll", "beforeEach"]);
139
- expectSubCommands = /* @__PURE__ */ new Set(["soft", "poll"]);
140
314
  equalityMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
315
+ joinNames = (a, b) => a && b ? `${a}.${b}` : null;
141
316
  }
142
317
  });
143
318
 
144
319
  // src/rules/expect-expect.ts
145
- function isAssertionCall(context, node, assertFunctionNames) {
146
- return isExpectCall(context, node) || assertFunctionNames.find((name) => dig(node.callee, name));
147
- }
148
320
  var expect_expect_default;
149
321
  var init_expect_expect = __esm({
150
322
  "src/rules/expect-expect.ts"() {
151
323
  "use strict";
152
324
  init_ast();
325
+ init_parseFnCall();
153
326
  expect_expect_default = {
154
327
  create(context) {
155
328
  const options = {
@@ -168,9 +341,10 @@ var init_expect_expect = __esm({
168
341
  }
169
342
  return {
170
343
  CallExpression(node) {
171
- if (isTestCall(context, node, ["fixme", "only", "skip"])) {
344
+ const call = parseFnCall(context, node);
345
+ if (call?.type === "test") {
172
346
  unchecked.push(node);
173
- } else if (isAssertionCall(context, node, options.assertFunctionNames)) {
347
+ } else if (call?.type === "expect" || options.assertFunctionNames.find((name) => dig(node.callee, name))) {
174
348
  const ancestors = context.sourceCode.getAncestors(node);
175
349
  checkExpressions(ancestors);
176
350
  }
@@ -216,6 +390,7 @@ var init_max_expects = __esm({
216
390
  "src/rules/max-expects.ts"() {
217
391
  "use strict";
218
392
  init_ast();
393
+ init_parseFnCall();
219
394
  max_expects_default = {
220
395
  create(context) {
221
396
  const options = {
@@ -225,7 +400,7 @@ var init_max_expects = __esm({
225
400
  let count = 0;
226
401
  const maybeResetCount = (node) => {
227
402
  const parent = getParent(node);
228
- const isTestFn = parent?.type !== "CallExpression" || isTestCall(context, parent);
403
+ const isTestFn = parent?.type !== "CallExpression" || isTypeOfFnCall(context, parent, ["test"]);
229
404
  if (isTestFn) {
230
405
  count = 0;
231
406
  }
@@ -234,8 +409,10 @@ var init_max_expects = __esm({
234
409
  ArrowFunctionExpression: maybeResetCount,
235
410
  "ArrowFunctionExpression:exit": maybeResetCount,
236
411
  CallExpression(node) {
237
- if (!getExpectType(context, node))
412
+ const call = parseFnCall(context, node);
413
+ if (call?.type !== "expect" || getParent(call.head.node)?.type === "MemberExpression") {
238
414
  return;
415
+ }
239
416
  count += 1;
240
417
  if (count > options.max) {
241
418
  context.report({
@@ -285,39 +462,33 @@ var max_nested_describe_default;
285
462
  var init_max_nested_describe = __esm({
286
463
  "src/rules/max-nested-describe.ts"() {
287
464
  "use strict";
288
- init_ast();
465
+ init_parseFnCall();
289
466
  max_nested_describe_default = {
290
467
  create(context) {
291
468
  const { options } = context;
292
469
  const max = options[0]?.max ?? 5;
293
- const describeCallbackStack = [];
294
- function pushDescribeCallback(node) {
295
- if (node.parent.type !== "CallExpression" || !isDescribeCall(node.parent)) {
296
- return;
297
- }
298
- describeCallbackStack.push(0);
299
- if (describeCallbackStack.length > max) {
300
- context.report({
301
- data: {
302
- depth: describeCallbackStack.length.toString(),
303
- max: max.toString()
304
- },
305
- messageId: "exceededMaxDepth",
306
- node: node.parent.callee
307
- });
308
- }
309
- }
310
- function popDescribeCallback(node) {
311
- const { parent } = node;
312
- if (parent.type === "CallExpression" && isDescribeCall(parent)) {
313
- describeCallbackStack.pop();
314
- }
315
- }
470
+ const describes = [];
316
471
  return {
317
- ArrowFunctionExpression: pushDescribeCallback,
318
- "ArrowFunctionExpression:exit": popDescribeCallback,
319
- FunctionExpression: pushDescribeCallback,
320
- "FunctionExpression:exit": popDescribeCallback
472
+ CallExpression(node) {
473
+ if (isTypeOfFnCall(context, node, ["describe"])) {
474
+ describes.unshift(node);
475
+ if (describes.length > max) {
476
+ context.report({
477
+ data: {
478
+ depth: describes.length.toString(),
479
+ max: max.toString()
480
+ },
481
+ messageId: "exceededMaxDepth",
482
+ node: node.callee
483
+ });
484
+ }
485
+ }
486
+ },
487
+ "CallExpression:exit"(node) {
488
+ if (describes[0] === node) {
489
+ describes.shift();
490
+ }
491
+ }
321
492
  };
322
493
  },
323
494
  meta: {
@@ -349,24 +520,23 @@ var init_max_nested_describe = __esm({
349
520
  });
350
521
 
351
522
  // src/rules/missing-playwright-await.ts
352
- function getCallType(context, node, awaitableMatchers) {
353
- if (node.callee.type === "MemberExpression" && isIdentifier(node.callee.object, "test") && isPropertyAccessor(node.callee, "step")) {
354
- return { messageId: "testStep", node };
523
+ function getReportNode(node) {
524
+ const parent = getParent(node);
525
+ return parent?.type === "MemberExpression" ? parent : node;
526
+ }
527
+ function getCallType(call, awaitableMatchers) {
528
+ if (call.type === "step") {
529
+ return { messageId: "testStep", node: call.head.node };
355
530
  }
356
- const expectType = getExpectType(context, node);
357
- if (!expectType)
358
- return;
359
- const [lastMatcher] = getMatchers(node).slice(-1);
360
- const grandparent = lastMatcher?.parent?.parent;
361
- if (grandparent?.type !== "CallExpression")
362
- return;
363
- const matcherName = getStringValue(lastMatcher);
364
- if (expectType === "poll" || awaitableMatchers.has(matcherName)) {
365
- return {
366
- data: { matcherName },
367
- messageId: expectType === "poll" ? "expectPoll" : "expect",
368
- node: grandparent
369
- };
531
+ if (call.type === "expect") {
532
+ const isPoll = call.modifiers.some((m) => getStringValue(m) === "poll");
533
+ if (isPoll || awaitableMatchers.has(call.matcherName)) {
534
+ return {
535
+ data: { matcherName: call.matcherName },
536
+ messageId: isPoll ? "expectPoll" : "expect",
537
+ node: call.head.node
538
+ };
539
+ }
370
540
  }
371
541
  }
372
542
  var validTypes, expectPlaywrightMatchers, playwrightTestMatchers, missing_playwright_await_default;
@@ -374,6 +544,7 @@ var init_missing_playwright_await = __esm({
374
544
  "src/rules/missing-playwright-await.ts"() {
375
545
  "use strict";
376
546
  init_ast();
547
+ init_parseFnCall();
377
548
  validTypes = /* @__PURE__ */ new Set([
378
549
  "AwaitExpression",
379
550
  "ReturnStatement",
@@ -436,16 +607,19 @@ var init_missing_playwright_await = __esm({
436
607
  ...options.customMatchers || []
437
608
  ]);
438
609
  function checkValidity(node) {
439
- if (validTypes.has(node.parent.type))
610
+ const parent = getParent(node);
611
+ if (!parent)
612
+ return false;
613
+ if (validTypes.has(parent.type))
440
614
  return true;
441
- if (node.parent.type === "ArrayExpression") {
442
- return checkValidity(node.parent);
615
+ if (parent.type === "ArrayExpression") {
616
+ return checkValidity(parent);
443
617
  }
444
- if (node.parent.type === "CallExpression" && node.parent.callee.type === "MemberExpression" && isIdentifier(node.parent.callee.object, "Promise") && isIdentifier(node.parent.callee.property, "all")) {
618
+ if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && isIdentifier(parent.callee.object, "Promise") && isIdentifier(parent.callee.property, "all")) {
445
619
  return true;
446
620
  }
447
- if (node.parent.type === "VariableDeclarator") {
448
- const scope = context.sourceCode.getScope(node.parent.parent);
621
+ if (parent.type === "VariableDeclarator") {
622
+ const scope = context.sourceCode.getScope(parent.parent);
449
623
  for (const ref of scope.references) {
450
624
  const refParent = ref.identifier.parent;
451
625
  if (validTypes.has(refParent.type))
@@ -458,14 +632,17 @@ var init_missing_playwright_await = __esm({
458
632
  }
459
633
  return {
460
634
  CallExpression(node) {
461
- const result = getCallType(context, node, awaitableMatchers);
462
- const isValid = result ? checkValidity(result.node) : false;
635
+ const call = parseFnCall(context, node);
636
+ if (call?.type !== "step" && call?.type !== "expect")
637
+ return;
638
+ const result = getCallType(call, awaitableMatchers);
639
+ const isValid = result ? checkValidity(node) : false;
463
640
  if (result && !isValid) {
464
641
  context.report({
465
642
  data: result.data,
466
643
  fix: (fixer) => fixer.insertTextBefore(node, "await "),
467
644
  messageId: result.messageId,
468
- node: node.callee
645
+ node: getReportNode(result.node)
469
646
  });
470
647
  }
471
648
  }
@@ -502,6 +679,10 @@ var init_missing_playwright_await = __esm({
502
679
  });
503
680
 
504
681
  // src/rules/no-commented-out-tests.ts
682
+ function getTestNames(context) {
683
+ const aliases = context.settings.playwright?.globalAliases?.test ?? [];
684
+ return ["test", ...aliases];
685
+ }
505
686
  function hasTests(context, node) {
506
687
  const testNames = getTestNames(context);
507
688
  const names = testNames.join("|");
@@ -515,7 +696,6 @@ var no_commented_out_tests_default;
515
696
  var init_no_commented_out_tests = __esm({
516
697
  "src/rules/no-commented-out-tests.ts"() {
517
698
  "use strict";
518
- init_ast();
519
699
  no_commented_out_tests_default = {
520
700
  create(context) {
521
701
  function checkNode(node) {
@@ -554,13 +734,15 @@ var init_no_conditional_expect = __esm({
554
734
  "src/rules/no-conditional-expect.ts"() {
555
735
  "use strict";
556
736
  init_ast();
737
+ init_parseFnCall();
557
738
  isCatchCall = (node) => node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "catch");
558
739
  getTestCallExpressionsFromDeclaredVariables = (context, declaredVariables) => {
559
740
  return declaredVariables.reduce(
560
741
  (acc, { references }) => [
561
742
  ...acc,
562
743
  ...references.map(({ identifier }) => getParent(identifier)).filter(
563
- (node) => node?.type === "CallExpression" && isTestCall(context, node)
744
+ // ESLint types are infurating
745
+ (node) => node?.type === "CallExpression" && isTypeOfFnCall(context, node, ["test"])
564
746
  )
565
747
  ],
566
748
  []
@@ -575,20 +757,20 @@ var init_no_conditional_expect = __esm({
575
757
  const decreaseConditionalDepth = () => inTestCase && conditionalDepth--;
576
758
  return {
577
759
  CallExpression(node) {
578
- if (isTestCall(context, node)) {
760
+ const call = parseFnCall(context, node);
761
+ if (call?.type === "test") {
579
762
  inTestCase = true;
580
763
  }
581
764
  if (isCatchCall(node)) {
582
765
  inPromiseCatch = true;
583
766
  }
584
- const expectType = getExpectType(context, node);
585
- if (inTestCase && expectType && conditionalDepth > 0) {
767
+ if (inTestCase && call?.type === "expect" && conditionalDepth > 0) {
586
768
  context.report({
587
769
  messageId: "conditionalExpect",
588
770
  node
589
771
  });
590
772
  }
591
- if (inPromiseCatch && expectType) {
773
+ if (inPromiseCatch && call?.type === "expect") {
592
774
  context.report({
593
775
  messageId: "conditionalExpect",
594
776
  node
@@ -596,7 +778,7 @@ var init_no_conditional_expect = __esm({
596
778
  }
597
779
  },
598
780
  "CallExpression:exit"(node) {
599
- if (isTestCall(context, node)) {
781
+ if (isTypeOfFnCall(context, node, ["test"])) {
600
782
  inTestCase = false;
601
783
  }
602
784
  if (isCatchCall(node)) {
@@ -647,11 +829,18 @@ var init_no_conditional_in_test = __esm({
647
829
  "src/rules/no-conditional-in-test.ts"() {
648
830
  "use strict";
649
831
  init_ast();
832
+ init_parseFnCall();
650
833
  no_conditional_in_test_default = {
651
834
  create(context) {
652
835
  function checkConditional(node) {
653
836
  const call = findParent(node, "CallExpression");
654
- if (call && isTestCall(context, call)) {
837
+ if (!call)
838
+ return;
839
+ const fnCall = parseFnCall(context, call);
840
+ if (fnCall?.type === "test" && fnCall.members.some((member) => getStringValue(member) === "skip") && call.arguments[0]?.type === "LogicalExpression") {
841
+ return;
842
+ }
843
+ if (fnCall?.type === "test" || fnCall?.type === "step") {
655
844
  context.report({ messageId: "conditionalInTest", node });
656
845
  }
657
846
  }
@@ -685,15 +874,19 @@ var init_no_duplicate_hooks = __esm({
685
874
  "src/rules/no-duplicate-hooks.ts"() {
686
875
  "use strict";
687
876
  init_ast();
877
+ init_parseFnCall();
688
878
  no_duplicate_hooks_default = {
689
879
  create(context) {
690
880
  const hookContexts = [{}];
691
881
  return {
692
882
  CallExpression(node) {
693
- if (isDescribeCall(node)) {
883
+ const call = parseFnCall(context, node);
884
+ if (!call)
885
+ return;
886
+ if (call.type === "describe") {
694
887
  hookContexts.push({});
695
888
  }
696
- if (!isTestHook(context, node)) {
889
+ if (call.type !== "hook") {
697
890
  return;
698
891
  }
699
892
  const currentLayer = hookContexts[hookContexts.length - 1];
@@ -709,7 +902,7 @@ var init_no_duplicate_hooks = __esm({
709
902
  }
710
903
  },
711
904
  "CallExpression:exit"(node) {
712
- if (isDescribeCall(node)) {
905
+ if (isTypeOfFnCall(context, node, ["describe"])) {
713
906
  hookContexts.pop();
714
907
  }
715
908
  }
@@ -838,27 +1031,33 @@ var init_no_focused_test = __esm({
838
1031
  "src/rules/no-focused-test.ts"() {
839
1032
  "use strict";
840
1033
  init_ast();
1034
+ init_parseFnCall();
841
1035
  no_focused_test_default = {
842
1036
  create(context) {
843
1037
  return {
844
1038
  CallExpression(node) {
845
- if ((isTestCall(context, node) || isDescribeCall(node)) && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "only")) {
846
- const { callee } = node;
847
- context.report({
848
- messageId: "noFocusedTest",
849
- node: node.callee.property,
850
- suggest: [
851
- {
852
- // - 1 to remove the `.only` annotation with dot notation
853
- fix: (fixer) => fixer.removeRange([
854
- callee.property.range[0] - 1,
855
- callee.range[1]
856
- ]),
857
- messageId: "suggestRemoveOnly"
858
- }
859
- ]
860
- });
1039
+ const call = parseFnCall(context, node);
1040
+ if (call?.type !== "test" && call?.type !== "describe") {
1041
+ return;
861
1042
  }
1043
+ const onlyNode = call.members.find((s) => getStringValue(s) === "only");
1044
+ if (!onlyNode)
1045
+ return;
1046
+ context.report({
1047
+ messageId: "noFocusedTest",
1048
+ node: onlyNode,
1049
+ suggest: [
1050
+ {
1051
+ fix: (fixer) => {
1052
+ return fixer.removeRange([
1053
+ onlyNode.range[0] - 1,
1054
+ onlyNode.range[1] + Number(onlyNode.type !== "Identifier")
1055
+ ]);
1056
+ },
1057
+ messageId: "suggestRemoveOnly"
1058
+ }
1059
+ ]
1060
+ });
862
1061
  }
863
1062
  };
864
1063
  },
@@ -971,7 +1170,7 @@ var no_hooks_default;
971
1170
  var init_no_hooks = __esm({
972
1171
  "src/rules/no-hooks.ts"() {
973
1172
  "use strict";
974
- init_ast();
1173
+ init_parseFnCall();
975
1174
  no_hooks_default = {
976
1175
  create(context) {
977
1176
  const options = {
@@ -981,7 +1180,9 @@ var init_no_hooks = __esm({
981
1180
  return {
982
1181
  CallExpression(node) {
983
1182
  const call = parseFnCall(context, node);
984
- if (call?.type === "hook" && !options.allow.includes(call.name)) {
1183
+ if (!call)
1184
+ return;
1185
+ if (call.type === "hook" && !options.allow.includes(call.name)) {
985
1186
  context.report({
986
1187
  data: { hookName: call.name },
987
1188
  messageId: "unexpectedHook",
@@ -1270,62 +1471,23 @@ var init_no_raw_locators = __esm({
1270
1471
  }
1271
1472
  });
1272
1473
 
1273
- // src/utils/parseExpectCall.ts
1274
- function getExpectArguments(node) {
1275
- const grandparent = node.parent.parent;
1276
- return grandparent.type === "CallExpression" ? grandparent.arguments : [];
1277
- }
1278
- function parseExpectCall(context, node) {
1279
- if (!isExpectCall(context, node)) {
1280
- return;
1281
- }
1282
- const members = getMatchers(node);
1283
- const modifiers = [];
1284
- let matcher;
1285
- members.forEach((item) => {
1286
- if (MODIFIER_NAMES.has(getStringValue(item))) {
1287
- modifiers.push(item);
1288
- } else {
1289
- matcher = item;
1290
- }
1291
- });
1292
- if (!matcher) {
1293
- return;
1294
- }
1295
- return {
1296
- args: getExpectArguments(matcher),
1297
- matcher,
1298
- matcherName: getStringValue(matcher),
1299
- members,
1300
- modifiers
1301
- };
1302
- }
1303
- var MODIFIER_NAMES;
1304
- var init_parseExpectCall = __esm({
1305
- "src/utils/parseExpectCall.ts"() {
1306
- "use strict";
1307
- init_ast();
1308
- MODIFIER_NAMES = /* @__PURE__ */ new Set(["not", "resolves", "rejects"]);
1309
- }
1310
- });
1311
-
1312
1474
  // src/rules/no-restricted-matchers.ts
1313
1475
  var no_restricted_matchers_default;
1314
1476
  var init_no_restricted_matchers = __esm({
1315
1477
  "src/rules/no-restricted-matchers.ts"() {
1316
1478
  "use strict";
1317
1479
  init_ast();
1318
- init_parseExpectCall();
1480
+ init_parseFnCall();
1319
1481
  no_restricted_matchers_default = {
1320
1482
  create(context) {
1321
1483
  const restrictedChains = context.options?.[0] ?? {};
1322
1484
  return {
1323
1485
  CallExpression(node) {
1324
- const expectCall = parseExpectCall(context, node);
1325
- if (!expectCall)
1486
+ const call = parseFnCall(context, node);
1487
+ if (call?.type !== "expect")
1326
1488
  return;
1327
1489
  Object.entries(restrictedChains).map(([restriction, message]) => {
1328
- const chain = expectCall.members;
1490
+ const chain = call.members;
1329
1491
  const restrictionLinks = restriction.split(".").length;
1330
1492
  const startIndex = chain.findIndex((_, i) => {
1331
1493
  const partial = chain.slice(i, i + restrictionLinks).map(getStringValue).join(".");
@@ -1382,34 +1544,39 @@ var init_no_skipped_test = __esm({
1382
1544
  "src/rules/no-skipped-test.ts"() {
1383
1545
  "use strict";
1384
1546
  init_ast();
1547
+ init_parseFnCall();
1385
1548
  no_skipped_test_default = {
1386
1549
  create(context) {
1387
1550
  return {
1388
1551
  CallExpression(node) {
1389
1552
  const options = context.options[0] || {};
1390
1553
  const allowConditional = !!options.allowConditional;
1391
- const { callee } = node;
1392
- if ((isTestIdentifier(context, callee) || isDescribeCall(node)) && callee.type === "MemberExpression" && isPropertyAccessor(callee, "skip")) {
1393
- const isHook = isTestCall(context, node) || isDescribeCall(node);
1394
- if (!isHook && allowConditional && node.arguments.length) {
1395
- return;
1396
- }
1397
- context.report({
1398
- messageId: "noSkippedTest",
1399
- node: isHook ? callee.property : node,
1400
- suggest: [
1401
- {
1402
- fix: (fixer) => {
1403
- return isHook ? fixer.removeRange([
1404
- callee.property.range[0] - 1,
1405
- callee.range[1]
1406
- ]) : fixer.remove(node.parent);
1407
- },
1408
- messageId: "removeSkippedTestAnnotation"
1409
- }
1410
- ]
1411
- });
1554
+ const call = parseFnCall(context, node);
1555
+ if (call?.type !== "test" && call?.type !== "describe") {
1556
+ return;
1412
1557
  }
1558
+ const skipNode = call.members.find((s) => getStringValue(s) === "skip");
1559
+ if (!skipNode)
1560
+ return;
1561
+ const isStandalone = call.type === "test" && !isFunction(node.arguments[1]);
1562
+ if (isStandalone && allowConditional && node.arguments.length) {
1563
+ return;
1564
+ }
1565
+ context.report({
1566
+ messageId: "noSkippedTest",
1567
+ node: isStandalone ? node : skipNode,
1568
+ suggest: [
1569
+ {
1570
+ fix: (fixer) => {
1571
+ return isStandalone ? fixer.remove(node.parent) : fixer.removeRange([
1572
+ skipNode.range[0] - 1,
1573
+ skipNode.range[1] + Number(skipNode.type !== "Identifier")
1574
+ ]);
1575
+ },
1576
+ messageId: "removeSkippedTestAnnotation"
1577
+ }
1578
+ ]
1579
+ });
1413
1580
  }
1414
1581
  };
1415
1582
  },
@@ -1449,7 +1616,8 @@ var init_no_standalone_expect = __esm({
1449
1616
  "src/rules/no-standalone-expect.ts"() {
1450
1617
  "use strict";
1451
1618
  init_ast();
1452
- getBlockType = (statement) => {
1619
+ init_parseFnCall();
1620
+ getBlockType = (context, statement) => {
1453
1621
  const func = getParent(statement);
1454
1622
  if (!func) {
1455
1623
  throw new Error(
@@ -1464,7 +1632,7 @@ var init_no_standalone_expect = __esm({
1464
1632
  if (expr.type === "VariableDeclarator" || expr.type === "MethodDefinition") {
1465
1633
  return "function";
1466
1634
  }
1467
- if (expr.type === "CallExpression" && isDescribeCall(expr)) {
1635
+ if (expr.type === "CallExpression" && isTypeOfFnCall(context, expr, ["describe"])) {
1468
1636
  return "describe";
1469
1637
  }
1470
1638
  }
@@ -1480,43 +1648,48 @@ var init_no_standalone_expect = __esm({
1480
1648
  }
1481
1649
  },
1482
1650
  "ArrowFunctionExpression:exit"() {
1483
- if (callStack[callStack.length - 1] === "arrow") {
1651
+ if (callStack.at(-1) === "arrow") {
1484
1652
  callStack.pop();
1485
1653
  }
1486
1654
  },
1487
1655
  BlockStatement(statement) {
1488
- const blockType = getBlockType(statement);
1656
+ const blockType = getBlockType(context, statement);
1489
1657
  if (blockType) {
1490
1658
  callStack.push(blockType);
1491
1659
  }
1492
1660
  },
1493
1661
  "BlockStatement:exit"(statement) {
1494
- if (callStack[callStack.length - 1] === getBlockType(statement)) {
1662
+ if (callStack.at(-1) === getBlockType(context, statement)) {
1495
1663
  callStack.pop();
1496
1664
  }
1497
1665
  },
1498
1666
  CallExpression(node) {
1499
- if (getExpectType(context, node)) {
1667
+ const call = parseFnCall(context, node);
1668
+ if (call?.type === "expect") {
1669
+ if (getParent(call.head.node)?.type === "MemberExpression" && call.members.length === 1 && !["assertions", "hasAssertions"].includes(
1670
+ getStringValue(call.members[0])
1671
+ )) {
1672
+ return;
1673
+ }
1500
1674
  const parent = callStack.at(-1);
1501
1675
  if (!parent || parent === "describe") {
1502
- const root = findParent(node, "CallExpression");
1503
- context.report({
1504
- messageId: "unexpectedExpect",
1505
- node: root ?? node
1506
- });
1676
+ context.report({ messageId: "unexpectedExpect", node });
1507
1677
  }
1508
1678
  return;
1509
1679
  }
1510
- if (isTestCall(context, node)) {
1680
+ if (call?.type === "test") {
1511
1681
  callStack.push("test");
1512
1682
  }
1683
+ if (call?.type === "hook") {
1684
+ callStack.push("hook");
1685
+ }
1513
1686
  if (node.callee.type === "TaggedTemplateExpression") {
1514
1687
  callStack.push("template");
1515
1688
  }
1516
1689
  },
1517
1690
  "CallExpression:exit"(node) {
1518
- const top = callStack[callStack.length - 1];
1519
- if (top === "test" && isTestCall(context, node) && node.callee.type !== "MemberExpression" || top === "template" && node.callee.type === "TaggedTemplateExpression") {
1691
+ const top = callStack.at(-1);
1692
+ if (top === "test" && isTypeOfFnCall(context, node, ["test"]) && node.callee.type !== "MemberExpression" || top === "template" && node.callee.type === "TaggedTemplateExpression") {
1520
1693
  callStack.pop();
1521
1694
  }
1522
1695
  }
@@ -1764,7 +1937,7 @@ var init_no_useless_not = __esm({
1764
1937
  "use strict";
1765
1938
  init_ast();
1766
1939
  init_fixer();
1767
- init_parseExpectCall();
1940
+ init_parseFnCall();
1768
1941
  matcherMap = {
1769
1942
  toBeDisabled: "toBeEnabled",
1770
1943
  toBeEnabled: "toBeDisabled",
@@ -1775,27 +1948,28 @@ var init_no_useless_not = __esm({
1775
1948
  create(context) {
1776
1949
  return {
1777
1950
  CallExpression(node) {
1778
- const expectCall = parseExpectCall(context, node);
1779
- if (!expectCall)
1951
+ const call = parseFnCall(context, node);
1952
+ if (call?.type !== "expect")
1780
1953
  return;
1781
- const notModifier = expectCall.modifiers.find(
1954
+ const notModifier = call.modifiers.find(
1782
1955
  (mod) => getStringValue(mod) === "not"
1783
1956
  );
1784
1957
  if (!notModifier)
1785
1958
  return;
1786
- if (expectCall.matcherName in matcherMap) {
1787
- const newMatcher = matcherMap[expectCall.matcherName];
1959
+ const matcherName = call.matcherName;
1960
+ if (matcherName in matcherMap) {
1961
+ const newMatcher = matcherMap[matcherName];
1788
1962
  context.report({
1789
- data: { new: newMatcher, old: expectCall.matcherName },
1963
+ data: { new: newMatcher, old: matcherName },
1790
1964
  fix: (fixer) => [
1791
1965
  fixer.removeRange([
1792
1966
  notModifier.range[0] - getRangeOffset(notModifier),
1793
1967
  notModifier.range[1] + 1
1794
1968
  ]),
1795
- replaceAccessorFixer(fixer, expectCall.matcher, newMatcher)
1969
+ replaceAccessorFixer(fixer, call.matcher, newMatcher)
1796
1970
  ],
1797
1971
  loc: {
1798
- end: expectCall.matcher.loc.end,
1972
+ end: call.matcher.loc.end,
1799
1973
  start: notModifier.loc.start
1800
1974
  },
1801
1975
  messageId: "noUselessNot"
@@ -1917,7 +2091,7 @@ var init_prefer_comparison_matcher = __esm({
1917
2091
  "src/rules/prefer-comparison-matcher.ts"() {
1918
2092
  "use strict";
1919
2093
  init_ast();
1920
- init_parseExpectCall();
2094
+ init_parseFnCall();
1921
2095
  isString = (node) => {
1922
2096
  return isStringLiteral(node) || node.type === "TemplateLiteral";
1923
2097
  };
@@ -1944,17 +2118,19 @@ var init_prefer_comparison_matcher = __esm({
1944
2118
  create(context) {
1945
2119
  return {
1946
2120
  CallExpression(node) {
1947
- const expectCall = parseExpectCall(context, node);
1948
- if (!expectCall || expectCall.args.length === 0)
2121
+ const call = parseFnCall(context, node);
2122
+ if (call?.type !== "expect" || call.matcherArgs.length === 0)
1949
2123
  return;
1950
- const { args, matcher } = expectCall;
1951
- const [comparison] = node.arguments;
1952
- const expectCallEnd = node.range[1];
1953
- const [matcherArg] = args;
1954
- if (comparison?.type !== "BinaryExpression" || isComparingToString(comparison) || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
2124
+ const expect = findParent(call.head.node, "CallExpression");
2125
+ if (!expect)
2126
+ return;
2127
+ const [comparison] = expect.arguments;
2128
+ const expectCallEnd = expect.range[1];
2129
+ const [matcherArg] = call.matcherArgs;
2130
+ if (comparison?.type !== "BinaryExpression" || isComparingToString(comparison) || !equalityMatchers.has(call.matcherName) || !isBooleanLiteral(matcherArg)) {
1955
2131
  return;
1956
2132
  }
1957
- const hasNot = expectCall.modifiers.some(
2133
+ const hasNot = call.modifiers.some(
1958
2134
  (node2) => getStringValue(node2) === "not"
1959
2135
  );
1960
2136
  const preferredMatcher = determineMatcher(
@@ -1967,7 +2143,7 @@ var init_prefer_comparison_matcher = __esm({
1967
2143
  context.report({
1968
2144
  data: { preferredMatcher },
1969
2145
  fix(fixer) {
1970
- const [modifier] = expectCall.modifiers;
2146
+ const [modifier] = call.modifiers;
1971
2147
  const modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
1972
2148
  return [
1973
2149
  // Replace the comparison argument with the left-hand side of the comparison
@@ -1977,7 +2153,7 @@ var init_prefer_comparison_matcher = __esm({
1977
2153
  ),
1978
2154
  // Replace the current matcher & modifier with the preferred matcher
1979
2155
  fixer.replaceTextRange(
1980
- [expectCallEnd, getParent(matcher).range[1]],
2156
+ [expectCallEnd, getParent(call.matcher).range[1]],
1981
2157
  `${modifierText}.${preferredMatcher}`
1982
2158
  ),
1983
2159
  // Replace the matcher argument with the right-hand side of the comparison
@@ -1988,7 +2164,7 @@ var init_prefer_comparison_matcher = __esm({
1988
2164
  ];
1989
2165
  },
1990
2166
  messageId: "useToBeComparison",
1991
- node: matcher
2167
+ node: call.matcher
1992
2168
  });
1993
2169
  }
1994
2170
  };
@@ -2015,30 +2191,32 @@ var init_prefer_equality_matcher = __esm({
2015
2191
  "src/rules/prefer-equality-matcher.ts"() {
2016
2192
  "use strict";
2017
2193
  init_ast();
2018
- init_parseExpectCall();
2194
+ init_parseFnCall();
2019
2195
  prefer_equality_matcher_default = {
2020
2196
  create(context) {
2021
2197
  return {
2022
2198
  CallExpression(node) {
2023
- const expectCall = parseExpectCall(context, node);
2024
- if (!expectCall || expectCall.args.length === 0)
2199
+ const call = parseFnCall(context, node);
2200
+ if (call?.type !== "expect" || call.matcherArgs.length === 0)
2025
2201
  return;
2026
- const { args, matcher } = expectCall;
2027
- const [comparison] = node.arguments;
2028
- const expectCallEnd = node.range[1];
2029
- const [matcherArg] = args;
2030
- if (comparison?.type !== "BinaryExpression" || comparison.operator !== "===" && comparison.operator !== "!==" || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg)) {
2202
+ const expect = findParent(call.head.node, "CallExpression");
2203
+ if (!expect)
2204
+ return;
2205
+ const [comparison] = expect.arguments;
2206
+ const expectCallEnd = expect.range[1];
2207
+ const [matcherArg] = call.matcherArgs;
2208
+ if (comparison?.type !== "BinaryExpression" || comparison.operator !== "===" && comparison.operator !== "!==" || !equalityMatchers.has(call.matcherName) || !isBooleanLiteral(matcherArg)) {
2031
2209
  return;
2032
2210
  }
2033
2211
  const matcherValue = getRawValue(matcherArg) === "true";
2034
- const [modifier] = expectCall.modifiers;
2035
- const hasNot = expectCall.modifiers.some(
2212
+ const [modifier] = call.modifiers;
2213
+ const hasNot = call.modifiers.some(
2036
2214
  (node2) => getStringValue(node2) === "not"
2037
2215
  );
2038
2216
  const addNotModifier = (comparison.operator === "!==" ? !matcherValue : matcherValue) === hasNot;
2039
2217
  context.report({
2040
2218
  messageId: "useEqualityMatcher",
2041
- node: matcher,
2219
+ node: call.matcher,
2042
2220
  suggest: [...equalityMatchers.keys()].map((equalityMatcher) => ({
2043
2221
  data: { matcher: equalityMatcher },
2044
2222
  fix(fixer) {
@@ -2054,7 +2232,7 @@ var init_prefer_equality_matcher = __esm({
2054
2232
  ),
2055
2233
  // replace the current matcher & modifier with the preferred matcher
2056
2234
  fixer.replaceTextRange(
2057
- [expectCallEnd, getParent(matcher).range[1]],
2235
+ [expectCallEnd, getParent(call.matcher).range[1]],
2058
2236
  `${modifierText}.${equalityMatcher}`
2059
2237
  ),
2060
2238
  // replace the matcher argument with the right-hand side of the comparison
@@ -2089,12 +2267,12 @@ var init_prefer_equality_matcher = __esm({
2089
2267
  });
2090
2268
 
2091
2269
  // src/rules/prefer-hooks-in-order.ts
2092
- var HooksOrder, prefer_hooks_in_order_default;
2270
+ var order, prefer_hooks_in_order_default;
2093
2271
  var init_prefer_hooks_in_order = __esm({
2094
2272
  "src/rules/prefer-hooks-in-order.ts"() {
2095
2273
  "use strict";
2096
- init_ast();
2097
- HooksOrder = ["beforeAll", "beforeEach", "afterEach", "afterAll"];
2274
+ init_parseFnCall();
2275
+ order = ["beforeAll", "beforeEach", "afterEach", "afterAll"];
2098
2276
  prefer_hooks_in_order_default = {
2099
2277
  create(context) {
2100
2278
  let previousHookIndex = -1;
@@ -2103,33 +2281,34 @@ var init_prefer_hooks_in_order = __esm({
2103
2281
  CallExpression(node) {
2104
2282
  if (inHook)
2105
2283
  return;
2106
- if (!isTestHook(context, node)) {
2284
+ const call = parseFnCall(context, node);
2285
+ if (call?.type !== "hook") {
2107
2286
  previousHookIndex = -1;
2108
2287
  return;
2109
2288
  }
2110
2289
  inHook = true;
2111
- const currentHook = node.callee.type === "MemberExpression" ? getStringValue(node.callee.property) : "";
2112
- const currentHookIndex = HooksOrder.indexOf(currentHook);
2290
+ const currentHook = call.name;
2291
+ const currentHookIndex = order.indexOf(currentHook);
2113
2292
  if (currentHookIndex < previousHookIndex) {
2114
- return context.report({
2293
+ context.report({
2115
2294
  data: {
2116
2295
  currentHook,
2117
- previousHook: HooksOrder[previousHookIndex]
2296
+ previousHook: order[previousHookIndex]
2118
2297
  },
2119
2298
  messageId: "reorderHooks",
2120
2299
  node
2121
2300
  });
2301
+ return;
2122
2302
  }
2123
2303
  previousHookIndex = currentHookIndex;
2124
2304
  },
2125
2305
  "CallExpression:exit"(node) {
2126
- if (isTestHook(context, node)) {
2306
+ if (isTypeOfFnCall(context, node, ["hook"])) {
2127
2307
  inHook = false;
2128
2308
  return;
2129
2309
  }
2130
- if (inHook) {
2310
+ if (inHook)
2131
2311
  return;
2132
- }
2133
2312
  previousHookIndex = -1;
2134
2313
  }
2135
2314
  };
@@ -2155,20 +2334,17 @@ var prefer_hooks_on_top_default;
2155
2334
  var init_prefer_hooks_on_top = __esm({
2156
2335
  "src/rules/prefer-hooks-on-top.ts"() {
2157
2336
  "use strict";
2158
- init_ast();
2337
+ init_parseFnCall();
2159
2338
  prefer_hooks_on_top_default = {
2160
2339
  create(context) {
2161
2340
  const stack = [false];
2162
2341
  return {
2163
2342
  CallExpression(node) {
2164
- if (isTestCall(context, node)) {
2343
+ if (isTypeOfFnCall(context, node, ["test"])) {
2165
2344
  stack[stack.length - 1] = true;
2166
2345
  }
2167
- if (stack.at(-1) && isTestHook(context, node)) {
2168
- context.report({
2169
- messageId: "noHookOnTop",
2170
- node
2171
- });
2346
+ if (stack.at(-1) && isTypeOfFnCall(context, node, ["hook"])) {
2347
+ context.report({ messageId: "noHookOnTop", node });
2172
2348
  }
2173
2349
  stack.push(false);
2174
2350
  },
@@ -2199,6 +2375,7 @@ var init_prefer_lowercase_title = __esm({
2199
2375
  "src/rules/prefer-lowercase-title.ts"() {
2200
2376
  "use strict";
2201
2377
  init_ast();
2378
+ init_parseFnCall();
2202
2379
  prefer_lowercase_title_default = {
2203
2380
  create(context) {
2204
2381
  const { allowedPrefixes, ignore, ignoreTopLevelDescribe } = {
@@ -2210,14 +2387,15 @@ var init_prefer_lowercase_title = __esm({
2210
2387
  let describeCount = 0;
2211
2388
  return {
2212
2389
  CallExpression(node) {
2213
- const method = isDescribeCall(node) ? "test.describe" : isTestCall(context, node) ? "test" : null;
2214
- if (method === "test.describe") {
2390
+ const call = parseFnCall(context, node);
2391
+ if (call?.type !== "describe" && call?.type !== "test") {
2392
+ return;
2393
+ }
2394
+ if (call.type === "describe") {
2215
2395
  describeCount++;
2216
2396
  if (ignoreTopLevelDescribe && describeCount === 1) {
2217
2397
  return;
2218
2398
  }
2219
- } else if (!method) {
2220
- return;
2221
2399
  }
2222
2400
  const [title] = node.arguments;
2223
2401
  if (!isStringNode(title)) {
@@ -2227,6 +2405,7 @@ var init_prefer_lowercase_title = __esm({
2227
2405
  if (!description || allowedPrefixes.some((name) => description.startsWith(name))) {
2228
2406
  return;
2229
2407
  }
2408
+ const method = call.type === "describe" ? "test.describe" : "test";
2230
2409
  const firstCharacter = description.charAt(0);
2231
2410
  if (!firstCharacter || firstCharacter === firstCharacter.toLowerCase() || ignore.includes(method)) {
2232
2411
  return;
@@ -2246,7 +2425,7 @@ var init_prefer_lowercase_title = __esm({
2246
2425
  });
2247
2426
  },
2248
2427
  "CallExpression:exit"(node) {
2249
- if (isDescribeCall(node)) {
2428
+ if (isTypeOfFnCall(context, node, ["describe"])) {
2250
2429
  describeCount--;
2251
2430
  }
2252
2431
  }
@@ -2299,22 +2478,24 @@ var init_prefer_strict_equal = __esm({
2299
2478
  "src/rules/prefer-strict-equal.ts"() {
2300
2479
  "use strict";
2301
2480
  init_fixer();
2302
- init_parseExpectCall();
2481
+ init_parseFnCall();
2303
2482
  prefer_strict_equal_default = {
2304
2483
  create(context) {
2305
2484
  return {
2306
2485
  CallExpression(node) {
2307
- const expectCall = parseExpectCall(context, node);
2308
- if (expectCall?.matcherName === "toEqual") {
2486
+ const call = parseFnCall(context, node);
2487
+ if (call?.type !== "expect")
2488
+ return;
2489
+ if (call.matcherName === "toEqual") {
2309
2490
  context.report({
2310
2491
  messageId: "useToStrictEqual",
2311
- node: expectCall.matcher,
2492
+ node: call.matcher,
2312
2493
  suggest: [
2313
2494
  {
2314
2495
  fix: (fixer) => {
2315
2496
  return replaceAccessorFixer(
2316
2497
  fixer,
2317
- expectCall.matcher,
2498
+ call.matcher,
2318
2499
  "toStrictEqual"
2319
2500
  );
2320
2501
  },
@@ -2347,8 +2528,8 @@ var init_prefer_strict_equal = __esm({
2347
2528
  });
2348
2529
 
2349
2530
  // src/rules/prefer-to-be.ts
2350
- function shouldUseToBe(expectCall) {
2351
- let arg = expectCall.args[0];
2531
+ function shouldUseToBe(call) {
2532
+ let arg = call.matcherArgs[0];
2352
2533
  if (arg.type === "UnaryExpression" && arg.operator === "-") {
2353
2534
  arg = arg.argument;
2354
2535
  }
@@ -2357,14 +2538,14 @@ function shouldUseToBe(expectCall) {
2357
2538
  }
2358
2539
  return arg.type === "TemplateLiteral";
2359
2540
  }
2360
- function reportPreferToBe(context, expectCall, whatToBe, notModifier) {
2541
+ function reportPreferToBe(context, call, whatToBe, notModifier) {
2361
2542
  context.report({
2362
2543
  fix(fixer) {
2363
2544
  const fixes = [
2364
- replaceAccessorFixer(fixer, expectCall.matcher, `toBe${whatToBe}`)
2545
+ replaceAccessorFixer(fixer, call.matcher, `toBe${whatToBe}`)
2365
2546
  ];
2366
- if (expectCall.args?.length && whatToBe !== "") {
2367
- fixes.push(fixer.remove(expectCall.args[0]));
2547
+ if (call.matcherArgs?.length && whatToBe !== "") {
2548
+ fixes.push(fixer.remove(call.matcherArgs[0]));
2368
2549
  }
2369
2550
  if (notModifier) {
2370
2551
  const [start, end] = notModifier.range;
@@ -2373,7 +2554,7 @@ function reportPreferToBe(context, expectCall, whatToBe, notModifier) {
2373
2554
  return fixes;
2374
2555
  },
2375
2556
  messageId: `useToBe${whatToBe}`,
2376
- node: expectCall.matcher
2557
+ node: call.matcher
2377
2558
  });
2378
2559
  }
2379
2560
  var prefer_to_be_default;
@@ -2382,42 +2563,42 @@ var init_prefer_to_be = __esm({
2382
2563
  "use strict";
2383
2564
  init_ast();
2384
2565
  init_fixer();
2385
- init_parseExpectCall();
2566
+ init_parseFnCall();
2386
2567
  prefer_to_be_default = {
2387
2568
  create(context) {
2388
2569
  return {
2389
2570
  CallExpression(node) {
2390
- const expectCall = parseExpectCall(context, node);
2391
- if (!expectCall)
2571
+ const call = parseFnCall(context, node);
2572
+ if (call?.type !== "expect")
2392
2573
  return;
2393
2574
  const notMatchers = ["toBeUndefined", "toBeDefined"];
2394
- const notModifier = expectCall.modifiers.find(
2575
+ const notModifier = call.modifiers.find(
2395
2576
  (node2) => getStringValue(node2) === "not"
2396
2577
  );
2397
- if (notModifier && notMatchers.includes(expectCall.matcherName)) {
2578
+ if (notModifier && notMatchers.includes(call.matcherName)) {
2398
2579
  return reportPreferToBe(
2399
2580
  context,
2400
- expectCall,
2401
- expectCall.matcherName === "toBeDefined" ? "Undefined" : "Defined",
2581
+ call,
2582
+ call.matcherName === "toBeDefined" ? "Undefined" : "Defined",
2402
2583
  notModifier
2403
2584
  );
2404
2585
  }
2405
- const firstArg = expectCall.args[0];
2406
- if (!equalityMatchers.has(expectCall.matcherName) || !firstArg) {
2586
+ const firstArg = call.matcherArgs[0];
2587
+ if (!equalityMatchers.has(call.matcherName) || !firstArg) {
2407
2588
  return;
2408
2589
  }
2409
2590
  if (firstArg.type === "Literal" && firstArg.value === null) {
2410
- return reportPreferToBe(context, expectCall, "Null");
2591
+ return reportPreferToBe(context, call, "Null");
2411
2592
  }
2412
2593
  if (isIdentifier(firstArg, "undefined")) {
2413
2594
  const name = notModifier ? "Defined" : "Undefined";
2414
- return reportPreferToBe(context, expectCall, name, notModifier);
2595
+ return reportPreferToBe(context, call, name, notModifier);
2415
2596
  }
2416
2597
  if (isIdentifier(firstArg, "NaN")) {
2417
- return reportPreferToBe(context, expectCall, "NaN");
2598
+ return reportPreferToBe(context, call, "NaN");
2418
2599
  }
2419
- if (shouldUseToBe(expectCall) && expectCall.matcherName !== "toBe") {
2420
- reportPreferToBe(context, expectCall, "");
2600
+ if (shouldUseToBe(call) && call.matcherName !== "toBe") {
2601
+ reportPreferToBe(context, call, "");
2421
2602
  }
2422
2603
  }
2423
2604
  };
@@ -2450,22 +2631,25 @@ var init_prefer_to_contain = __esm({
2450
2631
  "src/rules/prefer-to-contain.ts"() {
2451
2632
  "use strict";
2452
2633
  init_ast();
2453
- init_parseExpectCall();
2634
+ init_parseFnCall();
2454
2635
  isFixableIncludesCallExpression = (node) => node.type === "CallExpression" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "includes") && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement";
2455
2636
  prefer_to_contain_default = {
2456
2637
  create(context) {
2457
2638
  return {
2458
2639
  CallExpression(node) {
2459
- const expectCall = parseExpectCall(context, node);
2460
- if (!expectCall || expectCall.args.length === 0)
2640
+ const call = parseFnCall(context, node);
2641
+ if (call?.type !== "expect" || call.matcherArgs.length === 0)
2642
+ return;
2643
+ const expect = findParent(call.head.node, "CallExpression");
2644
+ if (!expect)
2461
2645
  return;
2462
- const { args, matcher, matcherName } = expectCall;
2463
- const [includesCall] = node.arguments;
2464
- const [matcherArg] = args;
2465
- if (!includesCall || matcherArg.type === "SpreadElement" || !equalityMatchers.has(matcherName) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
2646
+ const [includesCall] = expect.arguments;
2647
+ const { matcher } = call;
2648
+ const [matcherArg] = call.matcherArgs;
2649
+ if (!includesCall || matcherArg.type === "SpreadElement" || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
2466
2650
  return;
2467
2651
  }
2468
- const notModifier = expectCall.modifiers.find(
2652
+ const notModifier = call.modifiers.find(
2469
2653
  (node2) => getStringValue(node2) === "not"
2470
2654
  );
2471
2655
  context.report({
@@ -2484,7 +2668,7 @@ var init_prefer_to_contain = __esm({
2484
2668
  ),
2485
2669
  // replace the matcher argument with the value from the "includes"
2486
2670
  fixer.replaceText(
2487
- expectCall.args[0],
2671
+ call.matcherArgs[0],
2488
2672
  context.sourceCode.getText(includesCall.arguments[0])
2489
2673
  )
2490
2674
  ];
@@ -2528,17 +2712,17 @@ var init_prefer_to_have_count = __esm({
2528
2712
  "use strict";
2529
2713
  init_ast();
2530
2714
  init_fixer();
2531
- init_parseExpectCall();
2715
+ init_parseFnCall();
2532
2716
  prefer_to_have_count_default = {
2533
2717
  create(context) {
2534
2718
  return {
2535
2719
  CallExpression(node) {
2536
- const expectCall = parseExpectCall(context, node);
2537
- if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
2720
+ const call = parseFnCall(context, node);
2721
+ if (call?.type !== "expect" || !equalityMatchers.has(call.matcherName)) {
2538
2722
  return;
2539
2723
  }
2540
- const [argument] = node.arguments;
2541
- if (argument.type !== "AwaitExpression" || argument.argument.type !== "CallExpression" || argument.argument.callee.type !== "MemberExpression" || !isPropertyAccessor(argument.argument.callee, "count")) {
2724
+ const [argument] = call.args;
2725
+ if (argument?.type !== "AwaitExpression" || argument.argument.type !== "CallExpression" || argument.argument.callee.type !== "MemberExpression" || !isPropertyAccessor(argument.argument.callee, "count")) {
2542
2726
  return;
2543
2727
  }
2544
2728
  const callee = argument.argument.callee;
@@ -2556,13 +2740,13 @@ var init_prefer_to_have_count = __esm({
2556
2740
  argument.argument.range[1]
2557
2741
  ]),
2558
2742
  // replace the current matcher with "toHaveCount"
2559
- replaceAccessorFixer(fixer, expectCall.matcher, "toHaveCount"),
2743
+ replaceAccessorFixer(fixer, call.matcher, "toHaveCount"),
2560
2744
  // insert "await" to before "expect()"
2561
2745
  fixer.insertTextBefore(node, "await ")
2562
2746
  ];
2563
2747
  },
2564
2748
  messageId: "useToHaveCount",
2565
- node: expectCall.matcher
2749
+ node: call.matcher
2566
2750
  });
2567
2751
  }
2568
2752
  };
@@ -2592,16 +2776,16 @@ var init_prefer_to_have_length = __esm({
2592
2776
  "use strict";
2593
2777
  init_ast();
2594
2778
  init_fixer();
2595
- init_parseExpectCall();
2779
+ init_parseFnCall();
2596
2780
  prefer_to_have_length_default = {
2597
2781
  create(context) {
2598
2782
  return {
2599
2783
  CallExpression(node) {
2600
- const expectCall = parseExpectCall(context, node);
2601
- if (!expectCall || !equalityMatchers.has(expectCall.matcherName)) {
2784
+ const call = parseFnCall(context, node);
2785
+ if (call?.type !== "expect" || !equalityMatchers.has(call.matcherName)) {
2602
2786
  return;
2603
2787
  }
2604
- const [argument] = node.arguments;
2788
+ const [argument] = call.args;
2605
2789
  if (argument?.type !== "MemberExpression" || !isPropertyAccessor(argument, "length")) {
2606
2790
  return;
2607
2791
  }
@@ -2614,11 +2798,11 @@ var init_prefer_to_have_length = __esm({
2614
2798
  argument.range[1]
2615
2799
  ]),
2616
2800
  // replace the current matcher with "toHaveLength"
2617
- replaceAccessorFixer(fixer, expectCall.matcher, "toHaveLength")
2801
+ replaceAccessorFixer(fixer, call.matcher, "toHaveLength")
2618
2802
  ];
2619
2803
  },
2620
2804
  messageId: "useToHaveLength",
2621
- node: expectCall.matcher
2805
+ node: call.matcher
2622
2806
  });
2623
2807
  }
2624
2808
  };
@@ -2659,7 +2843,7 @@ var init_prefer_web_first_assertions = __esm({
2659
2843
  "src/rules/prefer-web-first-assertions.ts"() {
2660
2844
  "use strict";
2661
2845
  init_ast();
2662
- init_parseExpectCall();
2846
+ init_parseFnCall();
2663
2847
  methods3 = {
2664
2848
  getAttribute: {
2665
2849
  matcher: "toHaveAttribute",
@@ -2705,24 +2889,26 @@ var init_prefer_web_first_assertions = __esm({
2705
2889
  create(context) {
2706
2890
  return {
2707
2891
  CallExpression(node) {
2708
- const expectCall = parseExpectCall(context, node);
2709
- if (!expectCall)
2892
+ const call = parseFnCall(context, node);
2893
+ if (call?.type !== "expect")
2894
+ return;
2895
+ const expect = findParent(call.head.node, "CallExpression");
2896
+ if (!expect)
2710
2897
  return;
2711
- const arg = dereference(context, node.arguments[0]);
2898
+ const arg = dereference(context, call.args[0]);
2712
2899
  if (!arg || arg.type !== "AwaitExpression" || arg.argument.type !== "CallExpression" || arg.argument.callee.type !== "MemberExpression") {
2713
2900
  return;
2714
2901
  }
2715
- if (!supportedMatchers.has(expectCall.matcherName))
2902
+ if (!supportedMatchers.has(call.matcherName))
2716
2903
  return;
2717
2904
  const method = getStringValue(arg.argument.callee.property);
2718
2905
  const methodConfig = methods3[method];
2719
2906
  if (!methodConfig)
2720
2907
  return;
2721
- const { args, matcher } = expectCall;
2722
- const notModifier = expectCall.modifiers.find(
2908
+ const notModifier = call.modifiers.find(
2723
2909
  (mod) => getStringValue(mod) === "not"
2724
2910
  );
2725
- const isFalsy = methodConfig.type === "boolean" && (!!args.length && isBooleanLiteral(args[0], false) || expectCall.matcherName === "toBeFalsy");
2911
+ const isFalsy = methodConfig.type === "boolean" && (!!call.matcherArgs.length && isBooleanLiteral(call.matcherArgs[0], false) || call.matcherName === "toBeFalsy");
2726
2912
  const isInverse = methodConfig.inverse ? notModifier || isFalsy : notModifier && isFalsy;
2727
2913
  const newMatcher = +!!notModifier ^ +isFalsy && methodConfig.inverse || methodConfig.matcher;
2728
2914
  const { callee } = arg.argument;
@@ -2736,7 +2922,7 @@ var init_prefer_web_first_assertions = __esm({
2736
2922
  const methodEnd = methodArgs.length ? methodArgs.at(-1).range[1] + 1 : callee.property.range[1] + 2;
2737
2923
  const fixes = [
2738
2924
  // Add await to the expect call
2739
- fixer.insertTextBefore(node, "await "),
2925
+ fixer.insertTextBefore(expect, "await "),
2740
2926
  // Remove the await keyword
2741
2927
  fixer.replaceTextRange(
2742
2928
  [arg.range[0], arg.argument.range[0]],
@@ -2753,23 +2939,23 @@ var init_prefer_web_first_assertions = __esm({
2753
2939
  fixes.push(fixer.removeRange([notRange[0], notRange[1] + 1]));
2754
2940
  }
2755
2941
  if (!methodConfig.inverse && !notModifier && isFalsy) {
2756
- fixes.push(fixer.insertTextBefore(matcher, "not."));
2942
+ fixes.push(fixer.insertTextBefore(call.matcher, "not."));
2757
2943
  }
2758
- fixes.push(fixer.replaceText(matcher, newMatcher));
2759
- const [matcherArg] = args ?? [];
2944
+ fixes.push(fixer.replaceText(call.matcher, newMatcher));
2945
+ const [matcherArg] = call.matcherArgs ?? [];
2760
2946
  if (matcherArg && isBooleanLiteral(matcherArg)) {
2761
2947
  fixes.push(fixer.remove(matcherArg));
2762
2948
  } else if (methodConfig.prop && matcherArg) {
2763
2949
  const propArg = methodConfig.prop;
2764
2950
  const variable = getStringValue(matcherArg);
2765
- const args2 = `{ ${propArg}: ${variable} }`;
2766
- fixes.push(fixer.replaceText(matcherArg, args2));
2951
+ const args = `{ ${propArg}: ${variable} }`;
2952
+ fixes.push(fixer.replaceText(matcherArg, args));
2767
2953
  }
2768
2954
  const hasOtherArgs = !!methodArgs.filter(
2769
2955
  (arg2) => !isBooleanLiteral(arg2)
2770
2956
  ).length;
2771
2957
  if (methodArgs) {
2772
- const range = matcher.range;
2958
+ const range = call.matcher.range;
2773
2959
  const stringArgs = methodArgs.map((arg2) => getRawValue(arg2)).concat(hasOtherArgs ? "" : []).join(", ");
2774
2960
  fixes.push(
2775
2961
  fixer.insertTextAfterRange(
@@ -2781,7 +2967,7 @@ var init_prefer_web_first_assertions = __esm({
2781
2967
  return fixes;
2782
2968
  },
2783
2969
  messageId: "useWebFirstAssertion",
2784
- node
2970
+ node: expect
2785
2971
  });
2786
2972
  }
2787
2973
  };
@@ -2809,6 +2995,7 @@ var init_require_hook = __esm({
2809
2995
  "src/rules/require-hook.ts"() {
2810
2996
  "use strict";
2811
2997
  init_ast();
2998
+ init_parseFnCall();
2812
2999
  isNullOrUndefined = (node) => {
2813
3000
  return node.type === "Literal" && node.value === null || isIdentifier(node, "undefined");
2814
3001
  };
@@ -2848,7 +3035,7 @@ var init_require_hook = __esm({
2848
3035
  };
2849
3036
  return {
2850
3037
  CallExpression(node) {
2851
- if (!isDescribeCall(node) || node.arguments.length < 2) {
3038
+ if (!isTypeOfFnCall(context, node, ["describe"]) || node.arguments.length < 2) {
2852
3039
  return;
2853
3040
  }
2854
3041
  const [, testFn] = node.arguments;
@@ -2896,17 +3083,23 @@ var init_require_soft_assertions = __esm({
2896
3083
  "src/rules/require-soft-assertions.ts"() {
2897
3084
  "use strict";
2898
3085
  init_ast();
3086
+ init_parseFnCall();
2899
3087
  require_soft_assertions_default = {
2900
3088
  create(context) {
2901
3089
  return {
2902
3090
  CallExpression(node) {
2903
- if (getExpectType(context, node) === "standalone") {
2904
- context.report({
2905
- fix: (fixer) => fixer.insertTextAfter(node.callee, ".soft"),
2906
- messageId: "requireSoft",
2907
- node: node.callee
2908
- });
3091
+ const call = parseFnCall(context, node);
3092
+ if (call?.type !== "expect" || call.modifiers.some((m) => {
3093
+ const name = getStringValue(m);
3094
+ return name === "soft" || name === "poll";
3095
+ })) {
3096
+ return;
2909
3097
  }
3098
+ context.report({
3099
+ fix: (fixer) => fixer.insertTextAfter(call.head.node, ".soft"),
3100
+ messageId: "requireSoft",
3101
+ node: call.head.node
3102
+ });
2910
3103
  }
2911
3104
  };
2912
3105
  },
@@ -2927,13 +3120,54 @@ var init_require_soft_assertions = __esm({
2927
3120
  }
2928
3121
  });
2929
3122
 
3123
+ // src/rules/require-to-throw-message.ts
3124
+ var require_to_throw_message_default;
3125
+ var init_require_to_throw_message = __esm({
3126
+ "src/rules/require-to-throw-message.ts"() {
3127
+ "use strict";
3128
+ init_ast();
3129
+ init_parseFnCall();
3130
+ require_to_throw_message_default = {
3131
+ create(context) {
3132
+ return {
3133
+ CallExpression(node) {
3134
+ const call = parseFnCall(context, node);
3135
+ if (call?.type !== "expect")
3136
+ return;
3137
+ if (call.matcherArgs.length === 0 && ["toThrow", "toThrowError"].includes(call.matcherName) && !call.modifiers.some((nod) => getStringValue(nod) === "not")) {
3138
+ context.report({
3139
+ data: { matcherName: call.matcherName },
3140
+ messageId: "addErrorMessage",
3141
+ node: call.matcher
3142
+ });
3143
+ }
3144
+ }
3145
+ };
3146
+ },
3147
+ meta: {
3148
+ docs: {
3149
+ category: "Best Practices",
3150
+ description: "Require a message for `toThrow()`",
3151
+ recommended: false,
3152
+ url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-to-throw-message.md"
3153
+ },
3154
+ messages: {
3155
+ addErrorMessage: "Add an error message to {{ matcherName }}()"
3156
+ },
3157
+ schema: [],
3158
+ type: "suggestion"
3159
+ }
3160
+ };
3161
+ }
3162
+ });
3163
+
2930
3164
  // src/rules/require-top-level-describe.ts
2931
3165
  var require_top_level_describe_default;
2932
3166
  var init_require_top_level_describe = __esm({
2933
3167
  "src/rules/require-top-level-describe.ts"() {
2934
3168
  "use strict";
2935
- init_ast();
2936
3169
  init_misc();
3170
+ init_parseFnCall();
2937
3171
  require_top_level_describe_default = {
2938
3172
  create(context) {
2939
3173
  const { maxTopLevelDescribes } = {
@@ -2944,7 +3178,10 @@ var init_require_top_level_describe = __esm({
2944
3178
  let describeCount = 0;
2945
3179
  return {
2946
3180
  CallExpression(node) {
2947
- if (isDescribeCall(node)) {
3181
+ const call = parseFnCall(context, node);
3182
+ if (!call)
3183
+ return;
3184
+ if (call.type === "describe") {
2948
3185
  describeCount++;
2949
3186
  if (describeCount === 1) {
2950
3187
  topLevelDescribeCount++;
@@ -2957,15 +3194,15 @@ var init_require_top_level_describe = __esm({
2957
3194
  }
2958
3195
  }
2959
3196
  } else if (!describeCount) {
2960
- if (isTestCall(context, node)) {
3197
+ if (call.type === "test") {
2961
3198
  context.report({ messageId: "unexpectedTest", node: node.callee });
2962
- } else if (isTestHook(context, node)) {
3199
+ } else if (call.type === "hook") {
2963
3200
  context.report({ messageId: "unexpectedHook", node: node.callee });
2964
3201
  }
2965
3202
  }
2966
3203
  },
2967
3204
  "CallExpression:exit"(node) {
2968
- if (isDescribeCall(node)) {
3205
+ if (isTypeOfFnCall(context, node, ["describe"])) {
2969
3206
  describeCount--;
2970
3207
  }
2971
3208
  }
@@ -3001,23 +3238,121 @@ var init_require_top_level_describe = __esm({
3001
3238
  }
3002
3239
  });
3003
3240
 
3004
- // src/rules/valid-expect.ts
3005
- function isMatcherCalled(node) {
3006
- if (node.parent.type !== "MemberExpression") {
3007
- return {
3008
- called: node.parent.type === "CallExpression" && node.parent.callee === node,
3009
- node
3241
+ // src/rules/valid-describe-callback.ts
3242
+ var paramsLocation, valid_describe_callback_default;
3243
+ var init_valid_describe_callback = __esm({
3244
+ "src/rules/valid-describe-callback.ts"() {
3245
+ "use strict";
3246
+ init_ast();
3247
+ init_parseFnCall();
3248
+ paramsLocation = (params) => {
3249
+ const [first] = params;
3250
+ const last = params[params.length - 1];
3251
+ return {
3252
+ end: last.loc.end,
3253
+ start: first.loc.start
3254
+ };
3255
+ };
3256
+ valid_describe_callback_default = {
3257
+ create(context) {
3258
+ return {
3259
+ CallExpression(node) {
3260
+ const call = parseFnCall(context, node);
3261
+ if (call?.type !== "describe")
3262
+ return;
3263
+ if (node.arguments.length < 1) {
3264
+ return context.report({
3265
+ loc: node.loc,
3266
+ messageId: "nameAndCallback"
3267
+ });
3268
+ }
3269
+ const [, callback] = node.arguments;
3270
+ if (!callback) {
3271
+ context.report({
3272
+ loc: paramsLocation(node.arguments),
3273
+ messageId: "nameAndCallback"
3274
+ });
3275
+ return;
3276
+ }
3277
+ if (!isFunction(callback)) {
3278
+ context.report({
3279
+ loc: paramsLocation(node.arguments),
3280
+ messageId: "secondArgumentMustBeFunction"
3281
+ });
3282
+ return;
3283
+ }
3284
+ if (callback.async) {
3285
+ context.report({
3286
+ messageId: "noAsyncDescribeCallback",
3287
+ node: callback
3288
+ });
3289
+ }
3290
+ if (call.members.every((s) => getStringValue(s) !== "each") && callback.params.length) {
3291
+ context.report({
3292
+ loc: paramsLocation(callback.params),
3293
+ messageId: "unexpectedDescribeArgument"
3294
+ });
3295
+ }
3296
+ if (callback.body.type === "CallExpression") {
3297
+ context.report({
3298
+ messageId: "unexpectedReturnInDescribe",
3299
+ node: callback
3300
+ });
3301
+ }
3302
+ if (callback.body.type === "BlockStatement") {
3303
+ callback.body.body.forEach((node2) => {
3304
+ if (node2.type === "ReturnStatement") {
3305
+ context.report({
3306
+ messageId: "unexpectedReturnInDescribe",
3307
+ node: node2
3308
+ });
3309
+ }
3310
+ });
3311
+ }
3312
+ }
3313
+ };
3314
+ },
3315
+ meta: {
3316
+ docs: {
3317
+ category: "Possible Errors",
3318
+ description: "Enforce valid `describe()` callback",
3319
+ recommended: true,
3320
+ url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-describe-callback.md"
3321
+ },
3322
+ messages: {
3323
+ nameAndCallback: "Describe requires name and callback arguments",
3324
+ noAsyncDescribeCallback: "No async describe callback",
3325
+ secondArgumentMustBeFunction: "Second argument must be function",
3326
+ unexpectedDescribeArgument: "Unexpected argument(s) in describe callback",
3327
+ unexpectedReturnInDescribe: "Unexpected return statement in describe callback"
3328
+ },
3329
+ schema: [],
3330
+ type: "problem"
3331
+ }
3010
3332
  };
3011
3333
  }
3012
- return isMatcherCalled(node.parent);
3013
- }
3014
- var valid_expect_default;
3334
+ });
3335
+
3336
+ // src/rules/valid-expect.ts
3337
+ var findTopMostMemberExpression, valid_expect_default;
3015
3338
  var init_valid_expect = __esm({
3016
3339
  "src/rules/valid-expect.ts"() {
3017
3340
  "use strict";
3018
3341
  init_ast();
3019
3342
  init_misc();
3020
- init_parseExpectCall();
3343
+ init_parseFnCall();
3344
+ findTopMostMemberExpression = (node) => {
3345
+ let topMostMemberExpression = node;
3346
+ let parent = getParent(node);
3347
+ while (parent) {
3348
+ if (parent.type !== "MemberExpression") {
3349
+ break;
3350
+ }
3351
+ topMostMemberExpression = parent;
3352
+ parent = parent.parent;
3353
+ }
3354
+ return topMostMemberExpression;
3355
+ };
3021
3356
  valid_expect_default = {
3022
3357
  create(context) {
3023
3358
  const options = {
@@ -3029,32 +3364,70 @@ var init_valid_expect = __esm({
3029
3364
  const maxArgs = Math.max(options.minArgs, options.maxArgs);
3030
3365
  return {
3031
3366
  CallExpression(node) {
3032
- if (!isExpectCall(context, node))
3033
- return;
3034
- const expectCall = parseExpectCall(context, node);
3035
- if (!expectCall) {
3036
- context.report({ messageId: "matcherNotFound", node });
3037
- } else {
3038
- const result = isMatcherCalled(node);
3039
- if (!result.called) {
3367
+ const call = parseFnCallWithReason(context, node);
3368
+ if (typeof call === "string") {
3369
+ const reportingNode = node.parent?.type === "MemberExpression" ? findTopMostMemberExpression(node.parent).property : node;
3370
+ if (call === "matcher-not-found") {
3040
3371
  context.report({
3041
- messageId: "matcherNotCalled",
3042
- node: result.node.type === "MemberExpression" ? result.node.property : result.node
3372
+ messageId: "matcherNotFound",
3373
+ node: reportingNode
3043
3374
  });
3375
+ return;
3044
3376
  }
3377
+ if (call === "matcher-not-called") {
3378
+ context.report({
3379
+ messageId: isSupportedAccessor(reportingNode) && modifiers.has(getStringValue(reportingNode)) ? "matcherNotFound" : "matcherNotCalled",
3380
+ node: reportingNode
3381
+ });
3382
+ }
3383
+ if (call === "modifier-unknown") {
3384
+ context.report({
3385
+ messageId: "modifierUnknown",
3386
+ node: reportingNode
3387
+ });
3388
+ return;
3389
+ }
3390
+ return;
3391
+ } else if (call?.type !== "expect") {
3392
+ return;
3045
3393
  }
3046
- if (node.arguments.length < minArgs) {
3394
+ const expect = findParent(call.head.node, "CallExpression");
3395
+ if (!expect)
3396
+ return;
3397
+ if (expect.arguments.length < minArgs) {
3398
+ const expectLength = getStringValue(call.head.node).length;
3399
+ const loc = {
3400
+ end: {
3401
+ column: expect.loc.start.column + expectLength + 1,
3402
+ line: expect.loc.start.line
3403
+ },
3404
+ start: {
3405
+ column: expect.loc.start.column + expectLength,
3406
+ line: expect.loc.start.line
3407
+ }
3408
+ };
3047
3409
  context.report({
3048
3410
  data: getAmountData(minArgs),
3411
+ loc,
3049
3412
  messageId: "notEnoughArgs",
3050
- node
3413
+ node: expect
3051
3414
  });
3052
3415
  }
3053
- if (node.arguments.length > maxArgs) {
3416
+ if (expect.arguments.length > maxArgs) {
3417
+ const { start } = expect.arguments[maxArgs].loc;
3418
+ const { end } = expect.arguments.at(-1).loc;
3419
+ const loc = {
3420
+ end: {
3421
+ column: end.column,
3422
+ line: end.line
3423
+ },
3424
+ start
3425
+ };
3054
3426
  context.report({
3055
3427
  data: getAmountData(maxArgs),
3428
+ loc,
3056
3429
  messageId: "tooManyArgs",
3057
- node
3430
+ node: expect
3058
3431
  });
3059
3432
  }
3060
3433
  }
@@ -3095,12 +3468,265 @@ var init_valid_expect = __esm({
3095
3468
  }
3096
3469
  });
3097
3470
 
3471
+ // src/rules/valid-expect-in-promise.ts
3472
+ var isPromiseChainCall, isTestCaseCallWithCallbackArg, isPromiseMethodThatUsesValue, isValueAwaitedInElements, isValueAwaitedInArguments, getLeftMostCallExpression, isValueAwaitedOrReturned, findFirstBlockBodyUp, isDirectlyWithinTestCaseCall, isVariableAwaitedOrReturned, valid_expect_in_promise_default;
3473
+ var init_valid_expect_in_promise = __esm({
3474
+ "src/rules/valid-expect-in-promise.ts"() {
3475
+ "use strict";
3476
+ init_ast();
3477
+ init_parseFnCall();
3478
+ isPromiseChainCall = (node) => {
3479
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && isSupportedAccessor(node.callee.property)) {
3480
+ if (node.arguments.length === 0) {
3481
+ return false;
3482
+ }
3483
+ switch (getStringValue(node.callee.property)) {
3484
+ case "then":
3485
+ return node.arguments.length < 3;
3486
+ case "catch":
3487
+ case "finally":
3488
+ return node.arguments.length < 2;
3489
+ }
3490
+ }
3491
+ return false;
3492
+ };
3493
+ isTestCaseCallWithCallbackArg = (context, node) => {
3494
+ const jestCallFn = parseFnCall(context, node);
3495
+ if (jestCallFn?.type !== "test") {
3496
+ return false;
3497
+ }
3498
+ const isJestEach = jestCallFn.members.some(
3499
+ (s) => getStringValue(s) === "each"
3500
+ );
3501
+ if (isJestEach && node.callee.type !== "TaggedTemplateExpression") {
3502
+ return true;
3503
+ }
3504
+ const [, callback] = node.arguments;
3505
+ const callbackArgIndex = Number(isJestEach);
3506
+ return callback && isFunction(callback) && callback.params.length === 1 + callbackArgIndex;
3507
+ };
3508
+ isPromiseMethodThatUsesValue = (node, identifier) => {
3509
+ const name = getStringValue(identifier);
3510
+ if (node.argument == null)
3511
+ return false;
3512
+ if (node.argument.type === "CallExpression" && node.argument.arguments.length > 0) {
3513
+ const nodeName = getNodeName(node.argument);
3514
+ if (["Promise.all", "Promise.allSettled"].includes(nodeName)) {
3515
+ const [firstArg] = node.argument.arguments;
3516
+ if (firstArg.type === "ArrayExpression" && firstArg.elements.some((nod) => nod && isIdentifier(nod, name))) {
3517
+ return true;
3518
+ }
3519
+ }
3520
+ if (["Promise.resolve", "Promise.reject"].includes(nodeName) && node.argument.arguments.length === 1) {
3521
+ return isIdentifier(node.argument.arguments[0], name);
3522
+ }
3523
+ }
3524
+ return isIdentifier(node.argument, name);
3525
+ };
3526
+ isValueAwaitedInElements = (name, elements) => {
3527
+ for (const element of elements) {
3528
+ if (element?.type === "AwaitExpression" && isIdentifier(element.argument, name)) {
3529
+ return true;
3530
+ }
3531
+ if (element?.type === "ArrayExpression" && isValueAwaitedInElements(name, element.elements)) {
3532
+ return true;
3533
+ }
3534
+ }
3535
+ return false;
3536
+ };
3537
+ isValueAwaitedInArguments = (name, call) => {
3538
+ let node = call;
3539
+ while (node) {
3540
+ if (node.type === "CallExpression") {
3541
+ if (isValueAwaitedInElements(name, node.arguments)) {
3542
+ return true;
3543
+ }
3544
+ node = node.callee;
3545
+ }
3546
+ if (node.type !== "MemberExpression") {
3547
+ break;
3548
+ }
3549
+ node = node.object;
3550
+ }
3551
+ return false;
3552
+ };
3553
+ getLeftMostCallExpression = (call) => {
3554
+ let leftMostCallExpression = call;
3555
+ let node = call;
3556
+ while (node) {
3557
+ if (node.type === "CallExpression") {
3558
+ leftMostCallExpression = node;
3559
+ node = node.callee;
3560
+ }
3561
+ if (node.type !== "MemberExpression") {
3562
+ break;
3563
+ }
3564
+ node = node.object;
3565
+ }
3566
+ return leftMostCallExpression;
3567
+ };
3568
+ isValueAwaitedOrReturned = (context, identifier, body) => {
3569
+ const name = getStringValue(identifier);
3570
+ for (const node of body) {
3571
+ if (node.range[0] <= identifier.range[0]) {
3572
+ continue;
3573
+ }
3574
+ if (node.type === "ReturnStatement") {
3575
+ return isPromiseMethodThatUsesValue(node, identifier);
3576
+ }
3577
+ if (node.type === "ExpressionStatement") {
3578
+ if (node.expression.type === "CallExpression") {
3579
+ if (isValueAwaitedInArguments(name, node.expression)) {
3580
+ return true;
3581
+ }
3582
+ const leftMostCall = getLeftMostCallExpression(node.expression);
3583
+ const call = parseFnCall(context, node.expression);
3584
+ if (call?.type === "expect" && leftMostCall.arguments.length > 0 && isIdentifier(leftMostCall.arguments[0], name)) {
3585
+ if (call.members.some((m) => {
3586
+ const v = getStringValue(m);
3587
+ return v === "resolves" || v === "rejects";
3588
+ })) {
3589
+ return true;
3590
+ }
3591
+ }
3592
+ }
3593
+ if (node.expression.type === "AwaitExpression" && isPromiseMethodThatUsesValue(node.expression, identifier)) {
3594
+ return true;
3595
+ }
3596
+ if (node.expression.type === "AssignmentExpression") {
3597
+ if (isIdentifier(node.expression.left, name) && getNodeName(node.expression.right)?.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) {
3598
+ continue;
3599
+ }
3600
+ break;
3601
+ }
3602
+ }
3603
+ if (node.type === "BlockStatement" && isValueAwaitedOrReturned(context, identifier, node.body)) {
3604
+ return true;
3605
+ }
3606
+ }
3607
+ return false;
3608
+ };
3609
+ findFirstBlockBodyUp = (node) => {
3610
+ let parent = node;
3611
+ while (parent) {
3612
+ if (parent.type === "BlockStatement") {
3613
+ return parent.body;
3614
+ }
3615
+ parent = getParent(parent);
3616
+ }
3617
+ throw new Error(
3618
+ `Could not find BlockStatement - please file a github issue at https://github.com/playwright-community/eslint-plugin-playwright`
3619
+ );
3620
+ };
3621
+ isDirectlyWithinTestCaseCall = (context, node) => {
3622
+ let parent = node;
3623
+ while (parent) {
3624
+ if (isFunction(parent)) {
3625
+ parent = parent.parent;
3626
+ return parent?.type === "CallExpression" && isTypeOfFnCall(context, parent, ["test"]);
3627
+ }
3628
+ parent = getParent(parent);
3629
+ }
3630
+ return false;
3631
+ };
3632
+ isVariableAwaitedOrReturned = (context, variable) => {
3633
+ const body = findFirstBlockBodyUp(variable);
3634
+ if (!isIdentifier(variable.id)) {
3635
+ return true;
3636
+ }
3637
+ return isValueAwaitedOrReturned(context, variable.id, body);
3638
+ };
3639
+ valid_expect_in_promise_default = {
3640
+ create(context) {
3641
+ let inTestCaseWithDoneCallback = false;
3642
+ const chains = [];
3643
+ return {
3644
+ CallExpression(node) {
3645
+ if (isTestCaseCallWithCallbackArg(context, node)) {
3646
+ inTestCaseWithDoneCallback = true;
3647
+ return;
3648
+ }
3649
+ if (isPromiseChainCall(node)) {
3650
+ chains.unshift(false);
3651
+ return;
3652
+ }
3653
+ if (chains.length > 0 && isTypeOfFnCall(context, node, ["expect"])) {
3654
+ chains[0] = true;
3655
+ }
3656
+ },
3657
+ "CallExpression:exit"(node) {
3658
+ if (inTestCaseWithDoneCallback) {
3659
+ if (isTypeOfFnCall(context, node, ["test"])) {
3660
+ inTestCaseWithDoneCallback = false;
3661
+ }
3662
+ return;
3663
+ }
3664
+ if (!isPromiseChainCall(node)) {
3665
+ return;
3666
+ }
3667
+ const hasExpectCall = chains.shift();
3668
+ if (!hasExpectCall) {
3669
+ return;
3670
+ }
3671
+ const { parent } = findTopMostCallExpression(node);
3672
+ if (!parent || !isDirectlyWithinTestCaseCall(context, parent)) {
3673
+ return;
3674
+ }
3675
+ switch (parent.type) {
3676
+ case "VariableDeclarator": {
3677
+ if (isVariableAwaitedOrReturned(context, parent)) {
3678
+ return;
3679
+ }
3680
+ break;
3681
+ }
3682
+ case "AssignmentExpression": {
3683
+ if (parent.left.type === "Identifier" && isValueAwaitedOrReturned(
3684
+ context,
3685
+ parent.left,
3686
+ findFirstBlockBodyUp(parent)
3687
+ )) {
3688
+ return;
3689
+ }
3690
+ break;
3691
+ }
3692
+ case "ExpressionStatement":
3693
+ break;
3694
+ case "ReturnStatement":
3695
+ case "AwaitExpression":
3696
+ default:
3697
+ return;
3698
+ }
3699
+ context.report({
3700
+ messageId: "expectInFloatingPromise",
3701
+ node: parent
3702
+ });
3703
+ }
3704
+ };
3705
+ },
3706
+ meta: {
3707
+ docs: {
3708
+ category: "Best Practices",
3709
+ description: "Require promises that have expectations in their chain to be valid",
3710
+ recommended: true,
3711
+ url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect-in-promise.md"
3712
+ },
3713
+ messages: {
3714
+ expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in its chain are called"
3715
+ },
3716
+ schema: [],
3717
+ type: "suggestion"
3718
+ }
3719
+ };
3720
+ }
3721
+ });
3722
+
3098
3723
  // src/rules/valid-title.ts
3099
3724
  var doesBinaryExpressionContainStringNode, quoteStringValue, compileMatcherPattern, compileMatcherPatterns, MatcherAndMessageSchema, valid_title_default;
3100
3725
  var init_valid_title = __esm({
3101
3726
  "src/rules/valid-title.ts"() {
3102
3727
  "use strict";
3103
3728
  init_ast();
3729
+ init_parseFnCall();
3104
3730
  doesBinaryExpressionContainStringNode = (binaryExp) => {
3105
3731
  if (isStringNode(binaryExp.right)) {
3106
3732
  return true;
@@ -3154,9 +3780,8 @@ var init_valid_title = __esm({
3154
3780
  const mustMatchPatterns = compileMatcherPatterns(mustMatch ?? {});
3155
3781
  return {
3156
3782
  CallExpression(node) {
3157
- const isDescribe = isDescribeCall(node);
3158
- const isTest = isTestCall(context, node);
3159
- if (!isDescribe && !isTest) {
3783
+ const call = parseFnCall(context, node);
3784
+ if (call?.type !== "test" && call?.type !== "describe") {
3160
3785
  return;
3161
3786
  }
3162
3787
  const [argument] = node.arguments;
@@ -3167,7 +3792,7 @@ var init_valid_title = __esm({
3167
3792
  if (argument.type === "BinaryExpression" && doesBinaryExpressionContainStringNode(argument)) {
3168
3793
  return;
3169
3794
  }
3170
- if (!(isDescribe && ignoreTypeOfDescribeName || isTest && ignoreTypeOfTestName) && argument.type !== "TemplateLiteral") {
3795
+ if (!(call.type === "describe" && ignoreTypeOfDescribeName || call.type === "test" && ignoreTypeOfTestName) && argument.type !== "TemplateLiteral") {
3171
3796
  context.report({
3172
3797
  loc: argument.loc,
3173
3798
  messageId: "titleMustBeString"
@@ -3176,10 +3801,10 @@ var init_valid_title = __esm({
3176
3801
  return;
3177
3802
  }
3178
3803
  const title = getStringValue(argument);
3179
- const functionName = isDescribe ? "describe" : "test";
3804
+ const functionName = call.type;
3180
3805
  if (!title) {
3181
3806
  context.report({
3182
- data: { functionName },
3807
+ data: { functionName: call.type },
3183
3808
  messageId: "emptyTitle",
3184
3809
  node
3185
3810
  });
@@ -3358,8 +3983,11 @@ var require_src = __commonJS({
3358
3983
  init_prefer_web_first_assertions();
3359
3984
  init_require_hook();
3360
3985
  init_require_soft_assertions();
3986
+ init_require_to_throw_message();
3361
3987
  init_require_top_level_describe();
3988
+ init_valid_describe_callback();
3362
3989
  init_valid_expect();
3990
+ init_valid_expect_in_promise();
3363
3991
  init_valid_title();
3364
3992
  var index = {
3365
3993
  configs: {},
@@ -3404,8 +4032,11 @@ var require_src = __commonJS({
3404
4032
  "prefer-web-first-assertions": prefer_web_first_assertions_default,
3405
4033
  "require-hook": require_hook_default,
3406
4034
  "require-soft-assertions": require_soft_assertions_default,
4035
+ "require-to-throw-message": require_to_throw_message_default,
3407
4036
  "require-top-level-describe": require_top_level_describe_default,
4037
+ "valid-describe-callback": valid_describe_callback_default,
3408
4038
  "valid-expect": valid_expect_default,
4039
+ "valid-expect-in-promise": valid_expect_in_promise_default,
3409
4040
  "valid-title": valid_title_default
3410
4041
  }
3411
4042
  };
@@ -3432,7 +4063,9 @@ var require_src = __commonJS({
3432
4063
  "playwright/no-wait-for-selector": "warn",
3433
4064
  "playwright/no-wait-for-timeout": "warn",
3434
4065
  "playwright/prefer-web-first-assertions": "error",
4066
+ "playwright/valid-describe-callback": "error",
3435
4067
  "playwright/valid-expect": "error",
4068
+ "playwright/valid-expect-in-promise": "error",
3436
4069
  "playwright/valid-title": "error"
3437
4070
  }
3438
4071
  };