diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f3bb1..975626b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ 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). +## 2.7.1 — 2026-07-25 + +- Reject trailing newlines in validated values: anchor regexes with `\z` + instead of `$` in `CounterExample::toExamplesCode()` (the examples-method + name passed to the reflection/replay path) and in `PropertyInterceptor` / + `SeedStorage` integer parsers. The latter two are hygiene only — env values + and `trim()`'ed seed-file contents already strip the newline before the + regex runs. + ## 2.7.0 — 2026-07-25 - Ship an AI agent skill (`resources/skills/rasuvaeff-property-testing/SKILL.md` + diff --git a/src/CounterExample.php b/src/CounterExample.php index bbf7e48..8ca0077 100644 --- a/src/CounterExample.php +++ b/src/CounterExample.php @@ -66,7 +66,7 @@ public function toJson(bool $pretty = false): string public function toExamplesCode(string $methodName = 'propertyExamples'): string { - if (preg_match('/^[A-Za-z_]\w*$/', $methodName) !== 1) { + if (preg_match('/^[A-Za-z_]\w*\z/', $methodName) !== 1) { throw new \InvalidArgumentException(sprintf('Invalid examples method name "%s"', $methodName)); } diff --git a/src/Internal/PropertyInterceptor.php b/src/Internal/PropertyInterceptor.php index 4106e98..4430c0a 100644 --- a/src/Internal/PropertyInterceptor.php +++ b/src/Internal/PropertyInterceptor.php @@ -416,7 +416,7 @@ private function resolveRuns(int $runs): int return $runs; } - if (preg_match('/^\d+$/', $env) !== 1 || (int) $env < 1) { + if (preg_match('/^\d+\z/', $env) !== 1 || (int) $env < 1) { throw new \InvalidArgumentException(sprintf('PROPERTY_RUNS must be a positive integer, got "%s"', $env)); } @@ -478,7 +478,7 @@ private function resolveSeed(?int $attributeSeed): int return random_int(0, PHP_INT_MAX); } - if (preg_match('/^-?\d+$/', $env) !== 1) { + if (preg_match('/^-?\d+\z/', $env) !== 1) { throw new \InvalidArgumentException(sprintf('PROPERTY_SEED must be an integer, got "%s"', $env)); } diff --git a/src/Internal/SeedStorage.php b/src/Internal/SeedStorage.php index b5aa1be..3f96f98 100644 --- a/src/Internal/SeedStorage.php +++ b/src/Internal/SeedStorage.php @@ -51,7 +51,7 @@ public function recall(string $id): ?int $content = file_get_contents($file); - if ($content === false || preg_match('/^-?\d+$/', trim($content)) !== 1) { + if ($content === false || preg_match('/^-?\d+\z/', trim($content)) !== 1) { return null; } diff --git a/tests/CounterExampleTest.php b/tests/CounterExampleTest.php index add025e..f0de22a 100644 --- a/tests/CounterExampleTest.php +++ b/tests/CounterExampleTest.php @@ -102,6 +102,12 @@ public function rejectsInvalidExamplesMethodName(): void (new CounterExample(1, 0, [], []))->toExamplesCode('not-valid'); } + #[ExpectException(\InvalidArgumentException::class)] + public function rejectsExamplesMethodNameWithTrailingNewline(): void + { + (new CounterExample(1, 0, [], []))->toExamplesCode("validName\n"); + } + #[ExpectException(\LogicException::class)] public function refusesToGenerateNonRunnableObjectExampleCode(): void {