Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

- Mutation testing now counts code the tests execute without naming it in `#[Covers]`
(`infection --with-uncovered`), with `PublicVisibility` off and default-parameter
signature lines ignored; unit test added for the docblock splitter failing (`preg_split` returning false). No `src/` changes.

## 1.0.1 — 2026-09-27

- Package `type` changed to `rector-extension` so the rule pack is discoverable on Packagist (`type=rector-extension`) and recognised by `rector/extension-installer`. No rules are auto-applied: no `extra.rector.includes` key is declared, so `rector.php` still opts in explicitly.
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@
],
"cs": "php-cs-fixer fix --dry-run --diff --config=.php-cs-fixer.php",
"cs:fix": "php-cs-fixer fix --diff --config=.php-cs-fixer.php",
"mutation": "infection --threads=max",
"mutation": [
"Composer\\Config::disableProcessTimeout",
"infection --threads=max --with-uncovered"
],
"psalm": "psalm --no-cache",
"rector": "rector process --dry-run",
"rector:fix": "rector process",
Expand Down
20 changes: 19 additions & 1 deletion infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@
"testFramework": "testo",
"minMsi": 100,
"mutators": {
"@default": true
"@default": true,
// Constructor/method parameter declarations with a default value
// (`private int $x = 100,`, `bool $strict = false,`): the mutant sits on a
// signature line with no executable opcode under pcov, so it is reported Not
// Covered whatever the tests do. Only a type-prefixed `$name = default,` line
// matches; a body line cannot (no type before the variable). Do NOT add ^ or $:
// Infection wraps the pattern as ^-\s*REGEX$ over the removed diff line.
// Verified 2026-11-01 by diffing the survivor lists with and without it:
// bulkhead 6, circuit-breaker 13, json-ld 19 mutants removed, all of them
// such signature lines; Escaped and Timed Out unchanged.
"global-ignoreSourceCodeByRegex": [
"(?:(?:private|protected|public|readonly)\\s+)*\\??[A-Za-z_\\\\][\\w\\\\|]*\\s+\\$\\w+\\s*=\\s*[^,;]+,"
],
// Off: with --with-uncovered a PublicVisibility mutant sits on a parameterless
// method's signature line, which has no executable opcode under pcov/xdebug,
// so it is reported Not Covered and can never be killed, whatever #[Covers]
// says. Measured on pinned-http-client 2026-11-01: 4 of 4 such mutants were
// Not Covered, none killed.
"PublicVisibility": false
}
}
26 changes: 26 additions & 0 deletions tests/DocblockTypeRewriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ public function rewritesReturnTags(string $docText, ?string $expected): void
Assert::same((new DocblockTypeRewriter())->rewrittenReturnTag($docText), $expected);
}

public function aDocblockThePatternEngineCannotSplitIsLeftAlone(): void
{
// PCRE keeps compiled patterns (JIT included) in a bounded cache and a
// JIT-compiled match ignores the backtrack limit: evict, then recompile
// without JIT so that the limit applies to the split.
for ($i = 0; $i < 5_000; ++$i) {
preg_match('/evict' . $i . '/', '');
}

$jit = ini_set('pcre.jit', '0');
$limit = ini_set('pcre.backtrack_limit', '1');
set_error_handler(static function (int $severity, string $message): never {
throw new \ErrorException($message, 0, $severity);
});

try {
$rewritten = (new DocblockTypeRewriter())->rewrittenVarTag("/**\n * @var DateTime\n */");
} finally {
restore_error_handler();
ini_set('pcre.backtrack_limit', (string) $limit);
ini_set('pcre.jit', (string) $jit);
}

Assert::null($rewritten);
}

public static function varTagProvider(): iterable
{
yield 'plain type' => [
Expand Down
Loading