@sarj/eslint-plugin 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -343,6 +343,9 @@ declare const rules: {
343
343
  "trailing-value-narration": _typescript_eslint_utils_ts_eslint.RuleModule<"narratesValue", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
344
344
  name: string;
345
345
  };
346
+ "no-tautological-expect": _typescript_eslint_utils_ts_eslint.RuleModule<"tautologicalComparison" | "tautologicalMatcher", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
347
+ name: string;
348
+ };
346
349
  };
347
350
  declare const plugin: {
348
351
  meta: {
@@ -496,6 +499,9 @@ declare const plugin: {
496
499
  "trailing-value-narration": _typescript_eslint_utils_ts_eslint.RuleModule<"narratesValue", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
497
500
  name: string;
498
501
  };
502
+ "no-tautological-expect": _typescript_eslint_utils_ts_eslint.RuleModule<"tautologicalComparison" | "tautologicalMatcher", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
503
+ name: string;
504
+ };
499
505
  };
500
506
  configs: {
501
507
  recommended: {
@@ -542,6 +548,7 @@ declare const plugin: {
542
548
  "@sarj/no-restated-comment": string;
543
549
  "@sarj/jsdoc-restates-signature": string;
544
550
  "@sarj/trailing-value-narration": string;
551
+ "@sarj/no-tautological-expect": string;
545
552
  };
546
553
  };
547
554
  strict: {
@@ -593,6 +600,7 @@ declare const plugin: {
593
600
  "@sarj/no-restated-comment": string;
594
601
  "@sarj/jsdoc-restates-signature": string;
595
602
  "@sarj/trailing-value-narration": string;
603
+ "@sarj/no-tautological-expect": string;
596
604
  };
597
605
  };
598
606
  };
package/dist/index.d.ts CHANGED
@@ -343,6 +343,9 @@ declare const rules: {
343
343
  "trailing-value-narration": _typescript_eslint_utils_ts_eslint.RuleModule<"narratesValue", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
344
344
  name: string;
345
345
  };
346
+ "no-tautological-expect": _typescript_eslint_utils_ts_eslint.RuleModule<"tautologicalComparison" | "tautologicalMatcher", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
347
+ name: string;
348
+ };
346
349
  };
347
350
  declare const plugin: {
348
351
  meta: {
@@ -496,6 +499,9 @@ declare const plugin: {
496
499
  "trailing-value-narration": _typescript_eslint_utils_ts_eslint.RuleModule<"narratesValue", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
497
500
  name: string;
498
501
  };
502
+ "no-tautological-expect": _typescript_eslint_utils_ts_eslint.RuleModule<"tautologicalComparison" | "tautologicalMatcher", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
503
+ name: string;
504
+ };
499
505
  };
500
506
  configs: {
501
507
  recommended: {
@@ -542,6 +548,7 @@ declare const plugin: {
542
548
  "@sarj/no-restated-comment": string;
543
549
  "@sarj/jsdoc-restates-signature": string;
544
550
  "@sarj/trailing-value-narration": string;
551
+ "@sarj/no-tautological-expect": string;
545
552
  };
546
553
  };
547
554
  strict: {
@@ -593,6 +600,7 @@ declare const plugin: {
593
600
  "@sarj/no-restated-comment": string;
594
601
  "@sarj/jsdoc-restates-signature": string;
595
602
  "@sarj/trailing-value-narration": string;
603
+ "@sarj/no-tautological-expect": string;
596
604
  };
597
605
  };
598
606
  };
package/dist/index.js CHANGED
@@ -7157,6 +7157,107 @@ var trailing_value_narration_default = ESLintUtils44.RuleCreator(
7157
7157
  }
7158
7158
  });
7159
7159
 
