Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8296f46
docs(#5): expand Phase 3 into a step-by-step plan
larspohlmann Aug 11, 2026
2841cc5
feat(#5): dual thresholds with warning severity
larspohlmann Aug 11, 2026
21ad2b7
feat(#5): json reporter
larspohlmann Aug 11, 2026
c5d793f
chore: ignore .superpowers SDD workspace
larspohlmann Aug 11, 2026
1dde6cb
test(#5): fixture harness exercises the CLI json path
larspohlmann Aug 11, 2026
b4fb5ea
feat(#5): phptramp.json config with CLI-over-config precedence
larspohlmann Aug 11, 2026
6570105
feat(#5): exclude globs from config
larspohlmann Aug 11, 2026
40a7960
feat(#5): github actions reporter
larspohlmann Aug 11, 2026
249f328
feat(#5): checkstyle reporter
larspohlmann Aug 11, 2026
612ff29
feat(#5): sarif reporter
larspohlmann Aug 11, 2026
29c98d6
test(#5): close mutation gaps in machine reporters
larspohlmann Aug 11, 2026
9a5a1b5
feat(#5): TrampIgnore attribute and comment suppression
larspohlmann Aug 11, 2026
d87eb2d
fix(#5): suppress only on real phptramp-ignore comments
larspohlmann Aug 11, 2026
dc0c1aa
feat(#5): summary report
larspohlmann Aug 11, 2026
3f4c55b
test(#5): format, config and suppression fixtures
larspohlmann Aug 11, 2026
b12014b
docs(#5): phase 3 status and usage docs
larspohlmann Aug 11, 2026
02292ad
refactor(#5): extract shared pluralizer
larspohlmann Aug 11, 2026
7aff404
fix(#5): relativize file paths in text reporter
larspohlmann Aug 11, 2026
41cc10e
fix(#5): escape github annotation property values
larspohlmann Aug 11, 2026
aaf2ed5
refactor(#5): validate thresholds before indexing
larspohlmann Aug 11, 2026
f3f5ba0
docs(#5): correct fixture coverage wording
larspohlmann Aug 11, 2026
b89de5a
test(#5): tighten text reporter relativization assertion
larspohlmann Aug 11, 2026
f24df21
refactor(#5): share json encoder across reporters
larspohlmann Aug 11, 2026
91492ec
refactor(#5): reuse string-list validation in config loader
larspohlmann Aug 11, 2026
293f50c
refactor(#5): simplify github escaping and hop slicing
larspohlmann Aug 11, 2026
6964783
refactor(#5): skip exclude relativization when unused
larspohlmann Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/.phpunit.cache/
/.idea/
/infection.log
/.superpowers/
94 changes: 80 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
> methods that never use them — "this parameter was passed through 4 classes / 5 methods
> before being used."

**Status: Phase 2.** Cross-file chain reporting works: `phptramp --folder src`
stitches forwarding chains across files and prints text findings, exiting `0`/`1`/`2`.
The classifier and whole-project index remain inspectable via `--dump-index`; other
output formats (`json`, `github`, …) land in Phase 3.
**Status: Phase 3.** Cross-file chain reporting works: `phptramp --folder src`
stitches forwarding chains across files and prints findings in any of six formats,
exiting `0`/`1`/`2`. A `phptramp.json` config file, `#[TrampIgnore]`/
`// phptramp-ignore` suppression, and a `--warn-limit` warning tier are all wired
up. The classifier and whole-project index remain inspectable via `--dump-index`.
See [docs/plan.md](docs/plan.md) for the full implementation plan and current phase.

## What it does
Expand Down Expand Up @@ -55,10 +56,14 @@ Add a script to your project's `composer.json` to invoke it the short way:

```bash
composer tramp
composer tramp -- --changed-only --git-base origin/main
composer tramp -- --format json --warn-limit 2
```

## Planned CLI
phptramp itself does not ship a `composer.json` script of this name — `vendor/bin/phptramp`
(or `bin/phptramp` inside this repo) is the invocation; the snippet above is a convenience
you add to a *consuming* project.

## CLI

```text
phptramp [options]
Expand All @@ -68,17 +73,78 @@ phptramp [options]
--files <a,b,c> Comma-separated list of files
--limit <n> Fail on chains with >= n pass-through hops (default: 3)
--warn-limit <n> Warn (do not fail CI) on chains with >= n hops
--format <fmt> text|json|github|checkstyle|sarif (default: text)
--format <fmt> text|json|github|checkstyle|sarif|summary (default: text)
--explain Show why chains ended (call resolution trace)
--changed-only Only report chains touching changed lines
--git-base <ref> Diff base for --changed-only (default: origin/main)
--baseline <file> Ignore findings recorded in the baseline
--generate-baseline <f> Write current findings to a baseline file
--changed-only Reserved for diff-aware mode (Phase 4) — accepted, not yet wired up
--git-base <ref> Reserved for diff-aware mode (Phase 4) — accepted, not yet wired up
--baseline <file> Reserved for baselining (Phase 5) — accepted, not yet wired up
--generate-baseline <f> Reserved for baselining (Phase 5) — accepted, not yet wired up
```

Exit codes: `0` no finding at error severity, `1` at least one finding at/over
`--limit`, `2` tool error — so it drops straight into any CI pipeline.

## Configuration

phptramp reads `phptramp.json` (falling back to `phptramp.dist.json`) from the current
working directory. CLI flags always take precedence over the config file. An unknown
key, or a value of the wrong type, is a config error rather than a silently ignored typo.

```json
{
"paths": ["src"],
"exclude": ["src/Legacy/*.php"],
"limit": 3,
"warnLimit": 2,
"format": "json"
}
```

Exit codes: `0` no findings, `1` findings at/over `--limit`, `2` tool error —
so it drops straight into any CI pipeline. Configuration lives in `phptramp.json`
(or `phptramp.dist.json`); CLI flags override config.
| Key | Type | Same as |
|---|---|---|
| `paths` | list of strings | entries ending in `.php` become `--file`s, everything else a `--folder` |
| `exclude` | list of strings | `fnmatch` globs matched against each folder-discovered file's path, relative to the config file's directory; explicit files (`paths` entries ending in `.php`) are never excluded |
| `limit` | integer | `--limit` |
| `warnLimit` | integer | `--warn-limit` |
| `format` | string | `--format` |
| `baseline` | string | `--baseline` (reserved for Phase 5) |

## Output formats

`--format` selects the renderer; all six are implemented and each has an exact-string
unit test. `json` is additionally exercised end-to-end by the `tests/fixtures/` harness,
which always runs with `--format json`.

| Format | One-line example |
|---|---|
| `text` | `FINDING $config: 3 pass-through hops across 4 classes` |
| `json` | `{"limit":3,"warnLimit":null,"findings":[{"param":"config","severity":"error","hops":3,...}]}` |
| `github` | `::error file=src/Demo.php,line=12,title=phptramp%3A%3A$config%3A 3 pass-through hops across 4 classes (terminal%3A Demo\Mailer%3A%3A__construct [stored])` |
| `checkstyle` | `<error line="12" severity="error" message="$config: 3 pass-through hops across 4 classes (terminal: Demo\Mailer::__construct [stored])" source="phptramp.trampData"/>` |
| `sarif` | `{"ruleId":"phptramp.trampData","level":"error","message":{"text":"$config: 3 pass-through hops across 4 classes (terminal: Demo\\Mailer::__construct [stored])"}}` |
| `summary` | `12 chains total; 5 at or over the limit (limit: 3 hops).` |

## Suppression

Mark a specific false positive as intentional instead of raising `--limit` project-wide:

- **`#[TrampIgnore]`** on a class, method, function, or parameter. Matching is by the
attribute name's *short name*, so an analyzed codebase never has to `require` or
autoload phptramp's own `PhpTramp\Ignore\TrampIgnore` class to use it.
- **`// phptramp-ignore`** as a real PHP comment (a marker inside a string literal does
not count) on a hop's declaration line, the line directly above it, or a forwarding
call-site line.

Either drops the *entire chain* passing through that hop, not just the one parameter
reaching the flagged declaration.

## `--warn-limit`

`--warn-limit <n>` adds a second, lower threshold below `--limit`. Chains whose hop
count falls in `[warn-limit, limit)` render at `severity: "warning"` (`WARNING` in text,
`::warning` in the GitHub format, `"level": "warning"` in SARIF, …) but never fail the
run — only chains at or over `--limit` set exit code `1`. Useful for tightening a hop
budget gradually: warn today, promote to a hard failure once the codebase is clean.

## IDE integration

Expand Down
22 changes: 16 additions & 6 deletions docs/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -680,26 +680,36 @@ Tasks (expanded in `docs/plans/phase-2.md`):

---

## Phase 3 — CI & config surface
## Phase 3 — CI & config surface ✅ (issue #5)

**Deliverable:** all output formats, config file, suppression, dual thresholds, summary.

- **3.1 ConfigLoader:** `phptramp.json` then `phptramp.dist.json`; keys `paths`,
**Detailed step-by-step plan: [docs/plans/phase-3.md](plans/phase-3.md)** — written
against the actual Phase 1–2 interfaces; where it refines the sketches below (e.g.
`Severity`/`Thresholds` as reporting-layer concepts, config precedence via
parser-default seeding, suppression exposed through `MethodIndex::suppressions()`),
the detailed plan wins.

- [x] **3.1 ConfigLoader:** `phptramp.json` then `phptramp.dist.json`; keys `paths`,
`exclude`, `limit`, `warnLimit`, `format`, `baseline`; strict unknown-key error;
precedence CLI > config > defaults (merge into `Options`). `exclude` glob patterns
feed FileLocator.
- **3.2 Formats:** `JsonReporter` (the `expected.json` schema — one schema everywhere),
- [x] **3.2 Formats:** `JsonReporter` (the `expected.json` schema — one schema everywhere),
`GithubReporter` (`::error file=...,line=...::...` per origin, `::notice` per hop),
`CheckstyleReporter`, `SarifReporter` (rule id `phptramp.trampData`). Fixtures assert
each format byte-for-byte.
- **3.3 Suppression:** `#[TrampIgnore]` attribute class + `// phptramp-ignore` comments
- [x] **3.3 Suppression:** `#[TrampIgnore]` attribute class + `// phptramp-ignore` comments
scanned during indexing into a `SuppressionIndex`; ChainBuilder drops chains with any
suppressed hop (frozen rule 11).
- **3.4 Dual thresholds:** `--warn-limit` — findings in `[warn, limit)` render as
- [x] **3.4 Dual thresholds:** `--warn-limit` — findings in `[warn, limit)` render as
warnings, never affect exit code. GitHub format uses `::warning`.
- **3.5 SummaryReporter:** `--format=summary` — histogram of chain lengths, top 10
- [x] **3.5 SummaryReporter:** `--format=summary` — histogram of chain lengths, top 10
longest chains, most-forwarded parameters.

`--changed-only`/`--git-base` (Phase 4) and `--baseline`/`--generate-baseline` (Phase 5)
remain accepted-but-inert CLI flags — parsed into `Options` for forward compatibility,
not yet consumed by analysis. Not a Phase 3 deliverable; tracked in their own phases.

---

## Phase 4 — Diff-aware mode (flagship)
Expand Down
Loading
Loading