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.
- package/README.md +57 -54
- package/dist/index.cjs +617 -538
- package/index.d.ts +1 -1
- 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
|
|
123
|
-
const
|
|
124
|
-
|
|
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
|
-
|
|
140
|
-
if (name === "
|
|
141
|
-
|
|
142
|
-
if (name
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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) =>
|
|
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 =
|
|
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
|
|
375
|
-
var isAssignmentExpression = (node) => node
|
|
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 ? {
|
|
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/
|
|
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 =
|
|
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" ||
|
|
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/
|
|
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/
|
|
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 =
|
|
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 =
|
|
715
|
-
if (!parent)
|
|
716
|
-
|
|
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
|
-
|
|
734
|
-
if (
|
|
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/
|
|
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
|
|
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/
|
|
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 }) =>
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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:
|
|
1406
|
+
node: parent.callee
|
|
1280
1407
|
});
|
|
1281
1408
|
}
|
|
1282
1409
|
}
|
|
1283
1410
|
function popStepCallback(node) {
|
|
1284
1411
|
const { parent } = node;
|
|
1285
|
-
if (parent
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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 =
|
|
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/
|
|
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 (
|
|
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/
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1837
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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 =
|
|
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
|
-
|
|
2061
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
2254
|
-
|
|
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,
|
|
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/
|
|
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
|
-
|
|
2325
|
-
|
|
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,
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
2886
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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 =
|
|
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/
|
|
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 =
|
|
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/
|
|
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/
|
|
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
|
};
|