7160
+ // src/rules/no-tautological-expect.ts
7161
+ import { AST_NODE_TYPES as AST_NODE_TYPES32, ESLintUtils as ESLintUtils45 } from "@typescript-eslint/utils";
7162
+ var EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
7163
+ var ZERO_ARG_MATCHERS = /* @__PURE__ */ new Set([
7164
+ "toBeDefined",
7165
+ "toBeUndefined",
7166
+ "toBeNull",
7167
+ "toBeTruthy",
7168
+ "toBeFalsy",
7169
+ "toBeNaN"
7170
+ ]);
7171
+ var OPERAND_PREVIEW_CHARS = 40;
7172
+ var NUMERIC_SIGNS = /* @__PURE__ */ new Set(["-", "+"]);
7173
+ function isLiteral(node) {
7174
+ switch (node.type) {
7175
+ case AST_NODE_TYPES32.Literal:
7176
+ return true;
7177
+ case AST_NODE_TYPES32.TemplateLiteral:
7178
+ return node.expressions.length === 0;
7179
+ case AST_NODE_TYPES32.UnaryExpression:
7180
+ return NUMERIC_SIGNS.has(node.operator) && isLiteral(node.argument);
7181
+ case AST_NODE_TYPES32.ArrayExpression:
7182
+ return node.elements.every((element) => element !== null && isLiteral(element));
7183
+ case AST_NODE_TYPES32.ObjectExpression:
7184
+ return node.properties.every(
7185
+ (property) => property.type === AST_NODE_TYPES32.Property && !property.computed && isLiteral(property.value)
7186
+ );
7187
+ default:
7188
+ return false;
7189
+ }
7190
+ }
7191
+ function expectOperand(callee) {
7192
+ const receiver = callee.object;
7193
+ if (receiver.type !== AST_NODE_TYPES32.CallExpression || receiver.callee.type !== AST_NODE_TYPES32.Identifier || receiver.callee.name !== "expect" || receiver.arguments.length !== 1) {
7194
+ return null;
7195
+ }
7196
+ return receiver.arguments[0] ?? null;
7197
+ }
7198
+ var no_tautological_expect_default = ESLintUtils45.RuleCreator(
7199
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7200
+ )({
7201
+ name: "no-tautological-expect",
7202
+ meta: {
7203
+ type: "problem",
7204
+ docs: {
7205
+ description: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail."
7206
+ },
7207
+ schema: [],
7208
+ messages: {
7209
+ tautologicalComparison: "`expect({{operand}}).{{matcher}}({{operand}})` compares a literal with an identical literal \u2014 it passes even if the code under test is deleted. Assert on a value the code produced, or delete the test.",
7210
+ tautologicalMatcher: "`expect({{operand}}).{{matcher}}()` asserts on a literal, so its outcome is fixed before the code runs. Assert on a value the code produced, or delete the test."
7211
+ }
7212
+ },
7213
+ defaultOptions: [],
7214
+ create(context) {
7215
+ if (!isTestFile(context.filename)) {
7216
+ return {};
7217
+ }
7218
+ const preview2 = (node) => {
7219
+ const text = context.sourceCode.getText(node).replaceAll(/\s+/gu, " ");
7220
+ return text.length > OPERAND_PREVIEW_CHARS ? `${text.slice(0, OPERAND_PREVIEW_CHARS)}\u2026` : text;
7221
+ };
7222
+ return {
7223
+ CallExpression(node) {
7224
+ const callee = node.callee;
7225
+ if (callee.type !== AST_NODE_TYPES32.MemberExpression || callee.computed) {
7226
+ return;
7227
+ }
7228
+ if (callee.property.type !== AST_NODE_TYPES32.Identifier) {
7229
+ return;
7230
+ }
7231
+ const matcher = callee.property.name;
7232
+ const operand = expectOperand(callee);
7233
+ if (operand === null || !isLiteral(operand)) {
7234
+ return;
7235
+ }
7236
+ if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0) {
7237
+ context.report({
7238
+ node,
7239
+ messageId: "tautologicalMatcher",
7240
+ data: { operand: preview2(operand), matcher }
7241
+ });
7242
+ return;
7243
+ }
7244
+ const expected = node.arguments[0];
7245
+ if (!EQUALITY_MATCHERS.has(matcher) || node.arguments.length !== 1 || expected === void 0 || !isLiteral(expected)) {
7246
+ return;
7247
+ }
7248
+ if (context.sourceCode.getText(operand) !== context.sourceCode.getText(expected)) {
7249
+ return;
7250
+ }
7251
+ context.report({
7252
+ node,
7253
+ messageId: "tautologicalComparison",
7254
+ data: { operand: preview2(operand), matcher }
7255
+ });
7256
+ }
7257
+ };
7258
+ }
7259
+ });
7260
+
7160
7261
  // src/index.ts
7161
7262
  var rules = {
7162
7263
  "enforce-file-structure": enforce_file_structure_default,
@@ -7202,12 +7303,13 @@ var rules = {
7202
7303
  "prefer-module-level-constant": prefer_module_level_constant_default,
7203
7304
  "jsdoc-restates-signature": jsdoc_restates_signature_default,
7204
7305
  "no-restated-comment": no_restated_comment_default,
7205
- "trailing-value-narration": trailing_value_narration_default
7306
+ "trailing-value-narration": trailing_value_narration_default,
7307
+ "no-tautological-expect": no_tautological_expect_default
7206
7308
  };
7207
7309
  var plugin = {
7208
7310
  meta: {
7209
7311
  name: "@sarj/eslint-plugin",
7210
- version: "2.13.0"
7312
+ version: "2.14.0"
7211
7313
  },
7212
7314
  rules,
7213
7315
  configs: {
@@ -7274,7 +7376,15 @@ var plugin = {
7274
7376
  // wrong deletion is silent information loss.
7275
7377
  "@sarj/no-restated-comment": "warn",
7276
7378
  "@sarj/jsdoc-restates-signature": "warn",
7277
- "@sarj/trailing-value-narration": "warn"
7379
+ "@sarj/trailing-value-narration": "warn",
7380
+ // The TS half of SARJ057 (2026-07). Python has caught the
7381
+ // assertion-FREE test since 0.15.0 (SARJ043) and had no TS
7382
+ // counterpart, which is how `expect(true).toBe(true); // placeholder`
7383
+ // survived in internal-automations: the file HAS an assertion.
7384
+ // Measured across 5,819 .ts/.tsx files (1,003 of them test files) in
7385
+ // six internal repos plus got / hono / swr / trpc: 3 hits, 3 true
7386
+ // positives, 0 false positives.
7387
+ "@sarj/no-tautological-expect": "warn"
7278
7388
  }
7279
7389
  },
7280
7390
  strict: {
@@ -7342,7 +7452,9 @@ var plugin = {
7342
7452
  // for the measured hit counts and false-positive rates.
7343
7453
  "@sarj/no-restated-comment": "error",
7344
7454
  "@sarj/jsdoc-restates-signature": "error",
7345
- "@sarj/trailing-value-narration": "error"
7455
+ "@sarj/trailing-value-narration": "error",
7456
+ // TS half of SARJ057 — see the `recommended` block for the measurement.
7457
+ "@sarj/no-tautological-expect": "error"
7346
7458
  }
7347
7459
  }
7348
7460
  }