falsegreen-js 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-06-22
10
+
11
+ ### Added
12
+ - D8 (opt-in diagnostic): magic number in an assertion - a bare numeric literal as
13
+ the expected value. The most frequent smell in LLM-generated tests (2410.10628).
14
+ - JS15: inappropriate assertion - the comparison is wrapped in a boolean
15
+ (`expect(a === b).toBe(true)`), so the failure message is blind and the oracle
16
+ weak. Sourced from the xNose "Inappropriate Assertions" smell (Paul et al., 2024).
17
+
9
18
  ## [0.1.0] - 2026-06-22
10
19
 
11
20
  ### Added
@@ -36,5 +45,6 @@ All notable changes to this project are documented here. The format is based on
36
45
  - pre-commit hook (`.pre-commit-hooks.yaml`), CI matrix (Node 18/20/22), and an npm
37
46
  trusted-publishing release workflow.
38
47
 
39
- [Unreleased]: https://github.com/vinicq/falsegreen-js/compare/v0.1.0...HEAD
48
+ [Unreleased]: https://github.com/vinicq/falsegreen-js/compare/v0.2.0...HEAD
49
+ [0.2.0]: https://github.com/vinicq/falsegreen-js/compare/v0.1.0...v0.2.0
40
50
  [0.1.0]: https://github.com/vinicq/falsegreen-js/releases/tag/v0.1.0
package/README.md CHANGED
@@ -82,6 +82,7 @@ line up in the research. `JS*` codes are ecosystem-specific.
82
82
  | JS9 | high | assertion in a dead branch (`if(false)` / `if(true){}else`) — never runs |
83
83
  | JS11 | low | `try/catch` swallows the assertion — a failing `expect` is caught, test stays green |
84
84
  | JS13 | low | query (`getBy*`/`queryBy*`) as a loose statement — its result is never asserted |
85
+ | JS15 | low | inappropriate assertion — comparison wrapped in a boolean (`expect(a===b).toBe(true)`), blind failure message |
85
86
 
86
87
  Each code carries a judgment tag (J1-J6) shared with the
