Skip to content

Commit 42fdf3c

Browse files
v1.0
0 parents  commit 42fdf3c

143 files changed

Lines changed: 19702 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.php]
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.{json,neon,xml,yml,yaml}]
14+
indent_style = space
15+
indent_size = 4
16+
17+
[*.md]
18+
trim_trailing_whitespace = false

.github/FUNDING.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Funding configuration for phpcpd-next.
2+
#
3+
# Fill in the handles below and uncomment the lines you want to use. Lines are
4+
# left commented so the repository does not render broken "Sponsor" buttons
5+
# before the accounts exist.
6+
#
7+
# Docs: https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
8+
9+
# github: [your-github-username] # GitHub Sponsors
10+
# open_collective: phpcpd-next # Open Collective slug
11+
# ko_fi: yourhandle # Ko-fi
12+
# tidelift: packagist/phpcpd-next/phpcpd # Tidelift (packagist/<vendor>/<package>)
13+
# custom: # Any other URLs (PayPal, donate page)
14+
# - "https://example.org/donate"
15+
16+
# Funding targets to pursue (notes, not rendered by GitHub):
17+
# - PHP Foundation — PHP ecosystem work
18+
# - NLnet / NGI Zero — open-source public-good grants
19+
# - Sovereign Tech Fund — foundational open infrastructure
20+
# - GitHub Sponsors / Tidelift — adoption-driven, once dependents grow

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
name: PHP ${{ matrix.php }} — ${{ matrix.os }}
12+
runs-on: ${{ matrix.os }}
13+
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
os: [ubuntu-latest]
18+
php: ['8.5']
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Setup PHP
24+
uses: shivammathur/setup-php@v2
25+
with:
26+
php-version: ${{ matrix.php }}
27+
extensions: dom, mbstring
28+
coverage: none
29+
30+
- name: Install dependencies
31+
run: composer install --no-interaction --prefer-dist --optimize-autoloader
32+
33+
- name: Security audit
34+
run: composer audit
35+
36+
- name: Lint (PHP-CS-Fixer dry-run)
37+
run: vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no
38+
39+
- name: Static analysis (PHPStan level 9)
40+
run: vendor/bin/phpstan analyse --memory-limit=1G --no-progress
41+
42+
- name: Tests
43+
run: vendor/bin/phpunit --no-output
44+
# Remove --no-output once tests are written and passing

.gitignore

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Composer
2+
/vendor/
3+
composer.phar
4+
5+
# Benchmark corpus — downloaded third-party code, 1.5 GB, not part of this repo.
6+
# Run bench/fetch.sh to rebuild locally.
7+
/bench/corpus/
8+
9+
# Benchmark vendor — contains phpcpd.phar (comparison binary).
10+
# Run: curl -L https://github.com/sebastianbergmann/phpcpd/releases/download/6.0.3/phpcpd.phar \
11+
# -o bench/vendor/phpcpd.phar
12+
/bench/vendor/
13+
14+
# Built phar artifact (generate with bin/build-phar.sh)
15+
/build/
16+
17+
# Tool caches
18+
/.php-cs-fixer.cache
19+
/.phpunit.cache/
20+
/.phpcpd-cache/
21+
22+
# LaTeX build artifacts (the paper PDF is built via bin/build-paper.sh).
23+
# Keep paper.tex and paper.pdf; ignore everything LaTeX regenerates.
24+
/paper/*.aux
25+
/paper/*.log
26+
/paper/*.out
27+
/paper/*.toc
28+
/paper/*.lof
29+
/paper/*.lot
30+
/paper/*.bbl
31+
/paper/*.blg
32+
/paper/*.fls
33+
/paper/*.fdb_latexmk
34+
/paper/*.synctex.gz
35+
/paper/*.nav
36+
/paper/*.snm
37+
/paper/*.vrb
38+
/paper/*.auxlock
39+
/paper/*.dpth
40+
/paper/*.md5
41+
42+
# IDE / OS noise
43+
/.idea/
44+
/.vscode/
45+
*.DS_Store
46+
47+
# Claude Code local settings
48+
/.claude/

CHANGELOG.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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

Comments
 (0)