@eviano/tribunal 0.1.2
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/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/analyzers/assertionFreeTest.d.ts +12 -0
- package/dist/analyzers/assertionFreeTest.js +316 -0
- package/dist/analyzers/assertionFreeTest.js.map +1 -0
- package/dist/analyzers/claimReconciliation.d.ts +3 -0
- package/dist/analyzers/claimReconciliation.js +190 -0
- package/dist/analyzers/claimReconciliation.js.map +1 -0
- package/dist/analyzers/defaults.d.ts +8 -0
- package/dist/analyzers/defaults.js +82 -0
- package/dist/analyzers/defaults.js.map +1 -0
- package/dist/analyzers/exports.d.ts +12 -0
- package/dist/analyzers/exports.js +84 -0
- package/dist/analyzers/exports.js.map +1 -0
- package/dist/analyzers/hallucinatedSymbol.d.ts +22 -0
- package/dist/analyzers/hallucinatedSymbol.js +233 -0
- package/dist/analyzers/hallucinatedSymbol.js.map +1 -0
- package/dist/analyzers/index.d.ts +9 -0
- package/dist/analyzers/index.js +9 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/claims.d.ts +15 -0
- package/dist/claims.js +32 -0
- package/dist/claims.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff/gitDiff.d.ts +19 -0
- package/dist/diff/gitDiff.js +64 -0
- package/dist/diff/gitDiff.js.map +1 -0
- package/dist/diff/parseUnifiedDiff.d.ts +9 -0
- package/dist/diff/parseUnifiedDiff.js +63 -0
- package/dist/diff/parseUnifiedDiff.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/report/render.d.ts +10 -0
- package/dist/report/render.js +70 -0
- package/dist/report/render.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tribunal contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# ⚖️ Tribunal
|
|
2
|
+
|
|
3
|
+
**A deterministic, no-LLM-in-the-loop CI gate that catches the defects coding agents ship** — tests
|
|
4
|
+
that assert nothing, references to symbols that don't exist, and PR claims that contradict what the
|
|
5
|
+
diff actually did.
|
|
6
|
+
|
|
7
|
+
Agents now write both the change *and* a persuasive PR description optimized to sound done. Reviewers
|
|
8
|
+
anchor on the prose and rubber-stamp. Tribunal is the deterministic safety net for that failure mode —
|
|
9
|
+
not another stochastic reviewer that can be prompt-injected or hallucinate an approval.
|
|
10
|
+
|
|
11
|
+
> **Status: pre-alpha (M0).** The first analyzer — `assertion-free-test` — and the full
|
|
12
|
+
> diff → verdict → exit-code pipeline are working and tested. See [docs/SPEC.md](docs/SPEC.md) for the
|
|
13
|
+
> architecture and roadmap.
|
|
14
|
+
|
|
15
|
+
## The Trust Contract
|
|
16
|
+
|
|
17
|
+
Tribunal is only useful if a paranoid platform team will let it block a build. That requires hard
|
|
18
|
+
guarantees, not vibes:
|
|
19
|
+
|
|
20
|
+
1. **No LLM is ever in the verification path.** The thing that flips a build red is always
|
|
21
|
+
deterministic code. (A model may *propose* a claim to check; it may never *adjudicate* one.)
|
|
22
|
+
2. **Three verdicts:** 🟢 `PASS` · 🟡 `UNVERIFIED` · 🔴 `CONTRADICTED`.
|
|
23
|
+
3. **The build is gated ONLY on `CONTRADICTED`,** and only with `--hard-fail`. Absence of a
|
|
24
|
+
contradiction never blocks; `UNVERIFIED` never blocks.
|
|
25
|
+
4. **`CONTRADICTED` is a syntactic certainty, never a guess.** If a contradiction can't be *proven*
|
|
26
|
+
from the AST, the verdict degrades to `UNVERIFIED` — never a false red. We would rather miss a real
|
|
27
|
+
defect than block a correct PR.
|
|
28
|
+
5. **Reporter-first.** Default mode is a non-blocking PR comment. Teams earn trust in the signal before
|
|
29
|
+
it can break their build.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install
|
|
35
|
+
npm test # run the suite
|
|
36
|
+
npm run check -- check --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run it against your working changes (report-only):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run check -- check # diff working tree vs HEAD
|
|
43
|
+
npm run check -- check --base main --head HEAD
|
|
44
|
+
npm run check -- check --diff some.patch # analyze a unified diff file
|
|
45
|
+
npm run check -- check --format json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Make it block a build (opt-in, gates only on 🔴 CONTRADICTED):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run check -- check --base main --head HEAD --hard-fail
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Use it in CI (GitHub Action)
|
|
55
|
+
|
|
56
|
+
Tribunal ships a reusable composite action that runs on a PR, posts a **sticky comment** with the
|
|
57
|
+
report, and (optionally) fails the build — gating **only** on 🔴 CONTRADICTED.
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
# .github/workflows/tribunal.yml
|
|
61
|
+
name: Tribunal
|
|
62
|
+
on: pull_request
|
|
63
|
+
permissions:
|
|
64
|
+
contents: read
|
|
65
|
+
pull-requests: write
|
|
66
|
+
jobs:
|
|
67
|
+
tribunal:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
with: { fetch-depth: 0 }
|
|
72
|
+
- uses: eviano/tribunal@v1
|
|
73
|
+
with:
|
|
74
|
+
base: ${{ github.event.pull_request.base.sha }}
|
|
75
|
+
head: ${{ github.event.pull_request.head.sha }}
|
|
76
|
+
hard-fail: 'false' # start in report-only; flip to 'true' once you trust the signal
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This repo dogfoods its own action — see [.github/workflows/tribunal.yml](.github/workflows/tribunal.yml).
|
|
80
|
+
|
|
81
|
+
## What M0 catches: `assertion-free-test`
|
|
82
|
+
|
|
83
|
+
A test the PR added or changed that **can never fail** because it asserts nothing:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
it('validates the input', () => {
|
|
87
|
+
const result = validate(payload); // 🔴 CONTRADICTED: no assertion — this test can never fail
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- 🟢 **PASS** — an assertion is reachable (directly, or via a local helper Tribunal can follow).
|
|
92
|
+
- 🟡 **UNVERIFIED** — no assertion found, but the test calls an external helper that *might* assert.
|
|
93
|
+
Loud, never blocking.
|
|
94
|
+
- 🔴 **CONTRADICTED** — a syntactic certainty: the body is empty, has no calls at all, or only calls
|
|
95
|
+
local helpers that themselves cannot assert.
|
|
96
|
+
|
|
97
|
+
It understands `expect`, `assert`/`node:assert` (incl. named imports like `strictEqual`), chai
|
|
98
|
+
(`.should`, `chai.expect`), `sinon.assert.*`, Deno std `assert*`, and AVA/`node:test` context
|
|
99
|
+
assertions (`t.is`, `t.deepEqual`, …). Skipped tests (`it.skip`, `it.todo`) are ignored. Only tests
|
|
100
|
+
**touched by the diff** are evaluated.
|
|
101
|
+
|
|
102
|
+
## What M1 adds: `hallucinated-symbol`
|
|
103
|
+
|
|
104
|
+
Imports the PR added that reference something that doesn't exist:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { parseConifg } from './config'; // 🔴 CONTRADICTED: './config' has no export named 'parseConifg'
|
|
108
|
+
import { helper } from './utils/missing'; // 🔴 CONTRADICTED: path resolves to no file
|
|
109
|
+
import { Client } from 'some-sdk'; // 🟡 UNVERIFIED: package not installed (dependency check is separate)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
It resolves modules the way the target repo's own `tsconfig.json` does, follows local `export *`
|
|
113
|
+
re-exports, and degrades to 🟡 UNVERIFIED whenever exports can't be statically enumerated (CJS,
|
|
114
|
+
unresolvable re-exports). Default imports are never flagged (interop synthesizes a default). Scope for
|
|
115
|
+
M1 is imports; full identifier/call resolution is a later increment.
|
|
116
|
+
|
|
117
|
+
## What M3 adds: `claim-reconciliation`
|
|
118
|
+
|
|
119
|
+
Verifies the agent's **own claims** against the diff — deterministically. Claims are declared in a
|
|
120
|
+
machine-readable block (parsing, never NLU), so an unrecognized claim degrades to 🟡 UNVERIFIED and can
|
|
121
|
+
never become a false 🔴 CONTRADICTED:
|
|
122
|
+
|
|
123
|
+
````md
|
|
124
|
+
```tribunal
|
|
125
|
+
added-test
|
|
126
|
+
no-public-api-change
|
|
127
|
+
```
|
|
128
|
+
````
|
|
129
|
+
|
|
130
|
+
- `added-test` → 🟢 PASS if the diff adds a test with a reachable assertion; 🔴 CONTRADICTED if it adds
|
|
131
|
+
no test at all; 🟡 UNVERIFIED if a test was added but its assertion can't be detected.
|
|
132
|
+
- `no-public-api-change` → compares the exported-symbol set between base and head; 🔴 CONTRADICTED on any
|
|
133
|
+
added/removed export, 🟡 UNVERIFIED when there's no base ref or a module re-exports via `export *`.
|
|
134
|
+
- `no-default-flip` → compares literal default parameter values between base and head (scope to one with
|
|
135
|
+
`no-default-flip: paramName`); 🔴 CONTRADICTED when a default silently changed.
|
|
136
|
+
|
|
137
|
+
Pass claims with `--claims <file>` (a claims file) or `--pr-body <file>` (reads only the fenced block).
|
|
138
|
+
Adding a new claim is one entry in the verifier registry.
|
|
139
|
+
|
|
140
|
+
## Benchmark — the moat-proof
|
|
141
|
+
|
|
142
|
+
Tribunal is only safe as a hard-fail gate if it almost never false-fires. That's measured, not
|
|
143
|
+
asserted:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm run bench
|
|
147
|
+
# cases=16 TP=9 TN=7 FP=0 FN=0
|
|
148
|
+
# recall=100.0% precision=100.0% false-positive=0.0%
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The [`bench/`](bench/) corpus is deliberately adversarial on the clean side (big refactors carrying a
|
|
152
|
+
`no-public-api-change` claim, tests that assert through helpers) so the **false-positive rate** means
|
|
153
|
+
something. It runs in CI via `test/bench.test.ts` and is the regression guard for the Trust Contract.
|
|
154
|
+
The public MSR'26 PR-MCI labeled set (974 PRs) plugs in alongside the seed corpus — see
|
|
155
|
+
[bench/README.md](bench/README.md).
|
|
156
|
+
|
|
157
|
+
## Roadmap
|
|
158
|
+
|
|
159
|
+
| Milestone | Scope |
|
|
160
|
+
|-----------|-------|
|
|
161
|
+
| **M0** ✅ | scaffold + `assertion-free-test` + pipeline + tests |
|
|
162
|
+
| **M1** ✅ | `hallucinated-symbol` — import resolution (nonexistent named exports & relative paths) |
|
|
163
|
+
| **M2** ✅ | PR-comment reporter as a GitHub Action |
|
|
164
|
+
| **M3** ✅ | claim-reconciliation: `added-test`, `no-public-api-change`, `no-default-flip` (pluggable registry) |
|
|
165
|
+
| **M4** ✅ | benchmark harness + adversarial seed corpus + CI guard (currently **0% false-positive**); MSR'26 PR-MCI set pluggable |
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import type { Analyzer, Finding } from '../types.js';
|
|
3
|
+
declare function isTestFile(path: string): boolean;
|
|
4
|
+
declare function scriptKindFor(path: string): ts.ScriptKind;
|
|
5
|
+
declare function analyzeFile(sf: ts.SourceFile, path: string, addedLines: Set<number>): Finding[];
|
|
6
|
+
export declare const assertionFreeTest: Analyzer;
|
|
7
|
+
export declare const __test__: {
|
|
8
|
+
analyzeFile: typeof analyzeFile;
|
|
9
|
+
isTestFile: typeof isTestFile;
|
|
10
|
+
scriptKindFor: typeof scriptKindFor;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* `assertion-free-test` — flags tests the PR added/changed that contain NO assertion and therefore can
|
|
4
|
+
* never fail. This is a marquee agent defect: "I added tests" where the test does setup and asserts
|
|
5
|
+
* nothing.
|
|
6
|
+
*
|
|
7
|
+
* Trust Contract (docs/SPEC.md §3):
|
|
8
|
+
* - PASS : an assertion is reachable from the test body (directly or via a resolvable local helper).
|
|
9
|
+
* - CONTRADICTED : a SYNTACTIC CERTAINTY that no assertion can occur — the body is empty, or it contains
|
|
10
|
+
* zero function calls, or every call resolves to a local helper that also cannot assert.
|
|
11
|
+
* - UNVERIFIED : no assertion found, but the body calls something external/unresolvable that COULD
|
|
12
|
+
* assert indirectly. We never guess PASS and never guess CONTRADICTED here.
|
|
13
|
+
*/
|
|
14
|
+
const TEST_FILE_RE = /(^|[./\\])(test|spec)\.[cm]?[jt]sx?$/i;
|
|
15
|
+
const TEST_DIR_RE = /(^|[/\\])(__tests__|tests?)[/\\]/i;
|
|
16
|
+
const SOURCE_EXT_RE = /\.[cm]?[jt]sx?$/i;
|
|
17
|
+
function isTestFile(path) {
|
|
18
|
+
return TEST_FILE_RE.test(path) || (TEST_DIR_RE.test(path) && SOURCE_EXT_RE.test(path));
|
|
19
|
+
}
|
|
20
|
+
/** Test-defining call roots. `Deno` covers `Deno.test(...)`. */
|
|
21
|
+
const TEST_ROOTS = new Set(['it', 'test', 'fit', 'Deno']);
|
|
22
|
+
/** Modifiers that mean the test does not run, so its lack of assertions is not a defect. */
|
|
23
|
+
const SKIP_MODIFIERS = new Set(['skip', 'todo']);
|
|
24
|
+
/** AVA / node:test context-object assertion methods (`t.is`, `t.deepEqual`, ...). */
|
|
25
|
+
const CTX_ASSERTIONS = new Set([
|
|
26
|
+
'is', 'not', 'deepEqual', 'notDeepEqual', 'like', 'true', 'false', 'truthy', 'falsy',
|
|
27
|
+
'assert', 'pass', 'fail', 'throws', 'throwsAsync', 'notThrows', 'notThrowsAsync',
|
|
28
|
+
'regex', 'notRegex', 'snapshot',
|
|
29
|
+
]);
|
|
30
|
+
/** Walk a callee chain to its root identifier, noting any `.skip`/`.todo` modifier on the way. */
|
|
31
|
+
function calleeInfo(expr) {
|
|
32
|
+
let hasSkip = false;
|
|
33
|
+
let node = expr;
|
|
34
|
+
while (true) {
|
|
35
|
+
if (ts.isCallExpression(node)) {
|
|
36
|
+
node = node.expression;
|
|
37
|
+
}
|
|
38
|
+
else if (ts.isPropertyAccessExpression(node)) {
|
|
39
|
+
if (SKIP_MODIFIERS.has(node.name.text))
|
|
40
|
+
hasSkip = true;
|
|
41
|
+
node = node.expression;
|
|
42
|
+
}
|
|
43
|
+
else if (ts.isElementAccessExpression(node)) {
|
|
44
|
+
node = node.expression;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { root: ts.isIdentifier(node) ? node.text : undefined, hasSkip };
|
|
51
|
+
}
|
|
52
|
+
/** Find the function/arrow argument that is the test body (the last function-like argument). */
|
|
53
|
+
function testBodyFn(call) {
|
|
54
|
+
for (let i = call.arguments.length - 1; i >= 0; i--) {
|
|
55
|
+
const a = call.arguments[i];
|
|
56
|
+
if (ts.isArrowFunction(a) || ts.isFunctionExpression(a))
|
|
57
|
+
return a;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/** First string-literal argument, used as the human-readable test name. */
|
|
62
|
+
function testName(call) {
|
|
63
|
+
const a = call.arguments[0];
|
|
64
|
+
if (a && (ts.isStringLiteral(a) || ts.isNoSubstitutionTemplateLiteral(a)))
|
|
65
|
+
return a.text;
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Build the per-file assertion vocabulary from imports plus globals (`expect`, `assert`). */
|
|
69
|
+
function collectVocab(sf) {
|
|
70
|
+
const ids = new Set(['expect', 'assert']);
|
|
71
|
+
const memberRoots = new Set(['assert', 'sinon', 'chai', 'expect']);
|
|
72
|
+
sf.forEachChild((node) => {
|
|
73
|
+
if (!ts.isImportDeclaration(node) || !node.importClause)
|
|
74
|
+
return;
|
|
75
|
+
if (!ts.isStringLiteral(node.moduleSpecifier))
|
|
76
|
+
return;
|
|
77
|
+
const mod = node.moduleSpecifier.text;
|
|
78
|
+
const fromAssertModule = /assert/i.test(mod);
|
|
79
|
+
const considerBinding = (name) => {
|
|
80
|
+
if (name === 'expect' || name === 'should' || /^assert/i.test(name) || fromAssertModule) {
|
|
81
|
+
ids.add(name);
|
|
82
|
+
}
|
|
83
|
+
if (name === 'chai' || name === 'sinon' || name === 'expect' || name === 'assert') {
|
|
84
|
+
memberRoots.add(name);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const clause = node.importClause;
|
|
88
|
+
if (clause.name)
|
|
89
|
+
considerBinding(clause.name.text); // default import
|
|
90
|
+
const nb = clause.namedBindings;
|
|
91
|
+
if (nb) {
|
|
92
|
+
if (ts.isNamespaceImport(nb)) {
|
|
93
|
+
memberRoots.add(nb.name.text); // import * as assert / chai
|
|
94
|
+
}
|
|
95
|
+
else if (ts.isNamedImports(nb)) {
|
|
96
|
+
nb.elements.forEach((e) => considerBinding(e.name.text));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return { ids, memberRoots };
|
|
101
|
+
}
|
|
102
|
+
/** Leftmost root identifier of a (possibly chained) expression. */
|
|
103
|
+
function rootIdentifier(expr) {
|
|
104
|
+
let node = expr;
|
|
105
|
+
while (ts.isPropertyAccessExpression(node) ||
|
|
106
|
+
ts.isElementAccessExpression(node) ||
|
|
107
|
+
ts.isCallExpression(node) ||
|
|
108
|
+
ts.isNonNullExpression(node)) {
|
|
109
|
+
node = node.expression;
|
|
110
|
+
}
|
|
111
|
+
return ts.isIdentifier(node) ? node : undefined;
|
|
112
|
+
}
|
|
113
|
+
function isAssertionCallee(callee, vocab, ctxParam) {
|
|
114
|
+
if (ts.isIdentifier(callee))
|
|
115
|
+
return vocab.ids.has(callee.text);
|
|
116
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
117
|
+
const root = rootIdentifier(callee);
|
|
118
|
+
if (root) {
|
|
119
|
+
if (vocab.memberRoots.has(root.text))
|
|
120
|
+
return true;
|
|
121
|
+
if (ctxParam && root.text === ctxParam && CTX_ASSERTIONS.has(callee.name.text))
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
/** Local module-scope helpers: name → body node, so we can follow `helper()` calls one or more levels. */
|
|
128
|
+
function collectHelpers(sf) {
|
|
129
|
+
const helpers = new Map();
|
|
130
|
+
sf.forEachChild((node) => {
|
|
131
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.body) {
|
|
132
|
+
helpers.set(node.name.text, node.body);
|
|
133
|
+
}
|
|
134
|
+
else if (ts.isVariableStatement(node)) {
|
|
135
|
+
for (const d of node.declarationList.declarations) {
|
|
136
|
+
if (ts.isIdentifier(d.name) &&
|
|
137
|
+
d.initializer &&
|
|
138
|
+
(ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))) {
|
|
139
|
+
helpers.set(d.name.text, d.initializer.body);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return helpers;
|
|
145
|
+
}
|
|
146
|
+
const MAX_HELPER_DEPTH = 4;
|
|
147
|
+
function analyzeBody(body, vocab, ctxParam, helpers, visited, depth) {
|
|
148
|
+
let hasAssertion = false;
|
|
149
|
+
let callCount = 0;
|
|
150
|
+
let hasUnresolved = false;
|
|
151
|
+
const visit = (node) => {
|
|
152
|
+
if (hasAssertion)
|
|
153
|
+
return;
|
|
154
|
+
// chai BDD: `result.should.equal(x)` — the assertion signal is the `.should` property.
|
|
155
|
+
if (ts.isPropertyAccessExpression(node) && node.name.text === 'should') {
|
|
156
|
+
hasAssertion = true;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (ts.isCallExpression(node)) {
|
|
160
|
+
callCount++;
|
|
161
|
+
if (isAssertionCallee(node.expression, vocab, ctxParam)) {
|
|
162
|
+
hasAssertion = true;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const callee = node.expression;
|
|
166
|
+
const localName = ts.isIdentifier(callee) ? callee.text : undefined;
|
|
167
|
+
if (localName && helpers.has(localName)) {
|
|
168
|
+
if (!visited.has(localName) && depth < MAX_HELPER_DEPTH) {
|
|
169
|
+
visited.add(localName);
|
|
170
|
+
const sub = analyzeBody(helpers.get(localName), vocab, undefined, helpers, visited, depth + 1);
|
|
171
|
+
if (sub.hasAssertion) {
|
|
172
|
+
hasAssertion = true;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (sub.hasUnresolved)
|
|
176
|
+
hasUnresolved = true;
|
|
177
|
+
// else: a fully-resolved helper that cannot assert — contributes a call but nothing asserting.
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
hasUnresolved = true; // recursion limit / cycle — stay safe.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
hasUnresolved = true; // external/unknown call could assert indirectly.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
ts.forEachChild(node, visit);
|
|
188
|
+
};
|
|
189
|
+
visit(body);
|
|
190
|
+
return { hasAssertion, callCount, hasUnresolved };
|
|
191
|
+
}
|
|
192
|
+
function statementCount(body) {
|
|
193
|
+
if (ts.isBlock(body))
|
|
194
|
+
return body.statements.length;
|
|
195
|
+
return 1; // expression-bodied arrow
|
|
196
|
+
}
|
|
197
|
+
function ctxParamName(fn) {
|
|
198
|
+
const p = fn.parameters[0];
|
|
199
|
+
if (p && ts.isIdentifier(p.name))
|
|
200
|
+
return p.name.text;
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
function scriptKindFor(path) {
|
|
204
|
+
if (/\.tsx$/i.test(path))
|
|
205
|
+
return ts.ScriptKind.TSX;
|
|
206
|
+
if (/\.jsx$/i.test(path))
|
|
207
|
+
return ts.ScriptKind.JSX;
|
|
208
|
+
if (/\.[cm]?ts$/i.test(path))
|
|
209
|
+
return ts.ScriptKind.TS;
|
|
210
|
+
return ts.ScriptKind.JS;
|
|
211
|
+
}
|
|
212
|
+
/** Inclusive overlap test between a node's [start,end] line range and the diff's added lines. */
|
|
213
|
+
function isTouched(addedLines, startLine, endLine) {
|
|
214
|
+
if (addedLines.size === 0)
|
|
215
|
+
return false;
|
|
216
|
+
for (let l = startLine; l <= endLine; l++) {
|
|
217
|
+
if (addedLines.has(l))
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
function analyzeFile(sf, path, addedLines) {
|
|
223
|
+
const findings = [];
|
|
224
|
+
const vocab = collectVocab(sf);
|
|
225
|
+
const helpers = collectHelpers(sf);
|
|
226
|
+
const visit = (node) => {
|
|
227
|
+
if (ts.isCallExpression(node)) {
|
|
228
|
+
const info = calleeInfo(node.expression);
|
|
229
|
+
if (info.root && TEST_ROOTS.has(info.root) && !info.hasSkip) {
|
|
230
|
+
const fn = testBodyFn(node);
|
|
231
|
+
if (fn) {
|
|
232
|
+
const startLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
233
|
+
const endLine = sf.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
234
|
+
if (isTouched(addedLines, startLine, endLine)) {
|
|
235
|
+
findings.push(evaluateTest(node, fn, sf, path, vocab, helpers, startLine, endLine));
|
|
236
|
+
}
|
|
237
|
+
// Don't descend into the test body looking for nested tests — tests don't nest meaningfully.
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
ts.forEachChild(node, visit);
|
|
243
|
+
};
|
|
244
|
+
visit(sf);
|
|
245
|
+
return findings;
|
|
246
|
+
}
|
|
247
|
+
function evaluateTest(call, fn, sf, path, vocab, helpers, startLine, endLine) {
|
|
248
|
+
const name = testName(call);
|
|
249
|
+
const label = name ? `"${name}"` : 'this test';
|
|
250
|
+
const ctxParam = ctxParamName(fn);
|
|
251
|
+
const analysis = analyzeBody(fn.body, vocab, ctxParam, helpers, new Set(), 0);
|
|
252
|
+
let verdict;
|
|
253
|
+
let title;
|
|
254
|
+
let detail;
|
|
255
|
+
if (analysis.hasAssertion) {
|
|
256
|
+
verdict = 'PASS';
|
|
257
|
+
title = 'Test asserts';
|
|
258
|
+
detail = `${label} contains at least one assertion.`;
|
|
259
|
+
}
|
|
260
|
+
else if (statementCount(fn.body) === 0) {
|
|
261
|
+
verdict = 'CONTRADICTED';
|
|
262
|
+
title = 'Test body is empty';
|
|
263
|
+
detail = `${label} has an empty body and asserts nothing — it can never fail.`;
|
|
264
|
+
}
|
|
265
|
+
else if (analysis.callCount === 0) {
|
|
266
|
+
verdict = 'CONTRADICTED';
|
|
267
|
+
title = 'Test asserts nothing';
|
|
268
|
+
detail = `${label} has no assertions and calls nothing that could assert — it can never fail.`;
|
|
269
|
+
}
|
|
270
|
+
else if (analysis.hasUnresolved) {
|
|
271
|
+
verdict = 'UNVERIFIED';
|
|
272
|
+
title = 'No assertion found';
|
|
273
|
+
detail =
|
|
274
|
+
`No assertion was detected in ${label}, but it calls a function Tribunal cannot resolve ` +
|
|
275
|
+
`(an external/imported helper) that may assert. Verify manually.`;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
verdict = 'CONTRADICTED';
|
|
279
|
+
title = 'Test asserts nothing';
|
|
280
|
+
detail =
|
|
281
|
+
`${label} only calls local helpers that contain no assertions — it can never fail.`;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
analyzer: 'assertion-free-test',
|
|
285
|
+
verdict,
|
|
286
|
+
file: path,
|
|
287
|
+
line: startLine,
|
|
288
|
+
endLine,
|
|
289
|
+
title,
|
|
290
|
+
detail,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
export const assertionFreeTest = {
|
|
294
|
+
id: 'assertion-free-test',
|
|
295
|
+
title: 'Assertion-free tests',
|
|
296
|
+
description: 'Flags tests the PR added or changed that contain no assertion and therefore can never fail.',
|
|
297
|
+
kind: 'claim-independent',
|
|
298
|
+
run(ctx) {
|
|
299
|
+
const findings = [];
|
|
300
|
+
for (const f of ctx.changedFiles) {
|
|
301
|
+
if (f.status === 'deleted')
|
|
302
|
+
continue;
|
|
303
|
+
if (!isTestFile(f.path))
|
|
304
|
+
continue;
|
|
305
|
+
const src = ctx.readFile(f.path);
|
|
306
|
+
if (src == null)
|
|
307
|
+
continue;
|
|
308
|
+
const sf = ts.createSourceFile(f.path, src, ts.ScriptTarget.Latest, true, scriptKindFor(f.path));
|
|
309
|
+
findings.push(...analyzeFile(sf, f.path, f.addedLines));
|
|
310
|
+
}
|
|
311
|
+
return findings;
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
// Exposed for unit tests that drive a single source string directly.
|
|
315
|
+
export const __test__ = { analyzeFile, isTestFile, scriptKindFor };
|
|
316
|
+
//# sourceMappingURL=assertionFreeTest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertionFreeTest.js","sourceRoot":"","sources":["../../src/analyzers/assertionFreeTest.ts"],"names":[],"mappings":"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"}
|