87
88
  [falsegreen-skill](https://github.com/vinicq/falsegreen-skill) semantic framework.
@@ -99,6 +100,7 @@ default. Enable them with `--diagnostics`, or per code via config `severity`. Th
99
100
  | D4 | diagnostic | `it.each`/`test.each` without titled cases (index-only) |
100
101
  | D6 | diagnostic | `console.*` in a test body |
101
102
  | D7 | diagnostic | anonymous test — empty or missing description |
103
+ | D8 | diagnostic | magic number — a bare numeric literal as the expected value |
102
104
  | M2 | coupling | test body exceeds the line-count threshold |
103
105
 
104
106
  ```bash
package/dist/cases.js CHANGED
@@ -38,6 +38,7 @@ export const CASES = {
38
38
  JS9: { title: "assertion in a dead branch (if(false) / if(true){}else) — it never runs", confidence: "high", judgment: "J1" },
39
39
  JS11: { title: "try/catch swallows the assertion — a failing expect is caught and the test stays green", confidence: "low", judgment: "J1" },
40
40
  JS13: { title: "query (getBy*/queryBy*/wrapper.find) as a loose statement — its result is never asserted", confidence: "low", judgment: "J4" },
41
+ JS15: { title: "inappropriate assertion — the comparison is wrapped in a boolean (expect(a===b).toBe(true)), so the failure message is blind", confidence: "low", judgment: "J4" },
41
42
  // --- diagnostic group (maintainability; default off, opt-in via --diagnostics
42
43
  // or config severity). These are NOT false-green: the test still protects. They
43
44
  // are a "plus" for test-code health, mirroring falsegreen's D/M group. -------
@@ -46,6 +47,7 @@ export const CASES = {
46
47
  D4: { title: "it.each/test.each without titled cases — a failing case is identified only by its index", confidence: "off", judgment: "J4" },
47
48
  D6: { title: "console.* in a test body — a debug artifact that bypasses the oracle", confidence: "off", judgment: "J4" },
48
49
  D7: { title: "anonymous test — empty or missing description", confidence: "off", judgment: "J4" },
50
+ D8: { title: "magic number in an assertion — a bare numeric literal instead of a named constant", confidence: "off", judgment: "J4" },
49
51
  M2: { title: "test body exceeds the line-count threshold — hard to read and maintain", confidence: "off", judgment: "J5" },
50
52
  };
51
53
  /** Default thresholds for the diagnostic group (overridable later via config). */
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { JUDGMENTS, groupOf } from "./cases.js";
3
3
  import { scanPaths, scanFile, stagedFiles, loadConfig, } from "./scan.js";
4
- const VERSION = "0.1.0";
4
+ const VERSION = "0.2.0";
5
5
  const TOOL_URI = "https://github.com/vinicq/falsegreen-js";
6
6
  const HELP = `falsegreen-js ${VERSION} - find false-positive JS/TS tests (static AST scan)
7
7
 
package/dist/rules.js CHANGED
@@ -377,6 +377,32 @@ export function analyze(sf) {
377
377
  if ((chain.matcher === "toThrow" || chain.matcher === "toThrowError") && chain.args.length === 0) {
378
378
  push(lineOf(sf, node), "C9", "toThrow() with no error type or message accepts any error");
379
379
  }
380
+ // JS15 inappropriate assertion: the comparison is wrapped in a boolean, so
381
+ // the matcher only sees true/false (expect(a === b).toBe(true)). The failure
382
+ // message is blind ("expected false to be true") and the oracle is weak.
383
+ const COMPARISON_OPS = new Set([
384
+ ts.SyntaxKind.EqualsEqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsEqualsToken,
385
+ ts.SyntaxKind.EqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsToken,
386
+ ts.SyntaxKind.LessThanToken, ts.SyntaxKind.GreaterThanToken,
387
+ ts.SyntaxKind.LessThanEqualsToken, ts.SyntaxKind.GreaterThanEqualsToken,
388
+ ]);
389
+ const subjIsComparison = subj !== undefined && ts.isBinaryExpression(subj) &&
390
+ COMPARISON_OPS.has(subj.operatorToken.kind);
391
+ const boolMatcher = ((chain.matcher === "toBe" || chain.matcher === "toEqual" || chain.matcher === "toStrictEqual") &&
392
+ arg !== undefined && (arg.kind === ts.SyntaxKind.TrueKeyword || arg.kind === ts.SyntaxKind.FalseKeyword)) ||
393
+ chain.matcher === "toBeTruthy" || chain.matcher === "toBeFalsy";
394
+ if (subjIsComparison && boolMatcher) {
395
+ push(lineOf(sf, node), "JS15", "comparison wrapped in a boolean; assert the values directly");
396
+ }
397
+ // D8 (diagnostic, opt-in): a magic integer literal as the expected value.
398
+ // Floats are C8's concern; D8 covers bare integers abs > 1.
399
+ if ((chain.matcher === "toBe" || chain.matcher === "toEqual" || chain.matcher === "toStrictEqual") &&
400
+ arg && ts.isNumericLiteral(arg)) {
401
+ const v = Number(arg.text);
402
+ if (Number.isInteger(v) && Math.abs(v) > 1) {
403
+ push(lineOf(sf, node), "D8", `magic number ${arg.text} in the assertion`);
404
+ }
405
+ }
380
406
  // C5 always-true
381
407
  if (chain.matcher === "toBeTruthy" && literalTruthiness(subj) === true) {
382
408
  push(lineOf(sf, node), "C5", "toBeTruthy on a constant truthy literal");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "falsegreen-js",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Find JavaScript/TypeScript unit tests that give false positives: green tests that protect nothing, and tests that pass while asserting the wrong thing. Deterministic AST scanner, sibling of falsegreen (Python).",
5
5
  "keywords": [
6
6
  "jest", "vitest", "mocha", "testing", "test-smells", "false-positive",