|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to **phpcpd-next** are documented here. |
| 4 | + |
| 5 | +Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) |
| 6 | +Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) |
| 7 | + |
| 8 | +> For the complete technical diff against upstream (every changed line with a *Why* explanation), |
| 9 | +> see [MODERNIZATION.md](MODERNIZATION.md). |
| 10 | +
|
| 11 | +--- |
| 12 | + |
| 13 | +## [1.0.0] - 2026-06-27 |
| 14 | + |
| 15 | +### Performance |
| 16 | + |
| 17 | +- **Banded edit-distance DP in the suffix-tree engine.** Profiling showed the approximate-matching |
| 18 | + DP — not construction — dominated `findClones` and grew super-linearly with `--edit-distance`. Since |
| 19 | + a cell `(i,j)` with `|i−j| > maxErrors` can never lie on a sub-threshold path, the DP is restricted |
| 20 | + to the diagonal band of width `2·maxErrors+1` (Ukkonen cutoff), turning the per-clone cost from |
| 21 | + `O(L²)` to `O(L·maxErrors)`. Measured **~3.5× faster** at every edit distance on a Firefly III slice, |
| 22 | + with **byte-identical** clone output. |
| 23 | + |
| 24 | +### Fixed |
| 25 | + |
| 26 | +- **Degenerate zero-line clones** are no longer reported by the suffix-tree engine. A clone whose |
| 27 | + in-file span collapsed to zero lines (its matched run lay almost entirely beyond a file boundary) was |
| 28 | + emitted as meaningless `(0 lines)` noise; such clones are now skipped. |
| 29 | + |
| 30 | +### Added — detection |
| 31 | + |
| 32 | +- **Type-2 detection on every engine** via `--fuzzy`: a shared `TokenNormalizer` abstracts |
| 33 | + identifiers and literals to type classes (previously `--fuzzy` only touched variables, and only |
| 34 | + in the default engine — the suffix tree had no Type-2 at all). |
| 35 | +- **Inconsistent-clone reporting**: gapped (Type-3) clones are distinguished from exact copies |
| 36 | + (`CodeClone::isGapped()`), marked `[inconsistent]` in console output and surfaced as `warning` |
| 37 | + severity in SARIF. |
| 38 | +- **Type-aware edit weights** in the suffix-tree engine: a changed control keyword (`if`→`while`) |
| 39 | + costs more of the `--edit-distance` budget than a renamed identifier. |
| 40 | +- **New `tokenbag` engine** (`--algorithm=tokenbag`): a SourcererCC-style order-invariant token |
| 41 | + bag + inverted index that detects **reordered** clones the contiguous engines miss. Threshold |
| 42 | + via `--min-similarity` (default 0.7). |
| 43 | + |
| 44 | +### Added — CI |
| 45 | + |
| 46 | +- **Incremental result cache** (`--cache` / `--cache-dir`): keyed by a fingerprint of the |
| 47 | + configuration and a manifest of file hashes; a re-run on unchanged files skips detection |
| 48 | + entirely and prints `(cache hit)`. Designed to be mounted with `actions/cache`. |
| 49 | +- **Per-file incremental index** (`--incremental`, Rabin–Karp only): Hummel-style index that |
| 50 | + persists each file's tokenization and re-tokenizes **only the files that changed**, replaying the |
| 51 | + rest from the index. Finer-grained than `--cache` (one edit no longer invalidates the whole run) |
| 52 | + and provably equivalent to a full scan. Prints `(incremental index: N reused, M scanned)`. |
| 53 | + |
| 54 | +### Added — output |
| 55 | + |
| 56 | +- **JSON** report (`--log-json`) and **SARIF 2.1.0** report (`--log-sarif`, for GitHub Code |
| 57 | + Scanning), alongside the existing PMD-CPD XML. A shared `Log\Logger` contract unifies them. |
| 58 | + |
| 59 | +### Changed |
| 60 | + |
| 61 | +- **Zero runtime Composer dependencies**: `sebastian/version`, `sebastian/cli-parser`, |
| 62 | + `phpunit/php-file-iterator`, and `phpunit/php-timer` were removed — replaced with owned, |
| 63 | + improved code (a declarative self-documenting CLI parser with value validation; a file finder |
| 64 | + that prunes excluded directories and supports glob excludes; a timer that reports throughput). |
| 65 | +- Namespace migrated to `LucianoPereira\PhpcpdNext`; autoloading switched from classmap to PSR-4. |
| 66 | +- PMD XML logger simplified to use DOM-native escaping. |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +## [0.1.0] — 2026-06-26 |
| 71 | + |
| 72 | +First release of **phpcpd-next**. Picks up where Sebastian Bergmann's archived |
| 73 | +`sebastianbergmann/phpcpd` (7.0-dev) left off and brings the tool forward to PHP 8.5. |
| 74 | + |
| 75 | +### Platform |
| 76 | + |
| 77 | +- Requires PHP **≥ 8.5** (upstream required ≥ 8.1) |
| 78 | +- `composer.json` platform locked to `8.5.0` |
| 79 | + |
| 80 | +### Fixed |
| 81 | + |
| 82 | +- **`sebastian/version` v4 API break** — `getVersion()` renamed to `asString()` in v4; |
| 83 | + the banner was crashing silently on import. |
| 84 | +- **`empty($object)` always false** — `SuffixTreeStrategy` used `empty($this->result)` on |
| 85 | + a `CodeCloneMap` object; `empty()` on any object always returns `false`. Fixed to |
| 86 | + `=== null`. |
| 87 | +- **Division by zero** — `CodeCloneMap::averageSize()` divided by `count()` without |
| 88 | + guarding the empty case. Fixed with an early `return 0.0`. |
| 89 | +- **`current()` returning `false`** — `CodeClone::lines()` called `current()` on an |
| 90 | + associative array and used the result as a `CodeCloneFile`; `current()` returns `false` |
| 91 | + on an empty array. Replaced with `array_values($this->files)[0]` which is guaranteed safe |
| 92 | + after the existing non-empty guard. |
| 93 | +- **`file_get_contents()` false return** — both `DefaultStrategy` and `SuffixTreeStrategy` |
| 94 | + passed the raw `string|false` return directly into tokenisation. Added `if ($buffer === false) { return; }` guards. |
| 95 | +- **`file()` returning `false`** — `CodeClone::lines()` called `file()` without checking |
| 96 | + the return. Fixed with `?: []` fallback. |
| 97 | +- **`mb_convert_encoding()` returning `false`** — `AbstractXmlLogger` did not check the |
| 98 | + return of `mb_convert_encoding()`, which returns `false` on encoding failure. Fixed with |
| 99 | + an explicit false-check and fallback to the original string. |
| 100 | +- **`preg_replace()` returning `null`** — `AbstractXmlLogger::toUtf8String()` could return |
| 101 | + `string|null` from `preg_replace`. Fixed with `?? $string` fallback. |
| 102 | + |
| 103 | +### Changed |
| 104 | + |
| 105 | +- **Banner** updated to credit both the original author and the fork: |
| 106 | + `phpcpd 0.1.0 by Luciano Federico Pereira based on phpcpd 7.0-dev by Sebastian Bergmann.` |
| 107 | + |
| 108 | +### Modernised (PHP 8.0 – 8.5) |
| 109 | + |
| 110 | +- `readonly class` applied to `Arguments`, `CodeCloneFile`, `StrategyConfiguration`, |
| 111 | + `CloneInfo` — immutability enforced at the class level. |
| 112 | +- Constructor property promotion on all eligible classes — eliminates boilerplate |
| 113 | + `$this->x = $x` assignments. |
| 114 | +- `#[\Override]` attribute on every method that implements or overrides a contract. |
| 115 | +- Typed class constants (`private const string`, `private const int`) throughout. |
| 116 | +- `foreach ($array as $item)` replaces `foreach (array_keys($array) as $k)` where the key |
| 117 | + was never used. |
| 118 | +- `$result === null` replaces `empty($result)` wherever the variable is an object or |
| 119 | + nullable type. |
| 120 | + |
| 121 | +### Improved |
| 122 | + |
| 123 | +- **Duplicate code eliminated** — `DefaultStrategy::processFile()` contained two identical |
| 124 | + 17-line blocks that built and recorded a `CodeClone`. Extracted to |
| 125 | + `recordCloneIfValid()`. Running the tool on its own source now reports zero clones. |
| 126 | +- PHPDoc generics (`list<T>`, `@template`, `@implements`) on all collection classes. |
| 127 | + |
| 128 | +### Toolchain (new files) |
| 129 | + |
| 130 | +- **PHPStan level 9** — zero errors. `phpstan.neon` + `phpstan-stubs.php` for the |
| 131 | + untyped `sebastian/cli-parser` return. |
| 132 | +- **PHP-CS-Fixer** — `@PER-CS2.0` + risky fixers (`declare_strict_types`, |
| 133 | + `native_function_invocation`, `strict_param`, …). |
| 134 | +- **Rector** — `php85` set, `CODE_QUALITY`, `TYPE_DECLARATION`. |
| 135 | +- **PHPUnit 12** — `phpunit.xml` wired; test writing is the next milestone. |
| 136 | +- **GitHub Actions CI** — `audit → lint → analyse → test` on every push. |
| 137 | +- **`.editorconfig`** — consistent whitespace before any tool runs. |
| 138 | +- **Composer scripts** — `lint`, `lint:fix`, `analyse`, `test`, `check`. |
| 139 | + |
| 140 | +[1.0.0]: https://github.com/phpcpd-next/phpcpd/releases/tag/v1.0.0 |
| 141 | +[0.1.0]: https://github.com/phpcpd-next/phpcpd/releases/tag/v0.1.0 |
0 commit comments