eslint-plugin-playwright 2.4.1 → 2.5.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.
Files changed (4) hide show
  1. package/README.md +57 -54
  2. package/dist/index.cjs +617 -538
  3. package/index.d.ts +1 -1
  4. package/package.json +24 -25
package/dist/index.cjs CHANGED
@@ -119,10 +119,9 @@ var Chain = class {
119
119
  }
120
120
  };
121
121
  var resolvePossibleAliasedGlobal = (context, global) => {
122
- const globalAliases = context.settings.playwright?.globalAliases ?? {};
123
- const alias = Object.entries(globalAliases).find(
124
- ([, aliases]) => aliases.includes(global)
125
- );
122
+ const settings = context.settings;
123
+ const globalAliases = settings?.playwright?.globalAliases ?? {};
124
+ const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(global));
126
125
  return alias?.[0] ?? null;
127
126
  };
128
127
  var resolveToPlaywrightFn = (context, accessor) => {
@@ -135,16 +134,11 @@ var resolveToPlaywrightFn = (context, accessor) => {
135
134
  };
136
135
  };
137
136
  function determinePlaywrightFnGroup(name) {
138
- if (name === "step")
139
- return "step";
140
- if (name === "expect")
141
- return "expect";
142
- if (name === "describe")
143
- return "describe";
144
- if (name === "test")
145
- return "test";
146
- if (testHooks.has(name))
147
- return "hook";
137
+ if (name === "step") return "step";
138
+ if (name === "expect") return "expect";
139
+ if (name === "describe") return "describe";
140
+ if (name === "test") return "test";
141
+ if (testHooks.has(name)) return "hook";
148
142
  return "unknown";
149
143
  }
150
144
  var modifiers = /* @__PURE__ */ new Set(["not", "resolves", "rejects"]);
@@ -186,11 +180,7 @@ function getExpectArguments(call) {
186
180
  return findParent(call.head.node, "CallExpression")?.arguments ?? [];
187
181
  }
