@sarj/eslint-plugin 16.19.0 → 16.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +125 -7
- package/dist/index.js +125 -7
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
52
52
|
// package.json
|
|
53
53
|
var package_default = {
|
|
54
54
|
name: "@sarj/eslint-plugin",
|
|
55
|
-
version: "16.19.
|
|
55
|
+
version: "16.19.1",
|
|
56
56
|
packageManager: "npm@12.2.0",
|
|
57
57
|
description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
58
58
|
type: "module",
|
|
@@ -14354,9 +14354,10 @@ var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
|
|
|
14354
14354
|
remediation: "Assert on a value produced by the behavior under test against an independently specified expectation, or remove only the redundant assertion while preserving coverage.",
|
|
14355
14355
|
category: "testing",
|
|
14356
14356
|
limitations: [
|
|
14357
|
-
"Only supported `expect` matcher calls in recognized test files are inspected, including imported aliases and a single `.not` on zero-argument matchers. Local or type-only bindings, regular expressions, async modifiers and unsupported coercions are excluded; failing constant assertions are not tautologies.",
|
|
14357
|
+
"Only supported `expect` matcher calls in recognized test files are inspected, including imported aliases, static string messages on imported Vitest/Playwright expect calls, and a single `.not` on zero-argument matchers. Local or type-only bindings, regular expressions, async modifiers and unsupported coercions are excluded; failing constant assertions are not tautologies.",
|
|
14358
14358
|
"Scalar const bindings and aliases resolve through lexical scope; mutable bindings, objects, arrays, destructuring, runtime initializers and forward references are excluded.",
|
|
14359
|
-
"Expected-side runtime dependence, including a complete inline snapshot computed from its actual value, remains manual review; independent static interpolation and intentional partial masking are valid."
|
|
14359
|
+
"Expected-side runtime dependence, including a complete inline snapshot computed from its actual value, remains manual review; independent static interpolation and intentional partial masking are valid.",
|
|
14360
|
+
"Direct framework expect.extend registrations exclude replaced built-in matchers; opaque registrations exclude all supported matchers, and nonempty custom equality testers exclude deep equality. Direct synchronous expect callbacks observed by a throwing matcher retain their assertion contracts. Cross-file or indirect matcher customization is not inferred."
|
|
14360
14361
|
],
|
|
14361
14362
|
examples: [
|
|
14362
14363
|
{
|
|
@@ -14416,6 +14417,66 @@ var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
|
|
|
14416
14417
|
focusPath: "src/service.test.ts",
|
|
14417
14418
|
expectedCount: 0,
|
|
14418
14419
|
public: true
|
|
14420
|
+
},
|
|
14421
|
+
{
|
|
14422
|
+
id: "message-only-oracle",
|
|
14423
|
+
scenarioId: "static-message",
|
|
14424
|
+
title: "A diagnostic string does not make a literal comparison observe behavior",
|
|
14425
|
+
outcome: "match",
|
|
14426
|
+
files: [{ path: "src/service.test.ts", source: "import {expect} from 'vitest'; expect(true, 'service ready').toBe(true);" }],
|
|
14427
|
+
focusPath: "src/service.test.ts",
|
|
14428
|
+
expectedCount: 1,
|
|
14429
|
+
public: true
|
|
14430
|
+
},
|
|
14431
|
+
{
|
|
14432
|
+
id: "message-produced-oracle",
|
|
14433
|
+
scenarioId: "static-message",
|
|
14434
|
+
title: "A diagnostic message can describe an observed result",
|
|
14435
|
+
outcome: "no-match",
|
|
14436
|
+
files: [{ path: "src/service.test.ts", source: "import {expect} from 'vitest'; expect(serviceReady(), 'service ready').toBe(true);" }],
|
|
14437
|
+
focusPath: "src/service.test.ts",
|
|
14438
|
+
expectedCount: 0,
|
|
14439
|
+
public: true
|
|
14440
|
+
},
|
|
14441
|
+
{
|
|
14442
|
+
id: "uncaptured-matcher",
|
|
14443
|
+
scenarioId: "matcher-contract",
|
|
14444
|
+
title: "An uncaptured literal assertion remains redundant",
|
|
14445
|
+
outcome: "match",
|
|
14446
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect(true).toBeTruthy();" }],
|
|
14447
|
+
focusPath: "src/assertion.test.ts",
|
|
14448
|
+
expectedCount: 1,
|
|
14449
|
+
public: true
|
|
14450
|
+
},
|
|
14451
|
+
{
|
|
14452
|
+
id: "captured-matcher",
|
|
14453
|
+
scenarioId: "matcher-contract",
|
|
14454
|
+
title: "A throwing matcher observes the inner assertion contract",
|
|
14455
|
+
outcome: "no-match",
|
|
14456
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect(() => expect(true).toBeTruthy()).not.toThrow();" }],
|
|
14457
|
+
focusPath: "src/assertion.test.ts",
|
|
14458
|
+
expectedCount: 0,
|
|
14459
|
+
public: true
|
|
14460
|
+
},
|
|
14461
|
+
{
|
|
14462
|
+
id: "unrelated-custom-matcher",
|
|
14463
|
+
scenarioId: "custom-matcher",
|
|
14464
|
+
title: "Adding a different matcher preserves the built-in proof",
|
|
14465
|
+
outcome: "match",
|
|
14466
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect.extend({toBeReady: custom}); expect(true).toBe(true);" }],
|
|
14467
|
+
focusPath: "src/assertion.test.ts",
|
|
14468
|
+
expectedCount: 1,
|
|
14469
|
+
public: true
|
|
14470
|
+
},
|
|
14471
|
+
{
|
|
14472
|
+
id: "replaced-custom-matcher",
|
|
14473
|
+
scenarioId: "custom-matcher",
|
|
14474
|
+
title: "A replaced matcher can reject a literal comparison",
|
|
14475
|
+
outcome: "no-match",
|
|
14476
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect.extend({toBe: () => ({pass: false, message: () => 'custom contract'})}); expect(true).toBe(true);" }],
|
|
14477
|
+
focusPath: "src/assertion.test.ts",
|
|
14478
|
+
expectedCount: 0,
|
|
14479
|
+
public: true
|
|
14419
14480
|
}
|
|
14420
14481
|
]
|
|
14421
14482
|
};
|
|
@@ -14430,6 +14491,7 @@ var ZERO_ARG_MATCHERS = /* @__PURE__ */ new Set([
|
|
|
14430
14491
|
]);
|
|
14431
14492
|
var OPERAND_PREVIEW_CHARS = 40;
|
|
14432
14493
|
var NUMERIC_SIGNS = /* @__PURE__ */ new Set(["-", "+"]);
|
|
14494
|
+
var THROW_MATCHERS = /* @__PURE__ */ new Set(["toThrow", "toThrowError", "toThrowErrorMatchingSnapshot", "toThrowErrorMatchingInlineSnapshot"]);
|
|
14433
14495
|
function resolveLiteral(node, sourceCode, seen = /* @__PURE__ */ new Set()) {
|
|
14434
14496
|
node = unwrapExpression(node);
|
|
14435
14497
|
if (isLiteral(node)) return node;
|
|
@@ -14505,12 +14567,67 @@ function expectOperand(callee, sourceCode) {
|
|
|
14505
14567
|
const negated = receiver.type === import_utils80.AST_NODE_TYPES.MemberExpression && import_utils80.ASTUtils.getPropertyName(receiver) === "not";
|
|
14506
14568
|
if (negated && receiver.type === import_utils80.AST_NODE_TYPES.MemberExpression) receiver = unwrapExpression(receiver.object);
|
|
14507
14569
|
const expectCallee = receiver.type === import_utils80.AST_NODE_TYPES.CallExpression ? unwrapExpression(receiver.callee) : null;
|
|
14508
|
-
if (receiver.type !== import_utils80.AST_NODE_TYPES.CallExpression || expectCallee?.type !== import_utils80.AST_NODE_TYPES.Identifier || runtimeTestFrameworkName(sourceCode, expectCallee, "assertion") !== "expect" || receiver.arguments.length !== 1) {
|
|
14570
|
+
if (receiver.type !== import_utils80.AST_NODE_TYPES.CallExpression || expectCallee?.type !== import_utils80.AST_NODE_TYPES.Identifier || runtimeTestFrameworkName(sourceCode, expectCallee, "assertion") !== "expect" || receiver.arguments.length !== 1 && !hasStaticMessage(receiver, expectCallee, sourceCode)) {
|
|
14509
14571
|
return null;
|
|
14510
14572
|
}
|
|
14511
14573
|
const operand = receiver.arguments[0];
|
|
14512
14574
|
return operand === void 0 ? null : { operand, negated };
|
|
14513
14575
|
}
|
|
14576
|
+
function hasStaticMessage(call, callee, source) {
|
|
14577
|
+
if (call.arguments.length !== 2) return false;
|
|
14578
|
+
const definition = import_utils80.ASTUtils.findVariable(source.getScope(callee), callee.name)?.defs[0];
|
|
14579
|
+
if (definition?.type !== "ImportBinding" || definition.parent.type !== import_utils80.AST_NODE_TYPES.ImportDeclaration || !["vitest", "@playwright/test"].includes(definition.parent.source.value)) return false;
|
|
14580
|
+
const message = call.arguments[1];
|
|
14581
|
+
const literal2 = message === void 0 ? null : resolveLiteral(message, source);
|
|
14582
|
+
return literal2?.type === import_utils80.AST_NODE_TYPES.Literal && typeof literal2.value === "string" || literal2?.type === import_utils80.AST_NODE_TYPES.TemplateLiteral;
|
|
14583
|
+
}
|
|
14584
|
+
function isExceptionContract(node, source) {
|
|
14585
|
+
let callback = node.parent;
|
|
14586
|
+
while (callback !== void 0 && callback.type !== import_utils80.AST_NODE_TYPES.Program && callback.type !== import_utils80.AST_NODE_TYPES.ArrowFunctionExpression && callback.type !== import_utils80.AST_NODE_TYPES.FunctionExpression && callback.type !== import_utils80.AST_NODE_TYPES.FunctionDeclaration) callback = callback.parent;
|
|
14587
|
+
if (callback === void 0 || callback.type === import_utils80.AST_NODE_TYPES.Program || callback.type === import_utils80.AST_NODE_TYPES.FunctionDeclaration || callback.async || callback.generator) return false;
|
|
14588
|
+
const call = directArgumentCall(callback);
|
|
14589
|
+
if (call === null || call.optional) return false;
|
|
14590
|
+
let member = outerExpression(call).parent;
|
|
14591
|
+
if (member?.type === import_utils80.AST_NODE_TYPES.MemberExpression && import_utils80.ASTUtils.getPropertyName(member) === "not") member = outerExpression(member).parent;
|
|
14592
|
+
if (member?.type !== import_utils80.AST_NODE_TYPES.MemberExpression || member.optional || !THROW_MATCHERS.has(import_utils80.ASTUtils.getPropertyName(member) ?? "")) return false;
|
|
14593
|
+
const invocation = outerExpression(member).parent;
|
|
14594
|
+
return invocation?.type === import_utils80.AST_NODE_TYPES.CallExpression && !invocation.optional && unwrapExpression(expectOperand(member, source)?.operand ?? member) === callback;
|
|
14595
|
+
}
|
|
14596
|
+
function customizedMatchers(source) {
|
|
14597
|
+
const customized = /* @__PURE__ */ new Set();
|
|
14598
|
+
const visit = (node) => {
|
|
14599
|
+
if (node.type === import_utils80.AST_NODE_TYPES.CallExpression) collectCustomization(node, source, customized);
|
|
14600
|
+
for (const key of source.visitorKeys[node.type] ?? []) {
|
|
14601
|
+
const child = node[key];
|
|
14602
|
+
for (const value of Array.isArray(child) ? child : [child]) {
|
|
14603
|
+
if (typeof value === "object" && value !== null && "type" in value) visit(value);
|
|
14604
|
+
}
|
|
14605
|
+
}
|
|
14606
|
+
};
|
|
14607
|
+
visit(source.ast);
|
|
14608
|
+
return customized;
|
|
14609
|
+
}
|
|
14610
|
+
function collectCustomization(node, source, customized) {
|
|
14611
|
+
const member = unwrapExpression(node.callee);
|
|
14612
|
+
if (member.type !== import_utils80.AST_NODE_TYPES.MemberExpression) return;
|
|
14613
|
+
const receiver = unwrapExpression(member.object);
|
|
14614
|
+
if (receiver.type !== import_utils80.AST_NODE_TYPES.Identifier || runtimeTestFrameworkName(source, receiver, "assertion") !== "expect") return;
|
|
14615
|
+
const method = import_utils80.ASTUtils.getPropertyName(member);
|
|
14616
|
+
const argument = node.arguments[0] === void 0 ? null : unwrapExpression(node.arguments[0]);
|
|
14617
|
+
if (method === "addEqualityTesters" && !(argument?.type === import_utils80.AST_NODE_TYPES.ArrayExpression && argument.elements.length === 0)) {
|
|
14618
|
+
customized.add("toEqual");
|
|
14619
|
+
customized.add("toStrictEqual");
|
|
14620
|
+
}
|
|
14621
|
+
if (method !== "extend") return;
|
|
14622
|
+
if (argument?.type !== import_utils80.AST_NODE_TYPES.ObjectExpression) {
|
|
14623
|
+
customized.add("*");
|
|
14624
|
+
return;
|
|
14625
|
+
}
|
|
14626
|
+
for (const property of argument.properties) {
|
|
14627
|
+
const name = property.type === import_utils80.AST_NODE_TYPES.Property ? import_utils80.ASTUtils.getPropertyName(property) : null;
|
|
14628
|
+
customized.add(name ?? "*");
|
|
14629
|
+
}
|
|
14630
|
+
}
|
|
14514
14631
|
var no_tautological_expect_default = createRule({
|
|
14515
14632
|
name: "no-tautological-expect",
|
|
14516
14633
|
documentation: NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION,
|
|
@@ -14530,6 +14647,7 @@ var no_tautological_expect_default = createRule({
|
|
|
14530
14647
|
if (!isTestFile(context.filename)) {
|
|
14531
14648
|
return {};
|
|
14532
14649
|
}
|
|
14650
|
+
const customized = customizedMatchers(context.sourceCode);
|
|
14533
14651
|
const preview2 = (node) => {
|
|
14534
14652
|
const text = context.sourceCode.getText(node).replaceAll(/\s+/gu, " ");
|
|
14535
14653
|
return text.length > OPERAND_PREVIEW_CHARS ? `${text.slice(0, OPERAND_PREVIEW_CHARS)}\u2026` : text;
|
|
@@ -14539,12 +14657,12 @@ var no_tautological_expect_default = createRule({
|
|
|
14539
14657
|
const callee = unwrapExpression(node.callee);
|
|
14540
14658
|
if (callee.type !== import_utils80.AST_NODE_TYPES.MemberExpression) return;
|
|
14541
14659
|
const matcher = import_utils80.ASTUtils.getPropertyName(callee);
|
|
14542
|
-
if (matcher === null) return;
|
|
14660
|
+
if (matcher === null || !ZERO_ARG_MATCHERS.has(matcher) && !EQUALITY_MATCHERS.has(matcher) || customized.has("*") || customized.has(matcher)) return;
|
|
14543
14661
|
const assertion = expectOperand(callee, context.sourceCode);
|
|
14544
14662
|
if (assertion === null) return;
|
|
14545
14663
|
const writtenOperand = assertion.operand;
|
|
14546
14664
|
const operand = resolveLiteral(writtenOperand, context.sourceCode);
|
|
14547
|
-
if (operand === null) {
|
|
14665
|
+
if (operand === null || isExceptionContract(node, context.sourceCode)) {
|
|
14548
14666
|
return;
|
|
14549
14667
|
}
|
|
14550
14668
|
const writtenMatcher = `${assertion.negated ? "not." : ""}${matcher}`;
|
|
@@ -26014,7 +26132,7 @@ function classMethodCounts(body2, methods) {
|
|
|
26014
26132
|
|
|
26015
26133
|
// src/rules/source-coupled-test.ts
|
|
26016
26134
|
var import_utils136 = require("@typescript-eslint/utils");
|
|
26017
|
-
var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc
|
|
26135
|
+
var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc|toml|py|[cm]?[jt]s)$|(?<!\.(?:tf|tftest))\.json$/iu;
|
|
26018
26136
|
var FS_MODULES = /* @__PURE__ */ new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
|
|
26019
26137
|
var FS_READERS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
|
|
26020
26138
|
var TEXT_TRANSFORMS = /* @__PURE__ */ new Set([
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@sarj/eslint-plugin",
|
|
4
|
-
version: "16.19.
|
|
4
|
+
version: "16.19.1",
|
|
5
5
|
packageManager: "npm@12.2.0",
|
|
6
6
|
description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
7
7
|
type: "module",
|
|
@@ -14335,9 +14335,10 @@ var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
|
|
|
14335
14335
|
remediation: "Assert on a value produced by the behavior under test against an independently specified expectation, or remove only the redundant assertion while preserving coverage.",
|
|
14336
14336
|
category: "testing",
|
|
14337
14337
|
limitations: [
|
|
14338
|
-
"Only supported `expect` matcher calls in recognized test files are inspected, including imported aliases and a single `.not` on zero-argument matchers. Local or type-only bindings, regular expressions, async modifiers and unsupported coercions are excluded; failing constant assertions are not tautologies.",
|
|
14338
|
+
"Only supported `expect` matcher calls in recognized test files are inspected, including imported aliases, static string messages on imported Vitest/Playwright expect calls, and a single `.not` on zero-argument matchers. Local or type-only bindings, regular expressions, async modifiers and unsupported coercions are excluded; failing constant assertions are not tautologies.",
|
|
14339
14339
|
"Scalar const bindings and aliases resolve through lexical scope; mutable bindings, objects, arrays, destructuring, runtime initializers and forward references are excluded.",
|
|
14340
|
-
"Expected-side runtime dependence, including a complete inline snapshot computed from its actual value, remains manual review; independent static interpolation and intentional partial masking are valid."
|
|
14340
|
+
"Expected-side runtime dependence, including a complete inline snapshot computed from its actual value, remains manual review; independent static interpolation and intentional partial masking are valid.",
|
|
14341
|
+
"Direct framework expect.extend registrations exclude replaced built-in matchers; opaque registrations exclude all supported matchers, and nonempty custom equality testers exclude deep equality. Direct synchronous expect callbacks observed by a throwing matcher retain their assertion contracts. Cross-file or indirect matcher customization is not inferred."
|
|
14341
14342
|
],
|
|
14342
14343
|
examples: [
|
|
14343
14344
|
{
|
|
@@ -14397,6 +14398,66 @@ var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
|
|
|
14397
14398
|
focusPath: "src/service.test.ts",
|
|
14398
14399
|
expectedCount: 0,
|
|
14399
14400
|
public: true
|
|
14401
|
+
},
|
|
14402
|
+
{
|
|
14403
|
+
id: "message-only-oracle",
|
|
14404
|
+
scenarioId: "static-message",
|
|
14405
|
+
title: "A diagnostic string does not make a literal comparison observe behavior",
|
|
14406
|
+
outcome: "match",
|
|
14407
|
+
files: [{ path: "src/service.test.ts", source: "import {expect} from 'vitest'; expect(true, 'service ready').toBe(true);" }],
|
|
14408
|
+
focusPath: "src/service.test.ts",
|
|
14409
|
+
expectedCount: 1,
|
|
14410
|
+
public: true
|
|
14411
|
+
},
|
|
14412
|
+
{
|
|
14413
|
+
id: "message-produced-oracle",
|
|
14414
|
+
scenarioId: "static-message",
|
|
14415
|
+
title: "A diagnostic message can describe an observed result",
|
|
14416
|
+
outcome: "no-match",
|
|
14417
|
+
files: [{ path: "src/service.test.ts", source: "import {expect} from 'vitest'; expect(serviceReady(), 'service ready').toBe(true);" }],
|
|
14418
|
+
focusPath: "src/service.test.ts",
|
|
14419
|
+
expectedCount: 0,
|
|
14420
|
+
public: true
|
|
14421
|
+
},
|
|
14422
|
+
{
|
|
14423
|
+
id: "uncaptured-matcher",
|
|
14424
|
+
scenarioId: "matcher-contract",
|
|
14425
|
+
title: "An uncaptured literal assertion remains redundant",
|
|
14426
|
+
outcome: "match",
|
|
14427
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect(true).toBeTruthy();" }],
|
|
14428
|
+
focusPath: "src/assertion.test.ts",
|
|
14429
|
+
expectedCount: 1,
|
|
14430
|
+
public: true
|
|
14431
|
+
},
|
|
14432
|
+
{
|
|
14433
|
+
id: "captured-matcher",
|
|
14434
|
+
scenarioId: "matcher-contract",
|
|
14435
|
+
title: "A throwing matcher observes the inner assertion contract",
|
|
14436
|
+
outcome: "no-match",
|
|
14437
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect(() => expect(true).toBeTruthy()).not.toThrow();" }],
|
|
14438
|
+
focusPath: "src/assertion.test.ts",
|
|
14439
|
+
expectedCount: 0,
|
|
14440
|
+
public: true
|
|
14441
|
+
},
|
|
14442
|
+
{
|
|
14443
|
+
id: "unrelated-custom-matcher",
|
|
14444
|
+
scenarioId: "custom-matcher",
|
|
14445
|
+
title: "Adding a different matcher preserves the built-in proof",
|
|
14446
|
+
outcome: "match",
|
|
14447
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect.extend({toBeReady: custom}); expect(true).toBe(true);" }],
|
|
14448
|
+
focusPath: "src/assertion.test.ts",
|
|
14449
|
+
expectedCount: 1,
|
|
14450
|
+
public: true
|
|
14451
|
+
},
|
|
14452
|
+
{
|
|
14453
|
+
id: "replaced-custom-matcher",
|
|
14454
|
+
scenarioId: "custom-matcher",
|
|
14455
|
+
title: "A replaced matcher can reject a literal comparison",
|
|
14456
|
+
outcome: "no-match",
|
|
14457
|
+
files: [{ path: "src/assertion.test.ts", source: "import {expect} from 'vitest'; expect.extend({toBe: () => ({pass: false, message: () => 'custom contract'})}); expect(true).toBe(true);" }],
|
|
14458
|
+
focusPath: "src/assertion.test.ts",
|
|
14459
|
+
expectedCount: 0,
|
|
14460
|
+
public: true
|
|
14400
14461
|
}
|
|
14401
14462
|
]
|
|
14402
14463
|
};
|
|
@@ -14411,6 +14472,7 @@ var ZERO_ARG_MATCHERS = /* @__PURE__ */ new Set([
|
|
|
14411
14472
|
]);
|
|
14412
14473
|
var OPERAND_PREVIEW_CHARS = 40;
|
|
14413
14474
|
var NUMERIC_SIGNS = /* @__PURE__ */ new Set(["-", "+"]);
|
|
14475
|
+
var THROW_MATCHERS = /* @__PURE__ */ new Set(["toThrow", "toThrowError", "toThrowErrorMatchingSnapshot", "toThrowErrorMatchingInlineSnapshot"]);
|
|
14414
14476
|
function resolveLiteral(node, sourceCode, seen = /* @__PURE__ */ new Set()) {
|
|
14415
14477
|
node = unwrapExpression(node);
|
|
14416
14478
|
if (isLiteral(node)) return node;
|
|
@@ -14486,12 +14548,67 @@ function expectOperand(callee, sourceCode) {
|
|
|
14486
14548
|
const negated = receiver.type === AST_NODE_TYPES65.MemberExpression && ASTUtils40.getPropertyName(receiver) === "not";
|
|
14487
14549
|
if (negated && receiver.type === AST_NODE_TYPES65.MemberExpression) receiver = unwrapExpression(receiver.object);
|
|
14488
14550
|
const expectCallee = receiver.type === AST_NODE_TYPES65.CallExpression ? unwrapExpression(receiver.callee) : null;
|
|
14489
|
-
if (receiver.type !== AST_NODE_TYPES65.CallExpression || expectCallee?.type !== AST_NODE_TYPES65.Identifier || runtimeTestFrameworkName(sourceCode, expectCallee, "assertion") !== "expect" || receiver.arguments.length !== 1) {
|
|
14551
|
+
if (receiver.type !== AST_NODE_TYPES65.CallExpression || expectCallee?.type !== AST_NODE_TYPES65.Identifier || runtimeTestFrameworkName(sourceCode, expectCallee, "assertion") !== "expect" || receiver.arguments.length !== 1 && !hasStaticMessage(receiver, expectCallee, sourceCode)) {
|
|
14490
14552
|
return null;
|
|
14491
14553
|
}
|
|
14492
14554
|
const operand = receiver.arguments[0];
|
|
14493
14555
|
return operand === void 0 ? null : { operand, negated };
|
|
14494
14556
|
}
|
|
14557
|
+
function hasStaticMessage(call, callee, source) {
|
|
14558
|
+
if (call.arguments.length !== 2) return false;
|
|
14559
|
+
const definition = ASTUtils40.findVariable(source.getScope(callee), callee.name)?.defs[0];
|
|
14560
|
+
if (definition?.type !== "ImportBinding" || definition.parent.type !== AST_NODE_TYPES65.ImportDeclaration || !["vitest", "@playwright/test"].includes(definition.parent.source.value)) return false;
|
|
14561
|
+
const message = call.arguments[1];
|
|
14562
|
+
const literal2 = message === void 0 ? null : resolveLiteral(message, source);
|
|
14563
|
+
return literal2?.type === AST_NODE_TYPES65.Literal && typeof literal2.value === "string" || literal2?.type === AST_NODE_TYPES65.TemplateLiteral;
|
|
14564
|
+
}
|
|
14565
|
+
function isExceptionContract(node, source) {
|
|
14566
|
+
let callback = node.parent;
|
|
14567
|
+
while (callback !== void 0 && callback.type !== AST_NODE_TYPES65.Program && callback.type !== AST_NODE_TYPES65.ArrowFunctionExpression && callback.type !== AST_NODE_TYPES65.FunctionExpression && callback.type !== AST_NODE_TYPES65.FunctionDeclaration) callback = callback.parent;
|
|
14568
|
+
if (callback === void 0 || callback.type === AST_NODE_TYPES65.Program || callback.type === AST_NODE_TYPES65.FunctionDeclaration || callback.async || callback.generator) return false;
|
|
14569
|
+
const call = directArgumentCall(callback);
|
|
14570
|
+
if (call === null || call.optional) return false;
|
|
14571
|
+
let member = outerExpression(call).parent;
|
|
14572
|
+
if (member?.type === AST_NODE_TYPES65.MemberExpression && ASTUtils40.getPropertyName(member) === "not") member = outerExpression(member).parent;
|
|
14573
|
+
if (member?.type !== AST_NODE_TYPES65.MemberExpression || member.optional || !THROW_MATCHERS.has(ASTUtils40.getPropertyName(member) ?? "")) return false;
|
|
14574
|
+
const invocation = outerExpression(member).parent;
|
|
14575
|
+
return invocation?.type === AST_NODE_TYPES65.CallExpression && !invocation.optional && unwrapExpression(expectOperand(member, source)?.operand ?? member) === callback;
|
|
14576
|
+
}
|
|
14577
|
+
function customizedMatchers(source) {
|
|
14578
|
+
const customized = /* @__PURE__ */ new Set();
|
|
14579
|
+
const visit = (node) => {
|
|
14580
|
+
if (node.type === AST_NODE_TYPES65.CallExpression) collectCustomization(node, source, customized);
|
|
14581
|
+
for (const key of source.visitorKeys[node.type] ?? []) {
|
|
14582
|
+
const child = node[key];
|
|
14583
|
+
for (const value of Array.isArray(child) ? child : [child]) {
|
|
14584
|
+
if (typeof value === "object" && value !== null && "type" in value) visit(value);
|
|
14585
|
+
}
|
|
14586
|
+
}
|
|
14587
|
+
};
|
|
14588
|
+
visit(source.ast);
|
|
14589
|
+
return customized;
|
|
14590
|
+
}
|
|
14591
|
+
function collectCustomization(node, source, customized) {
|
|
14592
|
+
const member = unwrapExpression(node.callee);
|
|
14593
|
+
if (member.type !== AST_NODE_TYPES65.MemberExpression) return;
|
|
14594
|
+
const receiver = unwrapExpression(member.object);
|
|
14595
|
+
if (receiver.type !== AST_NODE_TYPES65.Identifier || runtimeTestFrameworkName(source, receiver, "assertion") !== "expect") return;
|
|
14596
|
+
const method = ASTUtils40.getPropertyName(member);
|
|
14597
|
+
const argument = node.arguments[0] === void 0 ? null : unwrapExpression(node.arguments[0]);
|
|
14598
|
+
if (method === "addEqualityTesters" && !(argument?.type === AST_NODE_TYPES65.ArrayExpression && argument.elements.length === 0)) {
|
|
14599
|
+
customized.add("toEqual");
|
|
14600
|
+
customized.add("toStrictEqual");
|
|
14601
|
+
}
|
|
14602
|
+
if (method !== "extend") return;
|
|
14603
|
+
if (argument?.type !== AST_NODE_TYPES65.ObjectExpression) {
|
|
14604
|
+
customized.add("*");
|
|
14605
|
+
return;
|
|
14606
|
+
}
|
|
14607
|
+
for (const property of argument.properties) {
|
|
14608
|
+
const name = property.type === AST_NODE_TYPES65.Property ? ASTUtils40.getPropertyName(property) : null;
|
|
14609
|
+
customized.add(name ?? "*");
|
|
14610
|
+
}
|
|
14611
|
+
}
|
|
14495
14612
|
var no_tautological_expect_default = createRule({
|
|
14496
14613
|
name: "no-tautological-expect",
|
|
14497
14614
|
documentation: NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION,
|
|
@@ -14511,6 +14628,7 @@ var no_tautological_expect_default = createRule({
|
|
|
14511
14628
|
if (!isTestFile(context.filename)) {
|
|
14512
14629
|
return {};
|
|
14513
14630
|
}
|
|
14631
|
+
const customized = customizedMatchers(context.sourceCode);
|
|
14514
14632
|
const preview2 = (node) => {
|
|
14515
14633
|
const text = context.sourceCode.getText(node).replaceAll(/\s+/gu, " ");
|
|
14516
14634
|
return text.length > OPERAND_PREVIEW_CHARS ? `${text.slice(0, OPERAND_PREVIEW_CHARS)}\u2026` : text;
|
|
@@ -14520,12 +14638,12 @@ var no_tautological_expect_default = createRule({
|
|
|
14520
14638
|
const callee = unwrapExpression(node.callee);
|
|
14521
14639
|
if (callee.type !== AST_NODE_TYPES65.MemberExpression) return;
|
|
14522
14640
|
const matcher = ASTUtils40.getPropertyName(callee);
|
|
14523
|
-
if (matcher === null) return;
|
|
14641
|
+
if (matcher === null || !ZERO_ARG_MATCHERS.has(matcher) && !EQUALITY_MATCHERS.has(matcher) || customized.has("*") || customized.has(matcher)) return;
|
|
14524
14642
|
const assertion = expectOperand(callee, context.sourceCode);
|
|
14525
14643
|
if (assertion === null) return;
|
|
14526
14644
|
const writtenOperand = assertion.operand;
|
|
14527
14645
|
const operand = resolveLiteral(writtenOperand, context.sourceCode);
|
|
14528
|
-
if (operand === null) {
|
|
14646
|
+
if (operand === null || isExceptionContract(node, context.sourceCode)) {
|
|
14529
14647
|
return;
|
|
14530
14648
|
}
|
|
14531
14649
|
const writtenMatcher = `${assertion.negated ? "not." : ""}${matcher}`;
|
|
@@ -26035,7 +26153,7 @@ function classMethodCounts(body2, methods) {
|
|
|
26035
26153
|
|
|
26036
26154
|
// src/rules/source-coupled-test.ts
|
|
26037
26155
|
import { AST_NODE_TYPES as AST_NODE_TYPES116, ASTUtils as ASTUtils75 } from "@typescript-eslint/utils";
|
|
26038
|
-
var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc
|
|
26156
|
+
var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc|toml|py|[cm]?[jt]s)$|(?<!\.(?:tf|tftest))\.json$/iu;
|
|
26039
26157
|
var FS_MODULES = /* @__PURE__ */ new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
|
|
26040
26158
|
var FS_READERS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
|
|
26041
26159
|
var TEXT_TRANSFORMS = /* @__PURE__ */ new Set([
|