188
182
  var parseExpectCall = (chain, call, stage) => {
189
- const modifiersAndMatcher = findModifiersAndMatcher(
190
- chain,
191
- call.members,
192
- stage
193
- );
183
+ const modifiersAndMatcher = findModifiersAndMatcher(chain, call.members, stage);
194
184
  if (!modifiersAndMatcher) {
195
185
  return null;
196
186
  }
@@ -207,31 +197,29 @@ var parseExpectCall = (chain, call, stage) => {
207
197
  };
208
198
  var findTopMostCallExpression = (node) => {
209
199
  let top = node;
210
- let parent = getParent(node);
200
+ let parent = node.parent;
211
201
  let child = node;
212
202
  while (parent) {
213
203
  if (parent.type === "CallExpression" && parent.callee === child) {
214
204
  top = parent;
215
205
  node = parent;
216
- parent = getParent(parent);
206
+ parent = parent.parent;
217
207
  continue;
218
208
  }
219
209
  if (parent.type !== "MemberExpression") {
220
210
  break;
221
211
  }
222
212
  child = parent;
223
- parent = getParent(parent);
213
+ parent = parent.parent;
224
214
  }
225
215
  return top;
226
216
  };
227
217
  function parse(context, node) {
228
218
  const chain = new Chain(node);
229
- if (!chain.nodes?.length)
230
- return null;
219
+ if (!chain.nodes?.length) return null;
231
220
  const [first, ...rest] = chain.nodes;
232
221
  const resolved = resolveToPlaywrightFn(context, first);
233
- if (!resolved)
234
- return null;
222
+ if (!resolved) return null;
235
223
  let name = resolved.original ?? resolved.local;
236
224
  const links = [name, ...rest.map((link) => getStringValue(link))];
237
225
  if (name === "test" && links.length > 1) {
@@ -259,22 +247,21 @@ function parse(context, node) {
259
247
  parsedFnCall.members.shift();
260
248
  }
261
249
  const result = parseExpectCall(chain, parsedFnCall, stage);
262
- if (!result)
263
- return null;
250
+ if (!result) return null;
264
251
  if (typeof result === "string" && findTopMostCallExpression(node) !== node) {
265
252
  return null;
266
253
  }
267
254
  if (result === "matcher-not-found") {
268
- if (getParent(node)?.type === "MemberExpression") {
255
+ if (node.parent?.type === "MemberExpression") {
269
256
  return "matcher-not-called";
270
257
  }
271
258
  }
272
259
  return result;
273
260
  }
274
- if (chain.nodes.slice(0, chain.nodes.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
261
+ if (chain.nodes.slice(0, chain.nodes.length - 1).some((n) => n.parent?.type !== "MemberExpression")) {
275
262
  return null;
276
263
  }
277
- const parent = getParent(node);
264
+ const parent = node.parent;
278
265
  if (parent?.type === "CallExpression" || parent?.type === "MemberExpression") {
279
266
  return null;
280
267
  }
@@ -308,8 +295,7 @@ var isTypeOfFnCall = (context, node, types) => {
308
295
 
309
296
  // src/utils/ast.ts
310
297
  function getStringValue(node) {
311
- if (!node)
312
- return "";
298
+ if (!node) return "";
313
299
  return node.type === "Identifier" ? node.name : node.type === "TemplateLiteral" ? node.quasis[0].value.raw : node.type === "Literal" && typeof node.value === "string" ? node.value : "";
314
300
  }
315
301
  function getRawValue(node) {
@@ -336,13 +322,9 @@ function isPropertyAccessor(node, name) {
336
322
  const value = getStringValue(node.property);
337
323
  return typeof name === "string" ? value === name : name.test(value);
338
324
  }
339
- function getParent(node) {
340
- return node.parent;
341
- }
342
325
  function findParent(node, type) {
343
326
  const parent = node.parent;
344
- if (!parent)
345
- return;
327
+ if (!parent) return;
346
328
  return parent.type === type ? parent : findParent(parent, type);
347
329
  }
348
330
  function dig(node, identifier) {
@@ -371,8 +353,8 @@ function getNodeName(node) {
371
353
  }
372
354
  return null;
373
355
  }
374
- var isVariableDeclarator = (node) => node.type === "VariableDeclarator";
375
- var isAssignmentExpression = (node) => node.type === "AssignmentExpression";
356
+ var isVariableDeclarator = (node) => node?.type === "VariableDeclarator";
357
+ var isAssignmentExpression = (node) => node?.type === "AssignmentExpression";
376
358
  function isNodeLastAssignment(node, assignment) {
377
359
  if (node.range && assignment.range && node.range[0] < assignment.range[1]) {
378
360
  return false;
@@ -389,6 +371,30 @@ function dereference(context, node) {
389
371
  const expr = parents.filter(isAssignmentExpression).reverse().find((assignment) => isNodeLastAssignment(node, assignment));
390
372
  return expr?.right ?? decl?.init;
391
373
  }
374
+ var getActualLastToken = (sourceCode, node) => {
375
+ const semiToken = sourceCode.getLastToken(node);
376
+ const prevToken = sourceCode.getTokenBefore(semiToken);
377
+ const nextToken = sourceCode.getTokenAfter(semiToken);
378
+ const isSemicolonLessStyle = !!prevToken && !!nextToken && prevToken.range[0] >= node.range[0] && semiToken.type === "Punctuator" && semiToken.value === ";" && semiToken.loc.start.line !== prevToken.loc.end.line && semiToken.loc.end.line === nextToken.loc.start.line;
379
+ return isSemicolonLessStyle ? prevToken : semiToken;
380
+ };
381
+ var getPaddingLineSequences = (prevNode, nextNode, sourceCode) => {
382
+ const pairs = [];
383
+ let prevToken = getActualLastToken(sourceCode, prevNode);
384
+ if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
385
+ do {
386
+ const token = sourceCode.getTokenAfter(prevToken, {
387
+ includeComments: true
388
+ });
389
+ if (token.loc.start.line - prevToken.loc.end.line >= 2) {
390
+ pairs.push([prevToken, token]);
391
+ }
392
+ prevToken = token;
393
+ } while (prevToken.range[0] < nextNode.range[0]);
394
+ }
395
+ return pairs;
396
+ };
397
+ var areTokensOnSameLine = (left, right) => left.loc.end.line === right.loc.start.line;
392
398
 
393
399
  // src/utils/createRule.ts
394
400
  function interpolate(str, data) {
@@ -406,7 +412,10 @@ function createRule(rule) {
406
412
  const { data, messageId: messageId2, ...rest } = options;
407
413
  const message = messages?.[messageId2];
408
414
  return context.report(
409
- message ? { ...rest, message: interpolate(message, data) } : options
415
+ message ? {
416
+ ...rest,
417
+ message: interpolate(message, data)
418
+ } : options
410
419
  );
411
420
  }
412
421
  return context.report(options);
@@ -430,6 +439,154 @@ function createRule(rule) {
430
439
  };
431
440
  }
432
441
 
442
+ // src/utils/scope.ts
443
+ function createScopeInfo() {
444
+ let scope = null;
445
+ return {
446
+ enter() {
447
+ scope = { prevNode: null, upper: scope };
448
+ },
449
+ exit() {
450
+ scope = scope.upper;
451
+ },
452
+ get prevNode() {
453
+ return scope.prevNode;
454
+ },
455
+ set prevNode(node) {
456
+ scope.prevNode = node;
457
+ }
458
+ };
459
+ }
460
+
461
+ // src/rules/consistent-spacing-between-blocks.ts
462
+ var STATEMENT_LIST_PARENTS = /* @__PURE__ */ new Set([
463
+ "Program",
464
+ "BlockStatement",
465
+ "SwitchCase",
466
+ "SwitchStatement"
467
+ ]);
468
+ function isValidParent(parentType) {
469
+ return STATEMENT_LIST_PARENTS.has(parentType);
470
+ }
471
+ function fixPadding(prevNode, nextNode, ctx) {
472
+ const { ruleContext, sourceCode } = ctx;
473
+ const paddingLines = getPaddingLineSequences(prevNode, nextNode, sourceCode);
474
+ if (paddingLines.length > 0) {
475
+ return;
476
+ }
477
+ ruleContext.report({
478
+ fix(fixer) {
479
+ let prevToken = getActualLastToken(sourceCode, prevNode);
480
+ const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, {
481
+ /**
482
+ * Skip the trailing comments of the previous node. This inserts a blank
483
+ * line after the last trailing comment.
484
+ *
485
+ * For example:
486
+ *
487
+ * foo() // trailing comment.
488
+ * // comment.
489
+ * bar()
490
+ *
491
+ * Get fixed to:
492
+ *
493
+ * foo() // trailing comment.
494
+ *
495
+ * // comment.
496
+ * bar()
497
+ */
498
+ filter(token) {
499
+ if (areTokensOnSameLine(prevToken, token)) {
500
+ prevToken = token;
501
+ return false;
502
+ }
503
+ return true;
504
+ },
505
+ includeComments: true
506
+ }) || nextNode;
507
+ const insertText = areTokensOnSameLine(prevToken, nextToken) ? "\n\n" : "\n";
508
+ return fixer.insertTextAfter(prevToken, insertText);
509
+ },
510
+ messageId: "missingWhitespace",
511
+ node: nextNode
512
+ });
513
+ }
514
+ function isTestNode(node, ctx) {
515
+ let curNode = node;
516
+ if (curNode.type === "ExpressionStatement") {
517
+ curNode = curNode.expression;
518
+ } else if (curNode.type === "VariableDeclaration") {
519
+ const decl = curNode.declarations.at(-1);
520
+ if (decl.init == null) {
521
+ return false;
522
+ }
523
+ curNode = decl.init;
524
+ }
525
+ if (curNode.type === "AwaitExpression") {
526
+ curNode = curNode.argument;
527
+ }
528
+ if (curNode.type !== "CallExpression") {
529
+ return false;
530
+ }
531
+ return isTypeOfFnCall(ctx.ruleContext, curNode, ["describe", "test", "step", "hook"]);
532
+ }
533
+ function testPadding(prevNode, nextNode, ctx) {
534
+ while (nextNode.type === "LabeledStatement") {
535
+ nextNode = nextNode.body;
536
+ }
537
+ if (isTestNode(prevNode, ctx) || isTestNode(nextNode, ctx)) {
538
+ fixPadding(prevNode, nextNode, ctx);
539
+ return;
540
+ }
541
+ }
542
+ function verifyNode(node, ctx) {
543
+ const { scopeInfo } = ctx;
544
+ if (!isValidParent(node.parent.type)) {
545
+ return;
546
+ }
547
+ if (scopeInfo.prevNode) {
548
+ testPadding(scopeInfo.prevNode, node, ctx);
549
+ }
550
+ scopeInfo.prevNode = node;
551
+ }
552
+ var consistent_spacing_between_blocks_default = createRule({
553
+ create(context) {
554
+ const scopeInfo = createScopeInfo();
555
+ const ctx = {
556
+ ruleContext: context,
557
+ scopeInfo,
558
+ sourceCode: context.sourceCode
559
+ };
560
+ return {
561
+ ":statement": (node) => verifyNode(node, ctx),
562
+ "BlockStatement": scopeInfo.enter,
563
+ "BlockStatement:exit": scopeInfo.exit,
564
+ "Program": scopeInfo.enter,
565
+ "Program:exit": scopeInfo.enter,
566
+ "SwitchCase"(node) {
567
+ verifyNode(node, ctx);
568
+ scopeInfo.enter();
569
+ },
570
+ "SwitchCase:exit": scopeInfo.exit,
571
+ "SwitchStatement": scopeInfo.enter,
572
+ "SwitchStatement:exit": scopeInfo.exit
573
+ };
574
+ },
575
+ meta: {
576
+ docs: {
577
+ description: "Enforces a blank line between Playwright test blocks (e.g., test, test.step, test.beforeEach, etc.).",
578
+ recommended: true,
579
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/consistent-spacing-between-blocks.md"
580
+ },
581
+ fixable: "whitespace",
582
+ messages: {
583
+ missingWhitespace: "Expected blank line before this statement."
584
+ },
585
+ schema: [],
586
+ type: "layout"
587
+ }
588
+ });
589
+
433
590
  // src/rules/expect-expect.ts
434
591
  var expect_expect_default = createRule({
435
592
  create(context) {
@@ -438,9 +595,7 @@ var expect_expect_default = createRule({
438
595
  assertFunctionPatterns: [],
439
596
  ...context.options?.[0] ?? {}
440
597
  };
441
- const patterns = options.assertFunctionPatterns.map(
442
- (pattern) => new RegExp(pattern)
443
- );
598
+ const patterns = options.assertFunctionPatterns.map((pattern) => new RegExp(pattern));
444
599
  const unchecked = [];
445
600
  function checkExpressions(nodes) {
446
601
  for (const node of nodes) {
@@ -461,7 +616,7 @@ var expect_expect_default = createRule({
461
616
  return false;
462
617
  }
463
618
  return {
464
- CallExpression(node) {
619
+ "CallExpression"(node) {
465
620
  const call = parseFnCall(context, node);
466
621
  if (call?.type === "test") {
467
622
  unchecked.push(node);
@@ -482,7 +637,7 @@ var expect_expect_default = createRule({
482
637
  category: "Best Practices",
483
638
  description: "Enforce assertion to be made in a test body",
484
639
  recommended: true,
485
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/expect-expect.md"
640
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/expect-expect.md"
486
641
  },
487
642
  messages: {
488
643
  noAssertions: "Test has no assertions"
@@ -516,18 +671,18 @@ var max_expects_default = createRule({
516
671
  };
517
672
  let count = 0;
518
673
  const maybeResetCount = (node) => {
519
- const parent = getParent(node);
674
+ const parent = node.parent;
520
675
  const isTestFn = parent?.type !== "CallExpression" || isTypeOfFnCall(context, parent, ["test"]);
521
676
  if (isTestFn) {
522
677
  count = 0;
523
678
  }
524
679
  };
525
680
  return {
526
- ArrowFunctionExpression: maybeResetCount,
681
+ "ArrowFunctionExpression": maybeResetCount,
527
682
  "ArrowFunctionExpression:exit": maybeResetCount,
528
- CallExpression(node) {
683
+ "CallExpression"(node) {
529
684
  const call = parseFnCall(context, node);
530
- if (call?.type !== "expect" || getParent(call.head.node)?.type === "MemberExpression") {
685
+ if (call?.type !== "expect" || call.head.node.parent?.type === "MemberExpression") {
531
686
  return;
532
687
  }
533
688
  count += 1;
@@ -542,7 +697,7 @@ var max_expects_default = createRule({
542
697
  });
543
698
  }
544
699
  },
545
- FunctionExpression: maybeResetCount,
700
+ "FunctionExpression": maybeResetCount,
546
701
  "FunctionExpression:exit": maybeResetCount
547
702
  };
548
703
  },
@@ -551,7 +706,7 @@ var max_expects_default = createRule({
551
706
  category: "Best Practices",
552
707
  description: "Enforces a maximum number assertion calls in a test body",
553
708
  recommended: false,
554
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/max-expects.md"
709
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/max-expects.md"
555
710
  },
556
711
  messages: {
557
712
  exceededMaxAssertion: "Too many assertion calls ({{ count }}) - maximum allowed is {{ max }}"
@@ -579,7 +734,7 @@ var max_nested_describe_default = createRule({
579
734
  const max = options[0]?.max ?? 5;
580
735
  const describes = [];
581
736
  return {
582
- CallExpression(node) {
737
+ "CallExpression"(node) {
583
738
  if (isTypeOfFnCall(context, node, ["describe"])) {
584
739
  describes.unshift(node);
585
740
  if (describes.length > max) {
@@ -606,7 +761,7 @@ var max_nested_describe_default = createRule({
606
761
  category: "Best Practices",
607
762
  description: "Enforces a maximum depth to nested describe calls",
608
763
  recommended: true,
609
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/max-nested-describe.md"
764
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/max-nested-describe.md"
610
765
  },
611
766
  messages: {
612
767
  exceededMaxDepth: "Maximum describe call depth exceeded ({{ depth }}). Maximum allowed is {{ max }}."
@@ -628,11 +783,7 @@ var max_nested_describe_default = createRule({
628
783
  });
629
784
 
630
785
  // src/rules/missing-playwright-await.ts
631
- var validTypes = /* @__PURE__ */ new Set([
632
- "AwaitExpression",
633
- "ReturnStatement",
634
- "ArrowFunctionExpression"
635
- ]);
786
+ var validTypes = /* @__PURE__ */ new Set(["AwaitExpression", "ReturnStatement", "ArrowFunctionExpression"]);
636
787
  var expectPlaywrightMatchers = [
637
788
  "toBeChecked",
638
789
  "toBeDisabled",
@@ -683,7 +834,7 @@ var playwrightTestMatchers = [
683
834
  "toContainClass"
684
835
  ];
685
836
  function getReportNode(node) {
686
- const parent = getParent(node);
837
+ const parent = node.parent;
687
838
  return parent?.type === "MemberExpression" ? parent : node;
688
839
  }
689
840
  function getCallType(call, awaitableMatchers) {
@@ -711,14 +862,11 @@ var missing_playwright_await_default = createRule({
711
862
  ...options.customMatchers || []
712
863
  ]);
713
864
  function checkValidity(node, visited) {
714
- const parent = getParent(node);
715
- if (!parent)
716
- return false;
717
- if (visited.has(parent))
718
- return false;
865
+ const parent = node.parent;
866
+ if (!parent) return false;
867
+ if (visited.has(parent)) return false;
719
868
  visited.add(parent);
720
- if (validTypes.has(parent.type))
721
- return true;
869
+ if (validTypes.has(parent.type)) return true;
722
870
  if (parent.type === "ArrayExpression") {
723
871
  return checkValidity(parent, visited);
724
872
  }
@@ -729,12 +877,9 @@ var missing_playwright_await_default = createRule({
729
877
  const scope = context.sourceCode.getScope(parent.parent);
730
878
  for (const ref of scope.references) {
731
879
  const refParent = ref.identifier.parent;
732
- if (visited.has(refParent))
733
- continue;
734
- if (validTypes.has(refParent.type))
735
- return true;
736
- if (checkValidity(refParent, visited))
737
- return true;
880
+ if (visited.has(refParent)) continue;
881
+ if (validTypes.has(refParent.type)) return true;
882
+ if (checkValidity(refParent, visited)) return true;
738
883
  }
739
884
  }
740
885
  return false;
@@ -742,8 +887,7 @@ var missing_playwright_await_default = createRule({
742
887
  return {
743
888
  CallExpression(node) {
744
889
  const call = parseFnCall(context, node);
745
- if (call?.type !== "step" && call?.type !== "expect")
746
- return;
890
+ if (call?.type !== "step" && call?.type !== "expect") return;
747
891
  const result = getCallType(call, awaitableMatchers);
748
892
  const isValid = result ? checkValidity(node, /* @__PURE__ */ new Set()) : false;
749
893
  if (result && !isValid) {
@@ -762,7 +906,7 @@ var missing_playwright_await_default = createRule({
762
906
  category: "Possible Errors",
763
907
  description: `Identify false positives when async Playwright APIs are not properly awaited.`,
764
908
  recommended: true,
765
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/missing-playwright-await.md"
909
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/missing-playwright-await.md"
766
910
  },
767
911
  fixable: "code",
768
912
  messages: {
@@ -788,23 +932,20 @@ var missing_playwright_await_default = createRule({
788
932
 
789
933
  // src/rules/no-commented-out-tests.ts
790
934
  function getTestNames(context) {
791
- const aliases = context.settings.playwright?.globalAliases?.test ?? [];
935
+ const settings = context.settings;
936
+ const aliases = settings?.playwright?.globalAliases?.test ?? [];
792
937
  return ["test", ...aliases];
793
938
  }
794
939
  function hasTests(context, node) {
795
940
  const testNames = getTestNames(context);
796
941
  const names = testNames.join("|");
797
- const regex = new RegExp(
798
- `^\\s*(${names}|describe)(\\.\\w+|\\[['"]\\w+['"]\\])?\\s*\\(`,
799
- "mu"
800
- );
942
+ const regex = new RegExp(`^\\s*(${names}|describe)(\\.\\w+|\\[['"]\\w+['"]\\])?\\s*\\(`, "mu");
801
943
  return regex.test(node.value);
802
944
  }
803
945
  var no_commented_out_tests_default = createRule({
804
946
  create(context) {
805
947
  function checkNode(node) {
806
- if (!hasTests(context, node))
807
- return;
948
+ if (!hasTests(context, node)) return;
808
949
  context.report({
809
950
  messageId: "commentedTests",
810
951
  node
@@ -821,7 +962,7 @@ var no_commented_out_tests_default = createRule({
821
962
  category: "Best Practices",
822
963
  description: "Disallow commented out tests",
823
964
  recommended: true,
824
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-commented-out-tests.md"
965
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-commented-out-tests.md"
825
966
  },
826
967
  messages: {
827
968
  commentedTests: "Some tests seem to be commented"
@@ -836,7 +977,7 @@ var getTestCallExpressionsFromDeclaredVariables = (context, declaredVariables) =
836
977
  return declaredVariables.reduce(
837
978
  (acc, { references }) => [
838
979
  ...acc,
839
- ...references.map(({ identifier }) => getParent(identifier)).filter(
980
+ ...references.map(({ identifier }) => identifier.parent).filter(
840
981
  // ESLint types are infurating
841
982
  (node) => node?.type === "CallExpression" && isTypeOfFnCall(context, node, ["test"])
842
983
  )
@@ -852,7 +993,7 @@ var no_conditional_expect_default = createRule({
852
993
  const increaseConditionalDepth = () => inTestCase && conditionalDepth++;
853
994
  const decreaseConditionalDepth = () => inTestCase && conditionalDepth--;
854
995
  return {
855
- CallExpression(node) {
996
+ "CallExpression"(node) {
856
997
  const call = parseFnCall(context, node);
857
998
  if (call?.type === "test") {
858
999
  inTestCase = true;
@@ -881,11 +1022,11 @@ var no_conditional_expect_default = createRule({
881
1022
  inPromiseCatch = false;
882
1023
  }
883
1024
  },
884
- CatchClause: increaseConditionalDepth,
1025
+ "CatchClause": increaseConditionalDepth,
885
1026
  "CatchClause:exit": decreaseConditionalDepth,
886
- ConditionalExpression: increaseConditionalDepth,
1027
+ "ConditionalExpression": increaseConditionalDepth,
887
1028
  "ConditionalExpression:exit": decreaseConditionalDepth,
888
- FunctionDeclaration(node) {
1029
+ "FunctionDeclaration"(node) {
889
1030
  const declaredVariables = context.sourceCode.getDeclaredVariables(node);
890
1031
  const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
891
1032
  context,
@@ -895,11 +1036,11 @@ var no_conditional_expect_default = createRule({
895
1036
  inTestCase = true;
896
1037
  }
897
1038
  },
898
- IfStatement: increaseConditionalDepth,
1039
+ "IfStatement": increaseConditionalDepth,
899
1040
  "IfStatement:exit": decreaseConditionalDepth,
900
- LogicalExpression: increaseConditionalDepth,
1041
+ "LogicalExpression": increaseConditionalDepth,
901
1042
  "LogicalExpression:exit": decreaseConditionalDepth,
902
- SwitchStatement: increaseConditionalDepth,
1043
+ "SwitchStatement": increaseConditionalDepth,
903
1044
  "SwitchStatement:exit": decreaseConditionalDepth
904
1045
  };
905
1046
  },
@@ -908,10 +1049,10 @@ var no_conditional_expect_default = createRule({
908
1049
  category: "Best Practices",
909
1050
  description: "Disallow calling `expect` conditionally",
910
1051
  recommended: true,
911
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-expect.md"
1052
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-expect.md"
912
1053
  },
913
1054
  messages: {
914
- conditionalExpect: "Avoid calling `expect` conditionally`"
1055
+ conditionalExpect: "Avoid calling `expect` conditionally"
915
1056
  },
916
1057
  type: "problem"
917
1058
  }
@@ -925,13 +1066,11 @@ var no_conditional_in_test_default = createRule({
925
1066
  return;
926
1067
  }
927
1068
  const call = findParent(node, "CallExpression");
928
- if (!call)
929
- return;
1069
+ if (!call) return;
930
1070
  if (isTypeOfFnCall(context, call, ["test", "step"])) {
931
1071
  const testFunction = call.arguments[call.arguments.length - 1];
932
1072
  const functionBody = findParent(node, "BlockStatement");
933
- if (!functionBody)
934
- return;
1073
+ if (!functionBody) return;
935
1074
  let currentParent = functionBody.parent;
936
1075
  while (currentParent && currentParent !== testFunction) {
937
1076
  currentParent = currentParent.parent;
@@ -953,7 +1092,7 @@ var no_conditional_in_test_default = createRule({
953
1092
  category: "Best Practices",
954
1093
  description: "Disallow conditional logic in tests",
955
1094
  recommended: true,
956
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-in-test.md"
1095
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-in-test.md"
957
1096
  },
958
1097
  messages: {
959
1098
  conditionalInTest: "Avoid having conditionals in tests"
@@ -968,10 +1107,9 @@ var no_duplicate_hooks_default = createRule({
968
1107
  create(context) {
969
1108
  const hookContexts = [{}];
970
1109
  return {
971
- CallExpression(node) {
1110
+ "CallExpression"(node) {
972
1111
  const call = parseFnCall(context, node);
973
- if (!call)
974
- return;
1112
+ if (!call) return;
975
1113
  if (call.type === "describe") {
976
1114
  hookContexts.push({});
977
1115
  }
@@ -1002,7 +1140,7 @@ var no_duplicate_hooks_default = createRule({
1002
1140
  category: "Best Practices",
1003
1141
  description: "Disallow duplicate setup and teardown hooks",
1004
1142
  recommended: false,
1005
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-duplicate-hooks.md"
1143
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-duplicate-hooks.md"
1006
1144
  },
1007
1145
  messages: {
1008
1146
  noDuplicateHook: "Duplicate {{ hook }} in describe block"
@@ -1027,19 +1165,9 @@ var no_element_handle_default = createRule({
1027
1165
  {
1028
1166
  fix: (fixer) => {
1029
1167
  const { property } = node.callee;
1030
- const fixes = [
1031
- fixer.replaceTextRange(
1032
- getPropertyRange(property),
1033
- "locator"
1034
- )
1035
- ];
1168
+ const fixes = [fixer.replaceTextRange(getPropertyRange(property), "locator")];
1036
1169
  if (node.parent.type === "AwaitExpression") {
1037
- fixes.push(
1038
- fixer.removeRange([
1039
- node.parent.range[0],
1040
- node.range[0]
1041
- ])
1042
- );
1170
+ fixes.push(fixer.removeRange([node.parent.range[0], node.range[0]]));
1043
1171
  }
1044
1172
  return fixes;
1045
1173
  },
@@ -1056,7 +1184,7 @@ var no_element_handle_default = createRule({
1056
1184
  category: "Possible Errors",
1057
1185
  description: "The use of ElementHandle is discouraged, use Locator instead",
1058
1186
  recommended: true,
1059
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md"
1187
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md"
1060
1188
  },
1061
1189
  hasSuggestions: true,
1062
1190
  messages: {
@@ -1088,7 +1216,7 @@ var no_eval_default = createRule({
1088
1216
  category: "Possible Errors",
1089
1217
  description: "The use of `page.$eval` and `page.$$eval` are discouraged, use `locator.evaluate` or `locator.evaluateAll` instead",
1090
1218
  recommended: true,
1091
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-eval.md"
1219
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-eval.md"
1092
1220
  },
1093
1221
  messages: {
1094
1222
  noEval: "Unexpected use of page.$eval().",
@@ -1108,8 +1236,7 @@ var no_focused_test_default = createRule({
1108
1236
  return;
1109
1237
  }
1110
1238
  const onlyNode = call.members.find((s) => getStringValue(s) === "only");
1111
- if (!onlyNode)
1112
- return;
1239
+ if (!onlyNode) return;
1113
1240
  context.report({
1114
1241
  messageId: "noFocusedTest",
1115
1242
  node: onlyNode,
@@ -1133,7 +1260,7 @@ var no_focused_test_default = createRule({
1133
1260
  category: "Possible Errors",
1134
1261
  description: "Prevent usage of `.only()` focus test annotation",
1135
1262
  recommended: true,
1136
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md"
1263
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md"
1137
1264
  },
1138
1265
  hasSuggestions: true,
1139
1266
  messages: {
@@ -1182,7 +1309,7 @@ var no_force_option_default = createRule({
1182
1309
  category: "Best Practices",
1183
1310
  description: "Prevent usage of `{ force: true }` option.",
1184
1311
  recommended: true,
1185
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-force-option.md"
1312
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-force-option.md"
1186
1313
  },
1187
1314
  messages: {
1188
1315
  noForceOption: "Unexpected use of { force: true } option."
@@ -1207,7 +1334,7 @@ var no_get_by_title_default = createRule({
1207
1334
  category: "Best Practices",
1208
1335
  description: "Disallows the usage of getByTitle()",
1209
1336
  recommended: false,
1210
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-get-by-title.md"
1337
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-get-by-title.md"
1211
1338
  },
1212
1339
  messages: {
1213
1340
  noGetByTitle: "The HTML title attribute is not an accessible name. Prefer getByRole() or getByLabelText() instead."
@@ -1226,8 +1353,7 @@ var no_hooks_default = createRule({
1226
1353
  return {
1227
1354
  CallExpression(node) {
1228
1355
  const call = parseFnCall(context, node);
1229
- if (!call)
1230
- return;
1356
+ if (!call) return;
1231
1357
  if (call.type === "hook" && !options.allow.includes(call.name)) {
1232
1358
  context.report({
1233
1359
  data: { hookName: call.name },
@@ -1243,7 +1369,7 @@ var no_hooks_default = createRule({
1243
1369
  category: "Best Practices",
1244
1370
  description: "Disallow setup and teardown hooks",
1245
1371
  recommended: false,
1246
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-hooks.md"
1372
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-hooks.md"
1247
1373
  },
1248
1374
  messages: {
1249
1375
  unexpectedHook: "Unexpected '{{ hookName }}' hook"
@@ -1269,27 +1395,28 @@ var no_nested_step_default = createRule({
1269
1395
  create(context) {
1270
1396
  const stack = [];
1271
1397
  function pushStepCallback(node) {
1272
- if (node.parent.type !== "CallExpression" || !isTypeOfFnCall(context, node.parent, ["step"])) {
1398
+ const { parent } = node;
1399
+ if (parent?.type !== "CallExpression" || !isTypeOfFnCall(context, parent, ["step"])) {
1273
1400
  return;
1274
1401
  }
1275
1402
  stack.push(0);
1276
1403
  if (stack.length > 1) {
1277
1404
  context.report({
1278
1405
  messageId: "noNestedStep",
1279
- node: node.parent.callee
1406
+ node: parent.callee
1280
1407
  });
1281
1408
  }
1282
1409
  }
1283
1410
  function popStepCallback(node) {
1284
1411
  const { parent } = node;
1285
- if (parent.type === "CallExpression" && isTypeOfFnCall(context, parent, ["step"])) {
1412
+ if (parent?.type === "CallExpression" && isTypeOfFnCall(context, parent, ["step"])) {
1286
1413
  stack.pop();
1287
1414
  }
1288
1415
  }
1289
1416
  return {
1290
- ArrowFunctionExpression: pushStepCallback,
1417
+ "ArrowFunctionExpression": pushStepCallback,
1291
1418
  "ArrowFunctionExpression:exit": popStepCallback,
1292
- FunctionExpression: pushStepCallback,
1419
+ "FunctionExpression": pushStepCallback,
1293
1420
  "FunctionExpression:exit": popStepCallback
1294
1421
  };
1295
1422
  },
@@ -1298,7 +1425,7 @@ var no_nested_step_default = createRule({
1298
1425
  category: "Best Practices",
1299
1426
  description: "Disallow nested `test.step()` methods",
1300
1427
  recommended: true,
1301
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-nested-step.md"
1428
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-nested-step.md"
1302
1429
  },
1303
1430
  messages: {
1304
1431
  noNestedStep: "Do not nest `test.step()` methods."
@@ -1323,11 +1450,9 @@ var no_networkidle_default = createRule({
1323
1450
  create(context) {
1324
1451
  return {
1325
1452
  CallExpression(node) {
1326
- if (node.callee.type !== "MemberExpression")
1327
- return;
1453
+ if (node.callee.type !== "MemberExpression") return;
1328
1454
  const methodName = getStringValue(node.callee.property);
1329
- if (!methods.has(methodName))
1330
- return;
1455
+ if (!methods.has(methodName)) return;
1331
1456
  if (methodName === "waitForLoadState") {
1332
1457
  const arg = node.arguments[0];
1333
1458
  if (arg && isStringLiteral(arg, "networkidle")) {
@@ -1337,8 +1462,7 @@ var no_networkidle_default = createRule({
1337
1462
  }
1338
1463
  if (node.arguments.length >= 2) {
1339
1464
  const [_, arg] = node.arguments;
1340
- if (arg.type !== "ObjectExpression")
1341
- return;
1465
+ if (arg.type !== "ObjectExpression") return;
1342
1466
  const property = arg.properties.filter((p) => p.type === "Property").find((p) => isStringLiteral(p.value, "networkidle"));
1343
1467
  if (property) {
1344
1468
  context.report({ messageId, node: property.value });
@@ -1352,7 +1476,7 @@ var no_networkidle_default = createRule({
1352
1476
  category: "Possible Errors",
1353
1477
  description: "Prevent usage of the networkidle option",
1354
1478
  recommended: true,
1355
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-networkidle.md"
1479
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-networkidle.md"
1356
1480
  },
1357
1481
  messages: {
1358
1482
  noNetworkidle: "Unexpected use of networkidle."
@@ -1367,11 +1491,9 @@ var no_nth_methods_default = createRule({
1367
1491
  create(context) {
1368
1492
  return {
1369
1493
  CallExpression(node) {
1370
- if (node.callee.type !== "MemberExpression")
1371
- return;
1494
+ if (node.callee.type !== "MemberExpression") return;
1372
1495
  const method = getStringValue(node.callee.property);
1373
- if (!methods2.has(method))
1374
- return;
1496
+ if (!methods2.has(method)) return;
1375
1497
  context.report({
1376
1498
  data: { method },
1377
1499
  loc: {
@@ -1388,7 +1510,7 @@ var no_nth_methods_default = createRule({
1388
1510
  category: "Best Practices",
1389
1511
  description: "Disallow usage of nth methods",
1390
1512
  recommended: true,
1391
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-nth-methods.md"
1513
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-nth-methods.md"
1392
1514
  },
1393
1515
  messages: {
1394
1516
  noNthMethod: "Unexpected use of {{method}}()"
@@ -1413,7 +1535,7 @@ var no_page_pause_default = createRule({
1413
1535
  category: "Possible Errors",
1414
1536
  description: "Prevent usage of page.pause()",
1415
1537
  recommended: true,
1416
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md"
1538
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md"
1417
1539
  },
1418
1540
  messages: {
1419
1541
  noPagePause: "Unexpected use of page.pause()."
@@ -1454,7 +1576,7 @@ var no_raw_locators_default = createRule({
1454
1576
  category: "Best Practices",
1455
1577
  description: "Disallows the usage of raw locators",
1456
1578
  recommended: false,
1457
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-raw-locators.md"
1579
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-raw-locators.md"
1458
1580
  },
1459
1581
  messages: {
1460
1582
  noRawLocator: "Usage of raw locator detected. Use methods like .getByRole() or .getByText() instead of raw locators."
@@ -1475,6 +1597,78 @@ var no_raw_locators_default = createRule({
1475
1597
  }
1476
1598
  });
1477
1599
 
1600
+ // src/rules/no-restricted-locators.ts
1601
+ var no_restricted_locators_default = createRule({
1602
+ create(context) {
1603
+ const options = context.options?.[0] ?? [];
1604
+ const restrictions = options.map((option) => {
1605
+ if (typeof option === "string") {
1606
+ return { message: null, type: option };
1607
+ }
1608
+ return {
1609
+ message: option.message ?? null,
1610
+ type: option.type
1611
+ };
1612
+ });
1613
+ const restrictionMap = /* @__PURE__ */ new Map();
1614
+ for (const restriction of restrictions) {
1615
+ restrictionMap.set(restriction.type, restriction.message);
1616
+ }
1617
+ return {
1618
+ CallExpression(node) {
1619
+ if (node.callee.type !== "MemberExpression") {
1620
+ return;
1621
+ }
1622
+ for (const [restrictedType, message] of restrictionMap.entries()) {
1623
+ if (isPageMethod(node, restrictedType)) {
1624
+ context.report({
1625
+ data: {
1626
+ message: message ?? "",
1627
+ method: restrictedType
1628
+ },
1629
+ messageId: message ? "restrictedWithMessage" : "restricted",
1630
+ node
1631
+ });
1632
+ break;
1633
+ }
1634
+ }
1635
+ }
1636
+ };
1637
+ },
1638
+ meta: {
1639
+ docs: {
1640
+ category: "Best Practices",
1641
+ description: "Disallows the usage of specific locator methods",
1642
+ recommended: false,
1643
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-locators.md"
1644
+ },
1645
+ messages: {
1646
+ restricted: "Usage of `{{method}}` is disallowed",
1647
+ restrictedWithMessage: "{{message}}"
1648
+ },
1649
+ schema: [
1650
+ {
1651
+ items: {
1652
+ oneOf: [
1653
+ { type: "string" },
1654
+ {
1655
+ additionalProperties: false,
1656
+ properties: {
1657
+ message: { type: "string" },
1658
+ type: { type: "string" }
1659
+ },
1660
+ required: ["type"],
1661
+ type: "object"
1662
+ }
1663
+ ]
1664
+ },
1665
+ type: "array"
1666
+ }
1667
+ ],
1668
+ type: "suggestion"
1669
+ }
1670
+ });
1671
+
1478
1672
  // src/rules/no-restricted-matchers.ts
1479
1673
  var no_restricted_matchers_default = createRule({
1480
1674
  create(context) {
@@ -1482,8 +1676,7 @@ var no_restricted_matchers_default = createRule({
1482
1676
  return {
1483
1677
  CallExpression(node) {
1484
1678
  const call = parseFnCall(context, node);
1485
- if (call?.type !== "expect")
1486
- return;
1679
+ if (call?.type !== "expect") return;
1487
1680
  Object.entries(restrictedChains).map(([restriction, message]) => {
1488
1681
  const chain = call.members;
1489
1682
  const restrictionLinks = restriction.split(".").length;
@@ -1516,7 +1709,7 @@ var no_restricted_matchers_default = createRule({
1516
1709
  category: "Best Practices",
1517
1710
  description: "Disallow specific matchers & modifiers",
1518
1711
  recommended: false,
1519
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md"
1712
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md"
1520
1713
  },
1521
1714
  messages: {
1522
1715
  restricted: "Use of `{{restriction}}` is disallowed",
@@ -1546,8 +1739,7 @@ var no_skipped_test_default = createRule({
1546
1739
  return;
1547
1740
  }
1548
1741
  const skipNode = call.members.find((s) => getStringValue(s) === "skip");
1549
- if (!skipNode)
1550
- return;
1742
+ if (!skipNode) return;
1551
1743
  const isStandalone = call.type === "config";
1552
1744
  if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement")) {
1553
1745
  return;
@@ -1575,7 +1767,7 @@ var no_skipped_test_default = createRule({
1575
1767
  category: "Best Practices",
1576
1768
  description: "Prevent usage of the `.skip()` skip test annotation.",
1577
1769
  recommended: true,
1578
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md"
1770
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md"
1579
1771
  },
1580
1772
  hasSuggestions: true,
1581
1773
  messages: {
@@ -1610,8 +1802,7 @@ var no_slowed_test_default = createRule({
1610
1802
  return;
1611
1803
  }
1612
1804
  const slowNode = call.members.find((s) => getStringValue(s) === "slow");
1613
- if (!slowNode)
1614
- return;
1805
+ if (!slowNode) return;
1615
1806
  const isStandalone = call.type === "config";
1616
1807
  if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement")) {
1617
1808
  return;
@@ -1639,7 +1830,7 @@ var no_slowed_test_default = createRule({
1639
1830
  category: "Best Practices",
1640
1831
  description: "Prevent usage of the `.slow()` slow test annotation.",
1641
1832
  recommended: false,
1642
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md"
1833
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md"
1643
1834
  },
1644
1835
  hasSuggestions: true,
1645
1836
  messages: {
@@ -1664,10 +1855,10 @@ var no_slowed_test_default = createRule({
1664
1855
 
1665
1856
  // src/rules/no-standalone-expect.ts
1666
1857
  var getBlockType = (context, statement) => {
1667
- const func = getParent(statement);
1858
+ const func = statement.parent;
1668
1859
  if (!func) {
1669
1860
  throw new Error(
1670
- `Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/playwright-community/eslint-plugin-playwright`
1861
+ `Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/mskelton/eslint-plugin-playwright`
1671
1862
  );
1672
1863
  }
1673
1864
  if (func.type === "FunctionDeclaration") {
@@ -1688,7 +1879,7 @@ var no_standalone_expect_default = createRule({
1688
1879
  create(context) {
1689
1880
  const callStack = [];
1690
1881
  return {
1691
- ArrowFunctionExpression(node) {
1882
+ "ArrowFunctionExpression"(node) {
1692
1883
  if (node.parent?.type !== "CallExpression") {
1693
1884
  callStack.push("arrow");
1694
1885
  }
@@ -1698,7 +1889,7 @@ var no_standalone_expect_default = createRule({
1698
1889
  callStack.pop();
1699
1890
  }
1700
1891
  },
1701
- BlockStatement(statement) {
1892
+ "BlockStatement"(statement) {
1702
1893
  const blockType = getBlockType(context, statement);
1703
1894
  if (blockType) {
1704
1895
  callStack.push(blockType);
@@ -1709,10 +1900,10 @@ var no_standalone_expect_default = createRule({
1709
1900
  callStack.pop();
1710
1901
  }
1711
1902
  },
1712
- CallExpression(node) {
1903
+ "CallExpression"(node) {
1713
1904
  const call = parseFnCall(context, node);
1714
1905
  if (call?.type === "expect") {
1715
- if (getParent(call.head.node)?.type === "MemberExpression" && call.members.length === 1) {
1906
+ if (call.head.node.parent?.type === "MemberExpression" && call.members.length === 1) {
1716
1907
  return;
1717
1908
  }
1718
1909
  const parent = callStack.at(-1);
@@ -1747,7 +1938,7 @@ var no_standalone_expect_default = createRule({
1747
1938
  category: "Best Practices",
1748
1939
  description: "Disallow using `expect` outside of `test` blocks",
1749
1940
  recommended: false,
1750
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md"
1941
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md"
1751
1942
  },
1752
1943
  fixable: "code",
1753
1944
  messages: {
@@ -1766,22 +1957,16 @@ var truthy = Boolean;
1766
1957
 
1767
1958
  // src/rules/no-unsafe-references.ts
1768
1959
  function collectVariables(scope) {
1769
- if (!scope || scope.type === "global")
1770
- return [];
1771
- return [
1772
- ...collectVariables(scope.upper),
1773
- ...scope.variables.map((ref) => ref.name)
1774
- ];
1960
+ if (!scope || scope.type === "global") return [];
1961
+ return [...collectVariables(scope.upper), ...scope.variables.map((ref) => ref.name)];
1775
1962
  }
1776
1963
  function addArgument(fixer, node, refs) {
1777
- if (!node.arguments.length)
1778
- return;
1964
+ if (!node.arguments.length) return;
1779
1965
  if (node.arguments.length === 1) {
1780
1966
  return fixer.insertTextAfter(node.arguments[0], `, [${refs}]`);
1781
1967
  }
1782
1968
  const arg = node.arguments.at(-1);
1783
- if (!arg)
1784
- return;
1969
+ if (!arg) return;
1785
1970
  if (arg.type !== "ArrayExpression") {
1786
1971
  return fixer.replaceText(arg, `[${getStringValue(arg)}, ${refs}]`);
1787
1972
  }
@@ -1807,15 +1992,13 @@ var no_unsafe_references_default = createRule({
1807
1992
  create(context) {
1808
1993
  return {
1809
1994
  CallExpression(node) {
1810
- if (!isPageMethod(node, "evaluate") && !isPageMethod(node, "addInitScript"))
1811
- return;
1995
+ if (!isPageMethod(node, "evaluate") && !isPageMethod(node, "addInitScript")) return;
1812
1996
  const [fn] = node.arguments;
1813
- if (!fn || !isFunction(fn))
1814
- return;
1997
+ if (!fn || !isFunction(fn)) return;
1815
1998
  const { through, upper } = context.sourceCode.getScope(fn.body);
1816
1999
  const allRefs = new Set(collectVariables(upper));
1817
2000
  through.filter((ref) => {
1818
- const parent = getParent(ref.identifier);
2001
+ const parent = ref.identifier.parent;
1819
2002
  return parent?.type !== "TSTypeReference";
1820
2003
  }).filter((ref) => allRefs.has(ref.identifier.name)).forEach((ref, i, arr) => {
1821
2004
  const methodName = isPageMethod(node, "evaluate") ? "evaluate" : "addInitScript";
@@ -1832,10 +2015,9 @@ var no_unsafe_references_default = createRule({
1832
2015
  ...descriptor,
1833
2016
  fix(fixer) {
1834
2017
  const refs = arr.map((ref2) => ref2.identifier.name).join(", ");
1835
- return [
1836
- addArgument(fixer, node, refs),
1837
- addParam(context, fixer, fn, refs)
1838
- ].filter(truthy);
2018
+ return [addArgument(fixer, node, refs), addParam(context, fixer, fn, refs)].filter(
2019
+ truthy
2020
+ );
1839
2021
  }
1840
2022
  });
1841
2023
  });
@@ -1847,7 +2029,7 @@ var no_unsafe_references_default = createRule({
1847
2029
  category: "Possible Errors",
1848
2030
  description: "Prevent unsafe variable references in page.evaluate() and page.addInitScript()",
1849
2031
  recommended: true,
1850
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md"
2032
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md"
1851
2033
  },
1852
2034
  fixable: "code",
1853
2035
  messages: {
@@ -1877,7 +2059,7 @@ var no_unused_locators_default = createRule({
1877
2059
  category: "Possible Errors",
1878
2060
  description: `Disallow usage of page locators that are not used`,
1879
2061
  recommended: true,
1880
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md"
2062
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md"
1881
2063
  },
1882
2064
  messages: {
1883
2065
  noUnusedLocator: "Unused locator"
@@ -1947,8 +2129,7 @@ var expectMatchers = /* @__PURE__ */ new Set([
1947
2129
  "toThrowError"
1948
2130
  ]);
1949
2131
  function isSupportedMethod(node) {
1950
- if (node.callee.type !== "MemberExpression")
1951
- return false;
2132
+ if (node.callee.type !== "MemberExpression") return false;
1952
2133
  const name = getStringValue(node.callee.property);
1953
2134
  return locatorMethods.has(name) || pageMethods.has(name) && isPageMethod(node, name);
1954
2135
  }
@@ -1975,9 +2156,7 @@ var no_useless_await_default = createRule({
1975
2156
  return fix(node.parent);
1976
2157
  }
1977
2158
  const call = parseFnCall(context, node);
1978
- if (call?.type === "expect" && !call.modifiers.some(
1979
- (modifier) => isIdentifier(modifier, /^(resolves|rejects)$/)
1980
- ) && !call.members.some((member) => isIdentifier(member, "poll")) && expectMatchers.has(call.matcherName)) {
2159
+ if (call?.type === "expect" && !call.modifiers.some((modifier) => isIdentifier(modifier, /^(resolves|rejects)$/)) && !call.members.some((member) => isIdentifier(member, "poll")) && expectMatchers.has(call.matcherName)) {
1981
2160
  return fix(node.parent);
1982
2161
  }
1983
2162
  }
@@ -1988,7 +2167,7 @@ var no_useless_await_default = createRule({
1988
2167
  category: "Possible Errors",
1989
2168
  description: "Disallow unnecessary awaits for Playwright methods",
1990
2169
  recommended: true,
1991
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-await.md"
2170
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-useless-await.md"
1992
2171
  },
1993
2172
  fixable: "code",
1994
2173
  messages: {
@@ -2002,15 +2181,11 @@ var no_useless_await_default = createRule({
2002
2181
  var getRangeOffset = (node) => node.type === "Identifier" ? 0 : 1;
2003
2182
  function replaceAccessorFixer(fixer, node, text) {
2004
2183
  const [start, end] = node.range;
2005
- return fixer.replaceTextRange(
2006
- [start + getRangeOffset(node), end - getRangeOffset(node)],
2007
- text
2008
- );
2184
+ return fixer.replaceTextRange([start + getRangeOffset(node), end - getRangeOffset(node)], text);
2009
2185
  }
2010
2186
  function removePropertyFixer(fixer, property) {
2011
- const parent = getParent(property);
2012
- if (parent?.type !== "ObjectExpression")
2013
- return;
2187
+ const parent = property.parent;
2188
+ if (parent?.type !== "ObjectExpression") return;
2014
2189
  if (parent.properties.length === 1) {
2015
2190
  return fixer.remove(parent);
2016
2191
  }
@@ -2034,8 +2209,7 @@ var matcherConfig = {
2034
2209
  };
2035
2210
  function getOptions(call, name) {
2036
2211
  const [arg] = call.matcherArgs;
2037
- if (arg?.type !== "ObjectExpression")
2038
- return;
2212
+ if (arg?.type !== "ObjectExpression") return;
2039
2213
  const property = arg.properties.find(
2040
2214
  (p) => p.type === "Property" && getStringValue(p.key) === name && isBooleanLiteral(p.value)
2041
2215
  );
@@ -2050,19 +2224,13 @@ var no_useless_not_default = createRule({
2050
2224
  return {
2051
2225
  CallExpression(node) {
2052
2226
  const call = parseFnCall(context, node);
2053
- if (call?.type !== "expect")
2054
- return;
2227
+ if (call?.type !== "expect") return;
2055
2228
  const config = matcherConfig[call.matcherName];
2056
- if (!config)
2057
- return;
2229
+ if (!config) return;
2058
2230
  const options = config.argName ? getOptions(call, config.argName) : void 0;
2059
- if (options?.arg && options.value === void 0)
2060
- return;
2061
- const notModifier = call.modifiers.find(
2062
- (mod) => getStringValue(mod) === "not"
2063
- );
2064
- if (!notModifier && !options?.property)
2065
- return;
2231
+ if (options?.arg && options.value === void 0) return;
2232
+ const notModifier = call.modifiers.find((mod) => getStringValue(mod) === "not");
2233
+ if (!notModifier && !options?.property) return;
2066
2234
  const isInverted = !!notModifier !== (options?.value === false);
2067
2235
  const newMatcherName = isInverted ? config.inverse : call.matcherName;
2068
2236
  context.report({
@@ -2095,7 +2263,7 @@ var no_useless_not_default = createRule({
2095
2263
  category: "Best Practices",
2096
2264
  description: `Disallow usage of 'not' matchers when a more specific matcher exists`,
2097
2265
  recommended: true,
2098
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md"
2266
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md"
2099
2267
  },
2100
2268
  fixable: "code",
2101
2269
  messages: {
@@ -2134,7 +2302,7 @@ var no_wait_for_navigation_default = createRule({
2134
2302
  category: "Possible Errors",
2135
2303
  description: "Prevent usage of page.waitForNavigation()",
2136
2304
  recommended: true,
2137
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-navigation.md"
2305
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-navigation.md"
2138
2306
  },
2139
2307
  hasSuggestions: true,
2140
2308
  messages: {
@@ -2172,7 +2340,7 @@ var no_wait_for_selector_default = createRule({
2172
2340
  category: "Best Practices",
2173
2341
  description: "Prevent usage of page.waitForSelector()",
2174
2342
  recommended: true,
2175
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-selector.md"
2343
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-selector.md"
2176
2344
  },
2177
2345
  hasSuggestions: true,
2178
2346
  messages: {
@@ -2210,7 +2378,7 @@ var no_wait_for_timeout_default = createRule({
2210
2378
  category: "Best Practices",
2211
2379
  description: "Prevent usage of page.waitForTimeout()",
2212
2380
  recommended: true,
2213
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md"
2381
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md"
2214
2382
  },
2215
2383
  hasSuggestions: true,
2216
2384
  messages: {
@@ -2249,20 +2417,16 @@ var prefer_comparison_matcher_default = createRule({
2249
2417
  return {
2250
2418
  CallExpression(node) {
2251
2419
  const call = parseFnCall(context, node);
2252
- if (call?.type !== "expect" || call.matcherArgs.length === 0)
2253
- return;
2254
- const expect = getParent(call.head.node);
2255
- if (expect?.type !== "CallExpression")
2256
- return;
2420
+ if (call?.type !== "expect" || call.matcherArgs.length === 0) return;
2421
+ const expect = call.head.node.parent;
2422
+ if (expect?.type !== "CallExpression") return;
2257
2423
  const [comparison] = expect.arguments;
2258
2424
  const expectCallEnd = expect.range[1];
2259
2425
  const [matcherArg] = call.matcherArgs;
2260
2426
  if (comparison?.type !== "BinaryExpression" || isComparingToString(comparison) || !equalityMatchers.has(call.matcherName) || !isBooleanLiteral(matcherArg)) {
2261
2427
  return;
2262
2428
  }
2263
- const hasNot = call.modifiers.some(
2264
- (node2) => getStringValue(node2) === "not"
2265
- );
2429
+ const hasNot = call.modifiers.some((node2) => getStringValue(node2) === "not");
2266
2430
  const preferredMatcher = determineMatcher(
2267
2431
  comparison.operator,
2268
2432
  getRawValue(matcherArg) === hasNot.toString()
@@ -2277,20 +2441,14 @@ var prefer_comparison_matcher_default = createRule({
2277
2441
  const modifierText = modifier && getStringValue(modifier) !== "not" ? `.${getStringValue(modifier)}` : "";
2278
2442
  return [
2279
2443
  // Replace the comparison argument with the left-hand side of the comparison
2280
- fixer.replaceText(
2281
- comparison,
2282
- context.sourceCode.getText(comparison.left)
2283
- ),
2444
+ fixer.replaceText(comparison, context.sourceCode.getText(comparison.left)),
2284
2445
  // Replace the current matcher & modifier with the preferred matcher
2285
2446
  fixer.replaceTextRange(
2286
- [expectCallEnd, getParent(call.matcher).range[1]],
2447
+ [expectCallEnd, call.matcher.parent.range[1]],
2287
2448
  `${modifierText}.${preferredMatcher}`
2288
2449
  ),
2289
2450
  // Replace the matcher argument with the right-hand side of the comparison
2290
- fixer.replaceText(
2291
- matcherArg,
2292
- context.sourceCode.getText(comparison.right)
2293
- )
2451
+ fixer.replaceText(matcherArg, context.sourceCode.getText(comparison.right))
2294
2452
  ];
2295
2453
  },
2296
2454
  messageId: "useToBeComparison",
@@ -2304,7 +2462,7 @@ var prefer_comparison_matcher_default = createRule({
2304
2462
  category: "Best Practices",
2305
2463
  description: "Suggest using the built-in comparison matchers",
2306
2464
  recommended: false,
2307
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-comparison-matcher.md"
2465
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-comparison-matcher.md"
2308
2466
  },
2309
2467
  fixable: "code",
2310
2468
  messages: {
@@ -2320,11 +2478,9 @@ var prefer_equality_matcher_default = createRule({
2320
2478
  return {
2321
2479
  CallExpression(node) {
2322
2480
  const call = parseFnCall(context, node);
2323
- if (call?.type !== "expect" || call.matcherArgs.length === 0)
2324
- return;
2325
- const expect = getParent(call.head.node);
2326
- if (expect?.type !== "CallExpression")
2327
- return;
2481
+ if (call?.type !== "expect" || call.matcherArgs.length === 0) return;
2482
+ const expect = call.head.node.parent;
2483
+ if (expect?.type !== "CallExpression") return;
2328
2484
  const [comparison] = expect.arguments;
2329
2485
  const expectCallEnd = expect.range[1];
2330
2486
  const [matcherArg] = call.matcherArgs;
@@ -2333,9 +2489,7 @@ var prefer_equality_matcher_default = createRule({
2333
2489
  }
2334
2490
  const matcherValue = getRawValue(matcherArg) === "true";
2335
2491
  const [modifier] = call.modifiers;
2336
- const hasNot = call.modifiers.some(
2337
- (node2) => getStringValue(node2) === "not"
2338
- );
2492
+ const hasNot = call.modifiers.some((node2) => getStringValue(node2) === "not");
2339
2493
  const addNotModifier = (comparison.operator === "!==" ? !matcherValue : matcherValue) === hasNot;
2340
2494
  context.report({
2341
2495
  messageId: "useEqualityMatcher",
@@ -2349,20 +2503,14 @@ var prefer_equality_matcher_default = createRule({
2349
2503
  }
2350
2504
  return [
2351
2505
  // replace the comparison argument with the left-hand side of the comparison
2352
- fixer.replaceText(
2353
- comparison,
2354
- context.sourceCode.getText(comparison.left)
2355
- ),
2506
+ fixer.replaceText(comparison, context.sourceCode.getText(comparison.left)),
2356
2507
  // replace the current matcher & modifier with the preferred matcher
2357
2508
  fixer.replaceTextRange(
2358
- [expectCallEnd, getParent(call.matcher).range[1]],
2509
+ [expectCallEnd, call.matcher.parent.range[1]],
2359
2510
  `${modifierText}.${equalityMatcher}`
2360
2511
  ),
2361
2512
  // replace the matcher argument with the right-hand side of the comparison
2362
- fixer.replaceText(
2363
- matcherArg,
2364
- context.sourceCode.getText(comparison.right)
2365
- )
2513
+ fixer.replaceText(matcherArg, context.sourceCode.getText(comparison.right))
2366
2514
  ];
2367
2515
  },
2368
2516
  messageId: "suggestEqualityMatcher"
@@ -2376,7 +2524,7 @@ var prefer_equality_matcher_default = createRule({
2376
2524
  category: "Best Practices",
2377
2525
  description: "Suggest using the built-in equality matchers",
2378
2526
  recommended: false,
2379
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md"
2527
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md"
2380
2528
  },
2381
2529
  hasSuggestions: true,
2382
2530
  messages: {
@@ -2394,9 +2542,8 @@ var prefer_hooks_in_order_default = createRule({
2394
2542
  let previousHookIndex = -1;
2395
2543
  let inHook = false;
2396
2544
  return {
2397
- CallExpression(node) {
2398
- if (inHook)
2399
- return;
2545
+ "CallExpression"(node) {
2546
+ if (inHook) return;
2400
2547
  const call = parseFnCall(context, node);
2401
2548
  if (call?.type !== "hook") {
2402
2549
  previousHookIndex = -1;
@@ -2423,8 +2570,7 @@ var prefer_hooks_in_order_default = createRule({
2423
2570
  inHook = false;
2424
2571
  return;
2425
2572
  }
2426
- if (inHook)
2427
- return;
2573
+ if (inHook) return;
2428
2574
  previousHookIndex = -1;
2429
2575
  }
2430
2576
  };
@@ -2434,7 +2580,7 @@ var prefer_hooks_in_order_default = createRule({
2434
2580
  category: "Best Practices",
2435
2581
  description: "Prefer having hooks in a consistent order",
2436
2582
  recommended: false,
2437
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md"
2583
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md"
2438
2584
  },
2439
2585
  messages: {
2440
2586
  reorderHooks: "`{{ currentHook }}` hooks should be before any `{{ previousHook }}` hooks"
@@ -2448,7 +2594,7 @@ var prefer_hooks_on_top_default = createRule({
2448
2594
  create(context) {
2449
2595
  const stack = [false];
2450
2596
  return {
2451
- CallExpression(node) {
2597
+ "CallExpression"(node) {
2452
2598
  if (isTypeOfFnCall(context, node, ["test"])) {
2453
2599
  stack[stack.length - 1] = true;
2454
2600
  }
@@ -2467,7 +2613,7 @@ var prefer_hooks_on_top_default = createRule({
2467
2613
  category: "Best Practices",
2468
2614
  description: "Suggest having hooks before any test cases",
2469
2615
  recommended: false,
2470
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md"
2616
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md"
2471
2617
  },
2472
2618
  messages: {
2473
2619
  noHookOnTop: "Hooks should come before test cases"
@@ -2503,8 +2649,7 @@ var pageMethods2 = /* @__PURE__ */ new Set([
2503
2649
  "uncheck"
2504
2650
  ]);
2505
2651
  function isSupportedMethod2(node) {
2506
- if (node.callee.type !== "MemberExpression")
2507
- return false;
2652
+ if (node.callee.type !== "MemberExpression") return false;
2508
2653
  const name = getStringValue(node.callee.property);
2509
2654
  return pageMethods2.has(name) && isPageMethod(node, name);
2510
2655
  }
@@ -2512,8 +2657,7 @@ var prefer_locator_default = createRule({
2512
2657
  create(context) {
2513
2658
  return {
2514
2659
  CallExpression(node) {
2515
- if (!isSupportedMethod2(node))
2516
- return;
2660
+ if (!isSupportedMethod2(node)) return;
2517
2661
  context.report({
2518
2662
  messageId: "preferLocator",
2519
2663
  node
@@ -2526,7 +2670,7 @@ var prefer_locator_default = createRule({
2526
2670
  category: "Best Practices",
2527
2671
  description: "Suggest locators over page methods",
2528
2672
  recommended: false,
2529
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md"
2673
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md"
2530
2674
  },
2531
2675
  messages: {
2532
2676
  preferLocator: "Prefer locator methods instead of page methods"
@@ -2547,7 +2691,7 @@ var prefer_lowercase_title_default = createRule({
2547
2691
  };
2548
2692
  let describeCount = 0;
2549
2693
  return {
2550
- CallExpression(node) {
2694
+ "CallExpression"(node) {
2551
2695
  const call = parseFnCall(context, node);
2552
2696
  if (call?.type !== "describe" && call?.type !== "test") {
2553
2697
  return;
@@ -2574,10 +2718,7 @@ var prefer_lowercase_title_default = createRule({
2574
2718
  context.report({
2575
2719
  data: { method },
2576
2720
  fix(fixer) {
2577
- const rangeIgnoringQuotes = [
2578
- title.range[0] + 1,
2579
- title.range[1] - 1
2580
- ];
2721
+ const rangeIgnoringQuotes = [title.range[0] + 1, title.range[1] - 1];
2581
2722
  const newDescription = description.substring(0, 1).toLowerCase() + description.substring(1);
2582
2723
  return fixer.replaceTextRange(rangeIgnoringQuotes, newDescription);
2583
2724
  },
@@ -2597,7 +2738,7 @@ var prefer_lowercase_title_default = createRule({
2597
2738
  category: "Best Practices",
2598
2739
  description: "Enforce lowercase test names",
2599
2740
  recommended: false,
2600
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md"
2741
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md"
2601
2742
  },
2602
2743
  fixable: "code",
2603
2744
  messages: {
@@ -2632,9 +2773,7 @@ var prefer_lowercase_title_default = createRule({
2632
2773
  });
2633
2774
 
2634
2775
  // src/rules/prefer-native-locators.ts
2635
- var compilePatterns = ({
2636
- testIdAttribute
2637
- }) => {
2776
+ var compilePatterns = ({ testIdAttribute }) => {
2638
2777
  const patterns = [
2639
2778
  {
2640
2779
  attribute: "aria-label",
@@ -2681,11 +2820,9 @@ var prefer_native_locators_default = createRule({
2681
2820
  const patterns = compilePatterns({ testIdAttribute });
2682
2821
  return {
2683
2822
  CallExpression(node) {
2684
- if (node.callee.type !== "MemberExpression")
2685
- return;
2823
+ if (node.callee.type !== "MemberExpression") return;
2686
2824
  const query = getStringValue(node.arguments[0]);
2687
- if (!isPageMethod(node, "locator"))
2688
- return;
2825
+ if (!isPageMethod(node, "locator")) return;
2689
2826
  for (const pattern of patterns) {
2690
2827
  const match = query.match(pattern.pattern);
2691
2828
  if (match) {
@@ -2711,7 +2848,7 @@ var prefer_native_locators_default = createRule({
2711
2848
  category: "Best Practices",
2712
2849
  description: "Prefer native locator functions",
2713
2850
  recommended: false,
2714
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md"
2851
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md"
2715
2852
  },
2716
2853
  fixable: "code",
2717
2854
  messages: {
@@ -2744,8 +2881,7 @@ var prefer_strict_equal_default = createRule({
2744
2881
  return {
2745
2882
  CallExpression(node) {
2746
2883
  const call = parseFnCall(context, node);
2747
- if (call?.type !== "expect")
2748
- return;
2884
+ if (call?.type !== "expect") return;
2749
2885
  if (call.matcherName === "toEqual") {
2750
2886
  context.report({
2751
2887
  messageId: "useToStrictEqual",
@@ -2753,11 +2889,7 @@ var prefer_strict_equal_default = createRule({
2753
2889
  suggest: [
2754
2890
  {
2755
2891
  fix: (fixer) => {
2756
- return replaceAccessorFixer(
2757
- fixer,
2758
- call.matcher,
2759
- "toStrictEqual"
2760
- );
2892
+ return replaceAccessorFixer(fixer, call.matcher, "toStrictEqual");
2761
2893
  },
2762
2894
  messageId: "suggestReplaceWithStrictEqual"
2763
2895
  }
@@ -2772,7 +2904,7 @@ var prefer_strict_equal_default = createRule({
2772
2904
  category: "Best Practices",
2773
2905
  description: "Suggest using `toStrictEqual()`",
2774
2906
  recommended: false,
2775
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md"
2907
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md"
2776
2908
  },
2777
2909
  fixable: "code",
2778
2910
  hasSuggestions: true,
@@ -2799,9 +2931,7 @@ function shouldUseToBe(call) {
2799
2931
  function reportPreferToBe(context, call, whatToBe, notModifier) {
2800
2932
  context.report({
2801
2933
  fix(fixer) {
2802
- const fixes = [
2803
- replaceAccessorFixer(fixer, call.matcher, `toBe${whatToBe}`)
2804
- ];
2934
+ const fixes = [replaceAccessorFixer(fixer, call.matcher, `toBe${whatToBe}`)];
2805
2935
  if (call.matcherArgs?.length && whatToBe !== "") {
2806
2936
  fixes.push(fixer.remove(call.matcherArgs[0]));
2807
2937
  }
@@ -2820,12 +2950,9 @@ var prefer_to_be_default = createRule({
2820
2950
  return {
2821
2951
  CallExpression(node) {
2822
2952
  const call = parseFnCall(context, node);
2823
- if (call?.type !== "expect")
2824
- return;
2953
+ if (call?.type !== "expect") return;
2825
2954
  const notMatchers = ["toBeUndefined", "toBeDefined"];
2826
- const notModifier = call.modifiers.find(
2827
- (node2) => getStringValue(node2) === "not"
2828
- );
2955
+ const notModifier = call.modifiers.find((node2) => getStringValue(node2) === "not");
2829
2956
  if (notModifier && notMatchers.includes(call.matcherName)) {
2830
2957
  return reportPreferToBe(
2831
2958
  context,
@@ -2859,7 +2986,7 @@ var prefer_to_be_default = createRule({
2859
2986
  category: "Best Practices",
2860
2987
  description: "Suggest using `toBe()` for primitive literals",
2861
2988
  recommended: false,
2862
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md"
2989
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md"
2863
2990
  },
2864
2991
  fixable: "code",
2865
2992
  messages: {
@@ -2881,20 +3008,16 @@ var prefer_to_contain_default = createRule({
2881
3008
  return {
2882
3009
  CallExpression(node) {
2883
3010
  const call = parseFnCall(context, node);
2884
- if (call?.type !== "expect" || call.matcherArgs.length === 0)
2885
- return;
2886
- const expect = getParent(call.head.node);
2887
- if (expect?.type !== "CallExpression")
2888
- return;
3011
+ if (call?.type !== "expect" || call.matcherArgs.length === 0) return;
3012
+ const expect = call.head.node.parent;
3013
+ if (expect?.type !== "CallExpression") return;
2889
3014
  const [includesCall] = expect.arguments;
2890
3015
  const { matcher } = call;
2891
3016
  const [matcherArg] = call.matcherArgs;
2892
3017
  if (!includesCall || matcherArg.type === "SpreadElement" || !equalityMatchers.has(getStringValue(matcher)) || !isBooleanLiteral(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
2893
3018
  return;
2894
3019
  }
2895
- const notModifier = call.modifiers.find(
2896
- (node2) => getStringValue(node2) === "not"
2897
- );
3020
+ const notModifier = call.modifiers.find((node2) => getStringValue(node2) === "not");
2898
3021
  context.report({
2899
3022
  fix(fixer) {
2900
3023
  const addNotModifier = matcherArg.type === "Literal" && matcherArg.value === !!notModifier;
@@ -2905,10 +3028,7 @@ var prefer_to_contain_default = createRule({
2905
3028
  includesCall.range[1]
2906
3029
  ]),
2907
3030
  // replace the current matcher with "toContain", adding "not" if needed
2908
- fixer.replaceText(
2909
- matcher,
2910
- addNotModifier ? "not.toContain" : "toContain"
2911
- ),
3031
+ fixer.replaceText(matcher, addNotModifier ? "not.toContain" : "toContain"),
2912
3032
  // replace the matcher argument with the value from the "includes"
2913
3033
  fixer.replaceText(
2914
3034
  call.matcherArgs[0],
@@ -2916,12 +3036,7 @@ var prefer_to_contain_default = createRule({
2916
3036
  )
2917
3037
  ];
2918
3038
  if (notModifier) {
2919
- fixes.push(
2920
- fixer.removeRange([
2921
- notModifier.range[0],
2922
- notModifier.range[1] + 1
2923
- ])
2924
- );
3039
+ fixes.push(fixer.removeRange([notModifier.range[0], notModifier.range[1] + 1]));
2925
3040
  }
2926
3041
  return fixes;
2927
3042
  },
@@ -2936,7 +3051,7 @@ var prefer_to_contain_default = createRule({
2936
3051
  category: "Best Practices",
2937
3052
  description: "Suggest using toContain()",
2938
3053
  recommended: false,
2939
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-contain.md"
3054
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-contain.md"
2940
3055
  },
2941
3056
  fixable: "code",
2942
3057
  messages: {
@@ -2966,15 +3081,9 @@ var prefer_to_have_count_default = createRule({
2966
3081
  fix(fixer) {
2967
3082
  return [
2968
3083
  // remove the "await" expression
2969
- fixer.removeRange([
2970
- argument.range[0],
2971
- argument.range[0] + "await".length + 1
2972
- ]),
3084
+ fixer.removeRange([argument.range[0], argument.range[0] + "await".length + 1]),
2973
3085
  // remove the "count()" method accessor
2974
- fixer.removeRange([
2975
- callee.property.range[0] - 1,
2976
- argument.argument.range[1]
2977
- ]),
3086
+ fixer.removeRange([callee.property.range[0] - 1, argument.argument.range[1]]),
2978
3087
  // replace the current matcher with "toHaveCount"
2979
3088
  replaceAccessorFixer(fixer, call.matcher, "toHaveCount"),
2980
3089
  // insert "await" to before "expect()"
@@ -2992,7 +3101,7 @@ var prefer_to_have_count_default = createRule({
2992
3101
  category: "Best Practices",
2993
3102
  description: "Suggest using `toHaveCount()`",
2994
3103
  recommended: false,
2995
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-count.md"
3104
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-count.md"
2996
3105
  },
2997
3106
  fixable: "code",
2998
3107
  messages: {
@@ -3020,10 +3129,7 @@ var prefer_to_have_length_default = createRule({
3020
3129
  fix(fixer) {
3021
3130
  return [
3022
3131
  // remove the "length" property accessor
3023
- fixer.removeRange([
3024
- argument.property.range[0] - 1,
3025
- argument.range[1]
3026
- ]),
3132
+ fixer.removeRange([argument.property.range[0] - 1, argument.range[1]]),
3027
3133
  // replace the current matcher with "toHaveLength"
3028
3134
  replaceAccessorFixer(fixer, call.matcher, "toHaveLength")
3029
3135
  ];
@@ -3039,7 +3145,7 @@ var prefer_to_have_length_default = createRule({
3039
3145
  category: "Best Practices",
3040
3146
  description: "Suggest using `toHaveLength()`",
3041
3147
  recommended: false,
3042
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md"
3148
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md"
3043
3149
  },
3044
3150
  fixable: "code",
3045
3151
  messages: {
@@ -3052,6 +3158,16 @@ var prefer_to_have_length_default = createRule({
3052
3158
 
3053
3159
  // src/rules/prefer-web-first-assertions.ts
3054
3160
  var methods3 = {
3161
+ allInnerTexts: {
3162
+ fixable: false,
3163
+ matcher: "toHaveText",
3164
+ type: "string"
3165
+ },
3166
+ allTextContents: {
3167
+ fixable: false,
3168
+ matcher: "toHaveText",
3169
+ type: "string"
3170
+ },
3055
3171
  getAttribute: {
3056
3172
  matcher: "toHaveAttribute",
3057
3173
  type: "string"
@@ -3086,41 +3202,37 @@ var methods3 = {
3086
3202
  },
3087
3203
  textContent: { matcher: "toHaveText", type: "string" }
3088
3204
  };
3089
- var supportedMatchers = /* @__PURE__ */ new Set([
3090
- "toBe",
3091
- "toEqual",
3092
- "toBeTruthy",
3093
- "toBeFalsy"
3094
- ]);
3205
+ var supportedMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toBeTruthy", "toBeFalsy"]);
3095
3206
  var prefer_web_first_assertions_default = createRule({
3096
3207
  create(context) {
3097
3208
  return {
3098
3209
  CallExpression(node) {
3099
3210
  const fnCall = parseFnCall(context, node);
3100
- if (fnCall?.type !== "expect")
3101
- return;
3211
+ if (fnCall?.type !== "expect") return;
3102
3212
  const expect = findParent(fnCall.head.node, "CallExpression");
3103
- if (!expect)
3104
- return;
3213
+ if (!expect) return;
3105
3214
  const arg = dereference(context, fnCall.args[0]);
3106
- if (!arg)
3107
- return;
3215
+ if (!arg) return;
3108
3216
  const call = arg.type === "AwaitExpression" ? arg.argument : arg;
3109
3217
  if (call.type !== "CallExpression" || call.callee.type !== "MemberExpression") {
3110
3218
  return;
3111
3219
  }
3112
- if (!supportedMatchers.has(fnCall.matcherName))
3113
- return;
3220
+ if (!supportedMatchers.has(fnCall.matcherName)) return;
3114
3221
  const method = getStringValue(call.callee.property);
3115
3222
  const methodConfig = methods3[method];
3116
- if (!Object.hasOwn(methods3, method))
3117
- return;
3118
- const notModifier = fnCall.modifiers.find(
3119
- (mod) => getStringValue(mod) === "not"
3120
- );
3223
+ if (!Object.hasOwn(methods3, method)) return;
3224
+ const notModifier = fnCall.modifiers.find((mod) => getStringValue(mod) === "not");
3121
3225
  const isFalsy = methodConfig.type === "boolean" && (!!fnCall.matcherArgs.length && isBooleanLiteral(fnCall.matcherArgs[0], false) || fnCall.matcherName === "toBeFalsy");
3122
3226
  const isInverse = methodConfig.inverse ? notModifier || isFalsy : notModifier && isFalsy;
3123
3227
  const newMatcher = +!!notModifier ^ +isFalsy && methodConfig.inverse || methodConfig.matcher;
3228
+ if (methodConfig.fixable === false) {
3229
+ context.report({
3230
+ data: { matcher: methodConfig.matcher, method },
3231
+ messageId: "useWebFirstAssertion",
3232
+ node: call.callee.property
3233
+ });
3234
+ return;
3235
+ }
3124
3236
  const { callee } = call;
3125
3237
  context.report({
3126
3238
  data: {
@@ -3136,10 +3248,7 @@ var prefer_web_first_assertions_default = createRule({
3136
3248
  // Remove the await keyword
3137
3249
  fixer.replaceTextRange([arg.range[0], call.range[0]], ""),
3138
3250
  // Remove the old Playwright method and any arguments
3139
- fixer.replaceTextRange(
3140
- [callee.property.range[0] - 1, methodEnd],
3141
- ""
3142
- )
3251
+ fixer.replaceTextRange([callee.property.range[0] - 1, methodEnd], "")
3143
3252
  ];
3144
3253
  if (isInverse && notModifier) {
3145
3254
  const notRange = notModifier.range;
@@ -3158,18 +3267,11 @@ var prefer_web_first_assertions_default = createRule({
3158
3267
  const args = `{ ${propArg}: ${variable} }`;
3159
3268
  fixes.push(fixer.replaceText(matcherArg, args));
3160
3269
  }
3161
- const hasOtherArgs = !!methodArgs.filter(
3162
- (arg2) => !isBooleanLiteral(arg2)
3163
- ).length;
3270
+ const hasOtherArgs = !!methodArgs.filter((arg2) => !isBooleanLiteral(arg2)).length;
3164
3271
  if (methodArgs) {
3165
3272
  const range = fnCall.matcher.range;
3166
3273
  const stringArgs = methodArgs.map((arg2) => getRawValue(arg2)).concat(hasOtherArgs ? "" : []).join(", ");
3167
- fixes.push(
3168
- fixer.insertTextAfterRange(
3169
- [range[0], range[1] + 1],
3170
- stringArgs
3171
- )
3172
- );
3274
+ fixes.push(fixer.insertTextAfterRange([range[0], range[1] + 1], stringArgs));
3173
3275
  }
3174
3276
  return fixes;
3175
3277
  },
@@ -3184,7 +3286,7 @@ var prefer_web_first_assertions_default = createRule({
3184
3286
  category: "Best Practices",
3185
3287
  description: "Prefer web first assertions",
3186
3288
  recommended: true,
3187
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-web-first-assertions.md"
3289
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-web-first-assertions.md"
3188
3290
  },
3189
3291
  fixable: "code",
3190
3292
  messages: {
@@ -3208,9 +3310,7 @@ var shouldBeInHook = (context, node, allowedFunctionCalls = []) => {
3208
3310
  if (node.kind === "const") {
3209
3311
  return false;
3210
3312
  }
3211
- return node.declarations.some(
3212
- ({ init }) => init != null && !isNullOrUndefined(init)
3213
- );
3313
+ return node.declarations.some(({ init }) => init != null && !isNullOrUndefined(init));
3214
3314
  }
3215
3315
  default:
3216
3316
  return false;
@@ -3253,7 +3353,7 @@ var require_hook_default = createRule({
3253
3353
  category: "Best Practices",
3254
3354
  description: "Require setup and teardown code to be within a hook",
3255
3355
  recommended: false,
3256
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-hook.md"
3356
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/require-hook.md"
3257
3357
  },
3258
3358
  messages: {
3259
3359
  useHook: "This should be done within a hook"
@@ -3298,7 +3398,7 @@ var require_soft_assertions_default = createRule({
3298
3398
  docs: {
3299
3399
  description: "Require all assertions to use `expect.soft`",
3300
3400
  recommended: false,
3301
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-soft-assertions.md"
3401
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/require-soft-assertions.md"
3302
3402
  },
3303
3403
  fixable: "code",
3304
3404
  messages: {
@@ -3315,8 +3415,7 @@ var require_to_throw_message_default = createRule({
3315
3415
  return {
3316
3416
  CallExpression(node) {
3317
3417
  const call = parseFnCall(context, node);
3318
- if (call?.type !== "expect")
3319
- return;
3418
+ if (call?.type !== "expect") return;
3320
3419
  if (call.matcherArgs.length === 0 && ["toThrow", "toThrowError"].includes(call.matcherName) && !call.modifiers.some((nod) => getStringValue(nod) === "not")) {
3321
3420
  context.report({
3322
3421
  data: { matcherName: call.matcherName },
@@ -3332,7 +3431,7 @@ var require_to_throw_message_default = createRule({
3332
3431
  category: "Best Practices",
3333
3432
  description: "Require a message for `toThrow()`",
3334
3433
  recommended: false,
3335
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-to-throw-message.md"
3434
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/require-to-throw-message.md"
3336
3435
  },
3337
3436
  messages: {
3338
3437
  addErrorMessage: "Add an error message to {{ matcherName }}()"
@@ -3352,10 +3451,9 @@ var require_top_level_describe_default = createRule({
3352
3451
  let topLevelDescribeCount = 0;
3353
3452
  let describeCount = 0;
3354
3453
  return {
3355
- CallExpression(node) {
3454
+ "CallExpression"(node) {
3356
3455
  const call = parseFnCall(context, node);
3357
- if (!call)
3358
- return;
3456
+ if (!call) return;
3359
3457
  if (call.type === "describe") {
3360
3458
  describeCount++;
3361
3459
  if (describeCount === 1) {
@@ -3388,7 +3486,7 @@ var require_top_level_describe_default = createRule({
3388
3486
  category: "Best Practices",
3389
3487
  description: "Require test cases and hooks to be inside a `test.describe` block",
3390
3488
  recommended: false,
3391
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-top-level-describe.md"
3489
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/require-top-level-describe.md"
3392
3490
  },
3393
3491
  messages: {
3394
3492
  tooManyDescribes: "There should not be more than {{amount}} describe{{s}} at the top level",
@@ -3425,8 +3523,7 @@ var valid_describe_callback_default = createRule({
3425
3523
  return {
3426
3524
  CallExpression(node) {
3427
3525
  const call = parseFnCall(context, node);
3428
- if (call?.group !== "describe")
3429
- return;
3526
+ if (call?.group !== "describe") return;
3430
3527
  if (call.members.some((s) => getStringValue(s) === "configure")) {
3431
3528
  return;
3432
3529
  }
@@ -3485,7 +3582,7 @@ var valid_describe_callback_default = createRule({
3485
3582
  category: "Possible Errors",
3486
3583
  description: "Enforce valid `describe()` callback",
3487
3584
  recommended: true,
3488
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-describe-callback.md"
3585
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/valid-describe-callback.md"
3489
3586
  },
3490
3587
  messages: {
3491
3588
  invalidCallback: "Callback argument must be a function",
@@ -3499,132 +3596,6 @@ var valid_describe_callback_default = createRule({
3499
3596
  }
3500
3597
  });
3501
3598
 
3502
- // src/rules/valid-expect.ts
3503
- var findTopMostMemberExpression = (node) => {
3504
- let topMostMemberExpression = node;
3505
- let parent = getParent(node);
3506
- while (parent) {
3507
- if (parent.type !== "MemberExpression") {
3508
- break;
3509
- }
3510
- topMostMemberExpression = parent;
3511
- parent = parent.parent;
3512
- }
3513
- return topMostMemberExpression;
3514
- };
3515
- var valid_expect_default = createRule({
3516
- create(context) {
3517
- const options = {
3518
- maxArgs: 2,
3519
- minArgs: 1,
3520
- ...context.options?.[0] ?? {}
3521
- };
3522
- const minArgs = Math.min(options.minArgs, options.maxArgs);
3523
- const maxArgs = Math.max(options.minArgs, options.maxArgs);
3524
- return {
3525
- CallExpression(node) {
3526
- const call = parseFnCallWithReason(context, node);
3527
- if (typeof call === "string") {
3528
- const reportingNode = node.parent?.type === "MemberExpression" ? findTopMostMemberExpression(node.parent).property : node;
3529
- if (call === "matcher-not-found") {
3530
- context.report({
3531
- messageId: "matcherNotFound",
3532
- node: reportingNode
3533
- });
3534
- return;
3535
- }
3536
- if (call === "matcher-not-called") {
3537
- context.report({
3538
- messageId: isSupportedAccessor(reportingNode) && modifiers.has(getStringValue(reportingNode)) ? "matcherNotFound" : "matcherNotCalled",
3539
- node: reportingNode
3540
- });
3541
- }
3542
- if (call === "modifier-unknown") {
3543
- context.report({
3544
- messageId: "modifierUnknown",
3545
- node: reportingNode
3546
- });
3547
- return;
3548
- }
3549
- return;
3550
- } else if (call?.type !== "expect") {
3551
- return;
3552
- }
3553
- const expect = getParent(call.head.node);
3554
- if (expect?.type !== "CallExpression")
3555
- return;
3556
- if (expect.arguments.length < minArgs) {
3557
- const expectLength = getStringValue(call.head.node).length;
3558
- const loc = {
3559
- end: {
3560
- column: expect.loc.start.column + expectLength + 1,
3561
- line: expect.loc.start.line
3562
- },
3563
- start: {
3564
- column: expect.loc.start.column + expectLength,
3565
- line: expect.loc.start.line
3566
- }
3567
- };
3568
- context.report({
3569
- data: getAmountData(minArgs),
3570
- loc,
3571
- messageId: "notEnoughArgs",
3572
- node: expect
3573
- });
3574
- }
3575
- if (expect.arguments.length > maxArgs) {
3576
- const { start } = expect.arguments[maxArgs].loc;
3577
- const { end } = expect.arguments.at(-1).loc;
3578
- const loc = {
3579
- end: {
3580
- column: end.column,
3581
- line: end.line
3582
- },
3583
- start
3584
- };
3585
- context.report({
3586
- data: getAmountData(maxArgs),
3587
- loc,
3588
- messageId: "tooManyArgs",
3589
- node: expect
3590
- });
3591
- }
3592
- }
3593
- };
3594
- },
3595
- meta: {
3596
- docs: {
3597
- category: "Possible Errors",
3598
- description: "Enforce valid `expect()` usage",
3599
- recommended: true,
3600
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md"
3601
- },
3602
- messages: {
3603
- matcherNotCalled: "Matchers must be called to assert.",
3604
- matcherNotFound: "Expect must have a corresponding matcher call.",
3605
- notEnoughArgs: "Expect requires at least {{amount}} argument{{s}}.",
3606
- tooManyArgs: "Expect takes at most {{amount}} argument{{s}}."
3607
- },
3608
- schema: [
3609
- {
3610
- additionalProperties: false,
3611
- properties: {
3612
- maxArgs: {
3613
- minimum: 1,
3614
- type: "number"
3615
- },
3616
- minArgs: {
3617
- minimum: 1,
3618
- type: "number"
3619
- }
3620
- },
3621
- type: "object"
3622
- }
3623
- ],
3624
- type: "problem"
3625
- }
3626
- });
3627
-
3628
3599
  // src/rules/valid-expect-in-promise.ts
3629
3600
  var isPromiseChainCall = (node) => {
3630
3601
  if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && isSupportedAccessor(node.callee.property)) {
@@ -3646,9 +3617,7 @@ var isTestCaseCallWithCallbackArg = (context, node) => {
3646
3617
  if (jestCallFn?.type !== "test") {
3647
3618
  return false;
3648
3619
  }
3649
- const isJestEach = jestCallFn.members.some(
3650
- (s) => getStringValue(s) === "each"
3651
- );
3620
+ const isJestEach = jestCallFn.members.some((s) => getStringValue(s) === "each");
3652
3621
  if (isJestEach && node.callee.type !== "TaggedTemplateExpression") {
3653
3622
  return true;
3654
3623
  }
@@ -3658,8 +3627,7 @@ var isTestCaseCallWithCallbackArg = (context, node) => {
3658
3627
  };
3659
3628
  var isPromiseMethodThatUsesValue = (node, identifier) => {
3660
3629
  const name = getStringValue(identifier);
3661
- if (node.argument == null)
3662
- return false;
3630
+ if (node.argument == null) return false;
3663
3631
  if (node.argument.type === "CallExpression" && node.argument.arguments.length > 0) {
3664
3632
  const nodeName = getNodeName(node.argument);
3665
3633
  if (["Promise.all", "Promise.allSettled"].includes(nodeName)) {
@@ -3763,10 +3731,10 @@ var findFirstBlockBodyUp = (node) => {
3763
3731
  if (parent.type === "BlockStatement") {
3764
3732
  return parent.body;
3765
3733
  }
3766
- parent = getParent(parent);
3734
+ parent = parent.parent;
3767
3735
  }
3768
3736
  throw new Error(
3769
- `Could not find BlockStatement - please file a github issue at https://github.com/playwright-community/eslint-plugin-playwright`
3737
+ `Could not find BlockStatement - please file a github issue at https://github.com/mskelton/eslint-plugin-playwright`
3770
3738
  );
3771
3739
  };
3772
3740
  var isDirectlyWithinTestCaseCall = (context, node) => {
@@ -3776,7 +3744,7 @@ var isDirectlyWithinTestCaseCall = (context, node) => {
3776
3744
  parent = parent.parent;
3777
3745
  return parent?.type === "CallExpression" && isTypeOfFnCall(context, parent, ["test"]);
3778
3746
  }
3779
- parent = getParent(parent);
3747
+ parent = parent.parent;
3780
3748
  }
3781
3749
  return false;
3782
3750
  };
@@ -3792,7 +3760,7 @@ var valid_expect_in_promise_default = createRule({
3792
3760
  let inTestCaseWithDoneCallback = false;
3793
3761
  const chains = [];
3794
3762
  return {
3795
- CallExpression(node) {
3763
+ "CallExpression"(node) {
3796
3764
  if (isTestCaseCallWithCallbackArg(context, node)) {
3797
3765
  inTestCaseWithDoneCallback = true;
3798
3766
  return;
@@ -3831,11 +3799,7 @@ var valid_expect_in_promise_default = createRule({
3831
3799
  break;
3832
3800
  }
3833
3801
  case "AssignmentExpression": {
3834
- if (parent.left.type === "Identifier" && isValueAwaitedOrReturned(
3835
- context,
3836
- parent.left,
3837
- findFirstBlockBodyUp(parent)
3838
- )) {
3802
+ if (parent.left.type === "Identifier" && isValueAwaitedOrReturned(context, parent.left, findFirstBlockBodyUp(parent))) {
3839
3803
  return;
3840
3804
  }
3841
3805
  break;
@@ -3859,7 +3823,7 @@ var valid_expect_in_promise_default = createRule({
3859
3823
  category: "Best Practices",
3860
3824
  description: "Require promises that have expectations in their chain to be valid",
3861
3825
  recommended: true,
3862
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect-in-promise.md"
3826
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/valid-expect-in-promise.md"
3863
3827
  },
3864
3828
  messages: {
3865
3829
  expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in its chain are called"
@@ -3869,6 +3833,131 @@ var valid_expect_in_promise_default = createRule({
3869
3833
  }
3870
3834
  });
3871
3835
 
3836
+ // src/rules/valid-expect.ts
3837
+ var findTopMostMemberExpression = (node) => {
3838
+ let topMostMemberExpression = node;
3839
+ let parent = node.parent;
3840
+ while (parent) {
3841
+ if (parent.type !== "MemberExpression") {
3842
+ break;
3843
+ }
3844
+ topMostMemberExpression = parent;
3845
+ parent = parent.parent;
3846
+ }
3847
+ return topMostMemberExpression;
3848
+ };
3849
+ var valid_expect_default = createRule({
3850
+ create(context) {
3851
+ const options = {
3852
+ maxArgs: 2,
3853
+ minArgs: 1,
3854
+ ...context.options?.[0] ?? {}
3855
+ };
3856
+ const minArgs = Math.min(options.minArgs, options.maxArgs);
3857
+ const maxArgs = Math.max(options.minArgs, options.maxArgs);
3858
+ return {
3859
+ CallExpression(node) {
3860
+ const call = parseFnCallWithReason(context, node);
3861
+ if (typeof call === "string") {
3862
+ const reportingNode = node.parent?.type === "MemberExpression" ? findTopMostMemberExpression(node.parent).property : node;
3863
+ if (call === "matcher-not-found") {
3864
+ context.report({
3865
+ messageId: "matcherNotFound",
3866
+ node: reportingNode
3867
+ });
3868
+ return;
3869
+ }
3870
+ if (call === "matcher-not-called") {
3871
+ context.report({
3872
+ messageId: isSupportedAccessor(reportingNode) && modifiers.has(getStringValue(reportingNode)) ? "matcherNotFound" : "matcherNotCalled",
3873
+ node: reportingNode
3874
+ });
3875
+ }
3876
+ if (call === "modifier-unknown") {
3877
+ context.report({
3878
+ messageId: "modifierUnknown",
3879
+ node: reportingNode
3880
+ });
3881
+ return;
3882
+ }
3883
+ return;
3884
+ } else if (call?.type !== "expect") {
3885
+ return;
3886
+ }
3887
+ const { parent: expect } = call.head.node;
3888
+ if (expect?.type !== "CallExpression") return;
3889
+ if (expect.arguments.length < minArgs) {
3890
+ const expectLength = getStringValue(call.head.node).length;
3891
+ const loc = {
3892
+ end: {
3893
+ column: expect.loc.start.column + expectLength + 1,
3894
+ line: expect.loc.start.line
3895
+ },
3896
+ start: {
3897
+ column: expect.loc.start.column + expectLength,
3898
+ line: expect.loc.start.line
3899
+ }
3900
+ };
3901
+ context.report({
3902
+ data: getAmountData(minArgs),
3903
+ loc,
3904
+ messageId: "notEnoughArgs",
3905
+ node: expect
3906
+ });
3907
+ }
3908
+ if (expect.arguments.length > maxArgs) {
3909
+ const { start } = expect.arguments[maxArgs].loc;
3910
+ const { end } = expect.arguments.at(-1).loc;
3911
+ const loc = {
3912
+ end: {
3913
+ column: end.column,
3914
+ line: end.line
3915
+ },
3916
+ start
3917
+ };
3918
+ context.report({
3919
+ data: getAmountData(maxArgs),
3920
+ loc,
3921
+ messageId: "tooManyArgs",
3922
+ node: expect
3923
+ });
3924
+ }
3925
+ }
3926
+ };
3927
+ },
3928
+ meta: {
3929
+ docs: {
3930
+ category: "Possible Errors",
3931
+ description: "Enforce valid `expect()` usage",
3932
+ recommended: true,
3933
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md"
3934
+ },
3935
+ messages: {
3936
+ matcherNotCalled: "Matchers must be called to assert.",
3937
+ matcherNotFound: "Expect must have a corresponding matcher call.",
3938
+ notEnoughArgs: "Expect requires at least {{amount}} argument{{s}}.",
3939
+ tooManyArgs: "Expect takes at most {{amount}} argument{{s}}."
3940
+ },
3941
+ schema: [
3942
+ {
3943
+ additionalProperties: false,
3944
+ properties: {
3945
+ maxArgs: {
3946
+ minimum: 1,
3947
+ type: "number"
3948
+ },
3949
+ minArgs: {
3950
+ minimum: 1,
3951
+ type: "number"
3952
+ }
3953
+ },
3954
+ type: "object"
3955
+ }
3956
+ ],
3957
+ type: "problem"
3958
+ }
3959
+ });
3960
+
3872
3961
  // src/rules/valid-test-tags.ts
3873
3962
  var valid_test_tags_default = createRule({
3874
3963
  create(context) {
@@ -3876,15 +3965,11 @@ var valid_test_tags_default = createRule({
3876
3965
  const allowedTags = options.allowedTags || [];
3877
3966
  const disallowedTags = options.disallowedTags || [];
3878
3967
  if (allowedTags.length > 0 && disallowedTags.length > 0) {
3879
- throw new Error(
3880
- "The allowedTags and disallowedTags options cannot be used together"
3881
- );
3968
+ throw new Error("The allowedTags and disallowedTags options cannot be used together");
3882
3969
  }
3883
3970
  for (const tag of [...allowedTags, ...disallowedTags]) {
3884
3971
  if (typeof tag === "string" && !tag.startsWith("@")) {
3885
- throw new Error(
3886
- `Invalid tag "${tag}" in configuration: tags must start with @`
3887
- );
3972
+ throw new Error(`Invalid tag "${tag}" in configuration: tags must start with @`);
3888
3973
  }
3889
3974
  }
3890
3975
  const extractTagsFromTitle = (title) => {
@@ -3927,11 +4012,9 @@ var valid_test_tags_default = createRule({
3927
4012
  return {
3928
4013
  CallExpression(node) {
3929
4014
  const call = parseFnCall(context, node);
3930
- if (!call)
3931
- return;
4015
+ if (!call) return;
3932
4016
  const { type } = call;
3933
- if (type !== "test" && type !== "describe" && type !== "step")
3934
- return;
4017
+ if (type !== "test" && type !== "describe" && type !== "step") return;
3935
4018
  if (node.arguments.length > 0) {
3936
4019
  const titleArg = node.arguments[0];
3937
4020
  if (titleArg && titleArg.type === "Literal" && typeof titleArg.value === "string") {
@@ -3941,17 +4024,14 @@ var valid_test_tags_default = createRule({
3941
4024
  }
3942
4025
  }
3943
4026
  }
3944
- if (node.arguments.length < 2)
3945
- return;
4027
+ if (node.arguments.length < 2) return;
3946
4028
  const optionsArg = node.arguments[1];
3947
- if (!optionsArg || optionsArg.type !== "ObjectExpression")
3948
- return;
4029
+ if (!optionsArg || optionsArg.type !== "ObjectExpression") return;
3949
4030
  const tagProperty = optionsArg.properties.find(
3950
4031
  (prop) => prop.type === "Property" && !("argument" in prop) && // Ensure it's not a spread element
3951
4032
  prop.key.type === "Identifier" && prop.key.name === "tag"
3952
4033
  );
3953
- if (!tagProperty)
3954
- return;
4034
+ if (!tagProperty) return;
3955
4035
  const tagValue = tagProperty.value;
3956
4036
  if (tagValue.type === "Literal") {
3957
4037
  if (typeof tagValue.value !== "string") {
@@ -4076,10 +4156,7 @@ var valid_title_default = createRule({
4076
4156
  mustMatch,
4077
4157
  mustNotMatch
4078
4158
  } = opts;
4079
- const disallowedWordsRegexp = new RegExp(
4080
- `\\b(${disallowedWords.join("|")})\\b`,
4081
- "iu"
4082
- );
4159
+ const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join("|")})\\b`, "iu");
4083
4160
  const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch ?? {});
4084
4161
  const mustMatchPatterns = compileMatcherPatterns(mustMatch ?? {});
4085
4162
  return {
@@ -4090,8 +4167,7 @@ var valid_title_default = createRule({
4090
4167
  }
4091
4168
  const [argument] = node.arguments;
4092
4169
  const title = dereference(context, argument) ?? argument;
4093
- if (!title)
4094
- return;
4170
+ if (!title) return;
4095
4171
  if (!isStringNode(title)) {
4096
4172
  if (title.type === "BinaryExpression" && doesBinaryExpressionContainStringNode(title)) {
4097
4173
  return;
@@ -4184,7 +4260,7 @@ var valid_title_default = createRule({
4184
4260
  category: "Best Practices",
4185
4261
  description: "Enforce valid titles",
4186
4262
  recommended: true,
4187
- url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-title.md"
4263
+ url: "https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/valid-title.md"
4188
4264
  },
4189
4265
  fixable: "code",
4190
4266
  messages: {
@@ -4249,6 +4325,7 @@ var valid_title_default = createRule({
4249
4325
  var index = {
4250
4326
  configs: {},
4251
4327
  rules: {
4328
+ "consistent-spacing-between-blocks": consistent_spacing_between_blocks_default,
4252
4329
  "expect-expect": expect_expect_default,
4253
4330
  "max-expects": max_expects_default,
4254
4331
  "max-nested-describe": max_nested_describe_default,
@@ -4268,6 +4345,7 @@ var index = {
4268
4345
  "no-nth-methods": no_nth_methods_default,
4269
4346
  "no-page-pause": no_page_pause_default,
4270
4347
  "no-raw-locators": no_raw_locators_default,
4348
+ "no-restricted-locators": no_restricted_locators_default,
4271
4349
  "no-restricted-matchers": no_restricted_matchers_default,
4272
4350
  "no-skipped-test": no_skipped_test_default,
4273
4351
  "no-slowed-test": no_slowed_test_default,
@@ -4306,6 +4384,7 @@ var index = {
4306
4384
  var sharedConfig = {
4307
4385
  rules: {
4308
4386
  "no-empty-pattern": "off",
4387
+ "playwright/consistent-spacing-between-blocks": "warn",
4309
4388
  "playwright/expect-expect": "warn",
4310
4389
  "playwright/max-nested-describe": "warn",
4311
4390
  "playwright/missing-playwright-await": "error",
@@ -4356,6 +4435,6 @@ module.exports = {
4356
4435
  configs: {
4357
4436
  "flat/recommended": flatConfig,
4358
4437
  "playwright-test": legacyConfig,
4359
- recommended: legacyConfig
4438
+ "recommended": legacyConfig
4360
4439
  }
4361
4440
  };