From 1c13b12ae5ef901974fa192d1bc2a68f1dc1fc58 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Thu, 29 Feb 2024 13:03:05 +0100 Subject: [PATCH 01/21] fix(deps): composer now also requires PHP 8.2+ --- php/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/composer.json b/php/composer.json index efb015e53..f7667b3d9 100644 --- a/php/composer.json +++ b/php/composer.json @@ -13,7 +13,7 @@ } }, "require": { - "php": "^8.1", + "php": "^8.2", "ext-mbstring": "*", "cucumber/messages": ">=19.1.4 <=24" }, From 254cc8fb610c36d5dc7468f6ed28d0b4c0e259b3 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Thu, 29 Feb 2024 13:04:34 +0100 Subject: [PATCH 02/21] CHANGELOG.md: Mention changes to PHP --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a5cc93e..f8d415379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt ## [Unreleased] +### Changed +- [PHP] Require PHP 8.2+ in CI and in composer.json. + ## [28.0.0] - 2024-02-15 ### Added - [Python] Added release workflow for releasing to Pypi ([#213](https://github.com/cucumber/gherkin/pull/213)) From 142388bc5493dc6c22ceb861239d6bfe33e2d8a8 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Thu, 29 Feb 2024 13:34:16 +0100 Subject: [PATCH 03/21] [PHP] Try updating PHP dev deps --- php/composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/php/composer.json b/php/composer.json index f7667b3d9..d5bd61f0c 100644 --- a/php/composer.json +++ b/php/composer.json @@ -18,11 +18,11 @@ "cucumber/messages": ">=19.1.4 <=24" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "vimeo/psalm": "5.19.1", + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^5.22.2", "friendsofphp/php-cs-fixer": "^3.5", "psalm/plugin-phpunit": "^0.18.0", - "nikic/php-parser": "^4.14" + "nikic/php-parser": "^5.0.1" }, "repositories": [ { From a1d65e0ad942ffa26584b99735cfb3d056bfda06 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Thu, 29 Feb 2024 13:37:56 +0100 Subject: [PATCH 04/21] [PHP] try with a 4.x dep --- php/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/composer.json b/php/composer.json index d5bd61f0c..789ab2ea4 100644 --- a/php/composer.json +++ b/php/composer.json @@ -22,7 +22,7 @@ "vimeo/psalm": "^5.22.2", "friendsofphp/php-cs-fixer": "^3.5", "psalm/plugin-phpunit": "^0.18.0", - "nikic/php-parser": "^5.0.1" + "nikic/php-parser": "^4.16" }, "repositories": [ { From bde6a001b2f24e1f46505e28ecd6222282055c3d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 13:25:13 +0200 Subject: [PATCH 05/21] Make phpunit version exact --- php/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/composer.json b/php/composer.json index a1c504790..aa2846b79 100644 --- a/php/composer.json +++ b/php/composer.json @@ -18,7 +18,7 @@ "cucumber/messages": ">=19.1.4 <=26" }, "require-dev": { - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^11.5.25", "vimeo/psalm": "^5.22.2", "friendsofphp/php-cs-fixer": "^3.51", "psalm/plugin-phpunit": "^0.19.0" From f5fa595c0f321971a6d51963ad5a9e5b46b4c3b3 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 13:44:43 +0200 Subject: [PATCH 06/21] Bump php version and fix dev dependencies --- .github/workflows/test-php.yml | 2 +- php/composer.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml index bfac204df..b7cab9b3d 100644 --- a/.github/workflows/test-php.yml +++ b/.github/workflows/test-php.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.2'] + php: ['8.3'] composer-mode: ['low-deps', 'high-deps'] steps: diff --git a/php/composer.json b/php/composer.json index aa2846b79..813f0435b 100644 --- a/php/composer.json +++ b/php/composer.json @@ -13,15 +13,15 @@ } }, "require": { - "php": "^8.2", + "php": "^8.3", "ext-mbstring": "*", "cucumber/messages": ">=19.1.4 <=26" }, "require-dev": { - "phpunit/phpunit": "^11.5.25", - "vimeo/psalm": "^5.22.2", - "friendsofphp/php-cs-fixer": "^3.51", - "psalm/plugin-phpunit": "^0.19.0" + "phpunit/phpunit": "11.5.25", + "vimeo/psalm": "6.12.0", + "friendsofphp/php-cs-fixer": "3.51", + "psalm/plugin-phpunit": "0.19.5" }, "repositories": [ { From 3d5f094a55b80cd9203bca404b620b8ef2a2468e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 13:50:44 +0200 Subject: [PATCH 07/21] Remove vcs reference, packages are on composer --- php/composer.json | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/php/composer.json b/php/composer.json index 813f0435b..d201382b8 100644 --- a/php/composer.json +++ b/php/composer.json @@ -19,19 +19,8 @@ }, "require-dev": { "phpunit/phpunit": "11.5.25", - "vimeo/psalm": "6.12.0", + "vimeo/psalm": "^6.5.0", "friendsofphp/php-cs-fixer": "3.51", - "psalm/plugin-phpunit": "0.19.5" - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/cucumber/messages-php", - "options": { - "versions": { - "cucumber/messages": "19.1.4" - } - } - } - ] + "psalm/plugin-phpunit": "^0.19.3" + } } From 58f379a3ed93448620b1d1c6fae67c2716b334ba Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 21:17:26 +0200 Subject: [PATCH 08/21] Fix some psalms --- php/src/GherkinDialectProvider.php | 6 +++++- php/src/GherkinDocumentBuilder.php | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/php/src/GherkinDialectProvider.php b/php/src/GherkinDialectProvider.php index 52d16889b..a890f865d 100644 --- a/php/src/GherkinDialectProvider.php +++ b/php/src/GherkinDialectProvider.php @@ -31,7 +31,11 @@ public function __construct( * * @var non-empty-array $data */ - $data = json_decode(file_get_contents(self::JSON_PATH), true, flags: JSON_THROW_ON_ERROR); + $contents = file_get_contents(self::JSON_PATH); + if(!is_string($contents)) { + throw new RuntimeException("Unable to read " . self::JSON_PATH); + } + $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); $this->DIALECTS = $data; } catch (JsonException $e) { throw new RuntimeException("Unable to parse " . self::JSON_PATH, previous: $e); diff --git a/php/src/GherkinDocumentBuilder.php b/php/src/GherkinDocumentBuilder.php index 9633aa375..d350446d6 100644 --- a/php/src/GherkinDocumentBuilder.php +++ b/php/src/GherkinDocumentBuilder.php @@ -45,6 +45,7 @@ public function __construct( $this->reset($uri); } + #[\Override] public function build(Token $token): void { if (null === $token->match) { @@ -60,11 +61,13 @@ public function build(Token $token): void } } + #[\Override] public function startRule(RuleType $ruleType): void { array_push($this->stack, new AstNode($ruleType)); } - + + #[\Override] public function endRule(RuleType $ruleType): void { $node = array_pop($this->stack); @@ -74,6 +77,7 @@ public function endRule(RuleType $ruleType): void } } + #[\Override] public function getResult(): GherkinDocument { $document = $this->currentNode()->getSingle(GherkinDocument::class, Ruletype::GherkinDocument); @@ -85,6 +89,7 @@ public function getResult(): GherkinDocument return $document; } + #[\Override] public function reset(string $uri): void { $this->stack = [new AstNode(RuleType::None)]; From 12590149c0e9c2565d07ea9e910696842cb8e369 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 21:21:40 +0200 Subject: [PATCH 09/21] Fix some psalms --- php/src/GherkinDocumentBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/src/GherkinDocumentBuilder.php b/php/src/GherkinDocumentBuilder.php index d350446d6..60c8082f2 100644 --- a/php/src/GherkinDocumentBuilder.php +++ b/php/src/GherkinDocumentBuilder.php @@ -66,7 +66,7 @@ public function startRule(RuleType $ruleType): void { array_push($this->stack, new AstNode($ruleType)); } - + #[\Override] public function endRule(RuleType $ruleType): void { From 78f25fa2040afcbbd3b7a04f4c146ff922f90c03 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 21:41:25 +0200 Subject: [PATCH 10/21] Fix some psalms --- php/composer.json | 2 +- php/src/GherkinLanguageConstants.php | 12 +++++------ php/src/StringUtils.php | 30 ++++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/php/composer.json b/php/composer.json index d201382b8..8095c3db0 100644 --- a/php/composer.json +++ b/php/composer.json @@ -15,7 +15,7 @@ "require": { "php": "^8.3", "ext-mbstring": "*", - "cucumber/messages": ">=19.1.4 <=26" + "cucumber/messages": ">=19.1.4 <=26" }, "require-dev": { "phpunit/phpunit": "11.5.25", diff --git a/php/src/GherkinLanguageConstants.php b/php/src/GherkinLanguageConstants.php index 3269de428..e7a3c360e 100644 --- a/php/src/GherkinLanguageConstants.php +++ b/php/src/GherkinLanguageConstants.php @@ -4,10 +4,10 @@ interface GherkinLanguageConstants { - public const TAG_PREFIX = "@"; - public const COMMENT_PREFIX = "#"; - public const TITLE_KEYWORD_SEPARATOR = ":"; - public const TABLE_CELL_SEPARATOR = "|"; - public const DOCSTRING_SEPARATOR = "\"\"\""; - public const DOCSTRING_ALTERNATIVE_SEPARATOR = "```"; + public const string TAG_PREFIX = "@"; + public const string COMMENT_PREFIX = "#"; + public const string TITLE_KEYWORD_SEPARATOR = ":"; + public const string TABLE_CELL_SEPARATOR = "|"; + public const string DOCSTRING_SEPARATOR = "\"\"\""; + public const string DOCSTRING_ALTERNATIVE_SEPARATOR = "```"; } diff --git a/php/src/StringUtils.php b/php/src/StringUtils.php index 0e5052676..6111271b1 100644 --- a/php/src/StringUtils.php +++ b/php/src/StringUtils.php @@ -4,6 +4,8 @@ namespace Cucumber\Gherkin; +use RuntimeException; + /** * Keeps common string operations in one place using correct unicode functions * (and normalises naming with other implementations) @@ -33,22 +35,42 @@ public static function substring(string $string, int $start, int $length = null) public static function rtrim(string $string): string { - return preg_replace('/' . self::WHITESPACE_PATTERN . '$/u', '', $string); + $pattern = '/' . self::WHITESPACE_PATTERN . '$/u'; + $value = preg_replace($pattern, '', $string); + if($value === null){ + throw new RuntimeException("invalid pattern " . $pattern); + } + return $value; } public static function rtrimKeepNewLines(string $string): string { - return preg_replace('/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u', '', $string); + $pattern = '/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u'; + $value = preg_replace($pattern, '', $string); + if($value === null){ + throw new RuntimeException("invalid pattern " . $pattern); + } + return $value; } public static function ltrim(string $string): string { - return preg_replace('/^'. self::WHITESPACE_PATTERN . '/u', '', $string); + $pattern = '/^' . self::WHITESPACE_PATTERN . '/u'; + $value = preg_replace($pattern, '', $string); + if($value === null){ + throw new RuntimeException("invalid pattern " . $pattern); + } + return $value; } public static function ltrimKeepNewLines(string $string): string { - return preg_replace('/^'. self::WHITESPACE_PATTERN_NO_NEWLINE . '/u', '', $string); + $pattern = '/^' . self::WHITESPACE_PATTERN_NO_NEWLINE . '/u'; + $value = preg_replace($pattern, '', $string); + if($value === null){ + throw new RuntimeException("invalid pattern " . $pattern); + } + return $value; } public static function trim(string $string): string From a401955515e8f3e028efdbbb851670325e028887 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 21:50:00 +0200 Subject: [PATCH 11/21] Fix some psalms --- php/src/StringUtils.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/php/src/StringUtils.php b/php/src/StringUtils.php index 6111271b1..3830c4ad0 100644 --- a/php/src/StringUtils.php +++ b/php/src/StringUtils.php @@ -37,7 +37,7 @@ public static function rtrim(string $string): string { $pattern = '/' . self::WHITESPACE_PATTERN . '$/u'; $value = preg_replace($pattern, '', $string); - if($value === null){ + if($value === null) { throw new RuntimeException("invalid pattern " . $pattern); } return $value; @@ -47,7 +47,7 @@ public static function rtrimKeepNewLines(string $string): string { $pattern = '/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u'; $value = preg_replace($pattern, '', $string); - if($value === null){ + if($value === null) { throw new RuntimeException("invalid pattern " . $pattern); } return $value; @@ -57,7 +57,7 @@ public static function ltrim(string $string): string { $pattern = '/^' . self::WHITESPACE_PATTERN . '/u'; $value = preg_replace($pattern, '', $string); - if($value === null){ + if($value === null) { throw new RuntimeException("invalid pattern " . $pattern); } return $value; @@ -67,7 +67,7 @@ public static function ltrimKeepNewLines(string $string): string { $pattern = '/^' . self::WHITESPACE_PATTERN_NO_NEWLINE . '/u'; $value = preg_replace($pattern, '', $string); - if($value === null){ + if($value === null) { throw new RuntimeException("invalid pattern " . $pattern); } return $value; From e1effbe0823f613f785347bb318f5ec9b4591c16 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 21:56:06 +0200 Subject: [PATCH 12/21] Fix some psalms --- php/src/TokenMatcher.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/php/src/TokenMatcher.php b/php/src/TokenMatcher.php index a1ac4b814..33aa91f26 100644 --- a/php/src/TokenMatcher.php +++ b/php/src/TokenMatcher.php @@ -54,6 +54,7 @@ private function setTokenMatched( ); } + #[\Override] public function match_EOF(Token $token): bool { if ($token->isEof()) { @@ -65,27 +66,32 @@ public function match_EOF(Token $token): bool return false; } + #[\Override] public function match_FeatureLine(Token $token): bool { return $this->matchTitleLine($token, TokenType::FeatureLine, $this->currentDialect->getFeatureKeywords()); } + #[\Override] public function match_BackgroundLine(Token $token): bool { return $this->matchTitleLine($token, TokenType::BackgroundLine, $this->currentDialect->getBackgroundKeywords()); } + #[\Override] public function match_ScenarioLine(Token $token): bool { return $this->matchTitleLine($token, TokenType::ScenarioLine, $this->currentDialect->getScenarioKeywords()) || $this->matchTitleLine($token, TokenType::ScenarioLine, $this->currentDialect->getScenarioOutlineKeywords()); } + #[\Override] public function match_RuleLine(Token $token): bool { return $this->matchTitleLine($token, TokenType::RuleLine, $this->currentDialect->getRuleKeywords()); } + #[\Override] public function match_ExamplesLine(Token $token): bool { return $this->matchTitleLine($token, TokenType::ExamplesLine, $this->currentDialect->getExamplesKeywords()); @@ -108,6 +114,7 @@ private function matchTitleLine(Token $token, TokenType $tokenType, array $keywo return false; } + #[\Override] public function match_Other(Token $token): bool { //take the entire line, except removing DocString indents @@ -117,6 +124,7 @@ public function match_Other(Token $token): bool return true; } + #[\Override] public function match_Empty(Token $token): bool { if ($token->line?->isEmpty()) { @@ -128,6 +136,7 @@ public function match_Empty(Token $token): bool return false; } + #[\Override] public function match_StepLine(Token $token): bool { $keywords = $this->currentDialect->getStepKeywords(); @@ -146,6 +155,7 @@ public function match_StepLine(Token $token): bool return false; } + #[\Override] public function match_TableRow(Token $token): bool { if ($token->line?->startsWith(GherkinLanguageConstants::TABLE_CELL_SEPARATOR)) { @@ -158,6 +168,7 @@ public function match_TableRow(Token $token): bool return false; } + #[\Override] public function match_Comment(Token $token): bool { if ($token->line?->startsWith(GherkinLanguageConstants::COMMENT_PREFIX)) { @@ -170,6 +181,7 @@ public function match_Comment(Token $token): bool return false; } + #[\Override] public function match_DocStringSeparator(Token $token): bool { return $this->activeDocStringSeparator === null @@ -200,6 +212,7 @@ private function _match_DocStringSeparator(Token $token, string $separator, bool return false; } + #[\Override] public function match_TagLine(Token $token): bool { if ($token->line?->startsWith(GherkinLanguageConstants::TAG_PREFIX)) { @@ -211,6 +224,7 @@ public function match_TagLine(Token $token): bool return false; } + #[\Override] public function match_Language(Token $token): bool { if ($token->line && preg_match(self::LANGUAGE_PATTERN, $token->line->getLineText(0), $matches)) { From 75ac661c22f26ac36551da450a9f40518fc6c68f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 21:57:38 +0200 Subject: [PATCH 13/21] Fix some psalms --- php/tests/AstNodeTest.php | 1 + php/tests/GherkinDialectProviderTest.php | 1 + php/tests/GherkinDialectTest.php | 1 + php/tests/TokenFormatterBuilderTest.php | 1 + php/tests/TokenMatcherTest.php | 1 + 5 files changed, 5 insertions(+) diff --git a/php/tests/AstNodeTest.php b/php/tests/AstNodeTest.php index bdaa19400..abb8ff636 100644 --- a/php/tests/AstNodeTest.php +++ b/php/tests/AstNodeTest.php @@ -14,6 +14,7 @@ final class AstNodeTest extends TestCase { private AstNode $astNode; + #[\Override] public function setUp(): void { $this->astNode = new AstNode(RuleType::None); diff --git a/php/tests/GherkinDialectProviderTest.php b/php/tests/GherkinDialectProviderTest.php index 7706b196a..5dab9858d 100644 --- a/php/tests/GherkinDialectProviderTest.php +++ b/php/tests/GherkinDialectProviderTest.php @@ -10,6 +10,7 @@ final class GherkinDialectProviderTest extends TestCase { private GherkinDialectProvider $dialectProvider; + #[\Override] public function setUp(): void { $this->dialectProvider = new GherkinDialectProvider(); diff --git a/php/tests/GherkinDialectTest.php b/php/tests/GherkinDialectTest.php index 0aa4a455b..bdf4958db 100644 --- a/php/tests/GherkinDialectTest.php +++ b/php/tests/GherkinDialectTest.php @@ -10,6 +10,7 @@ final class GherkinDialectTest extends TestCase { private GherkinDialect $dialect; + #[\Override] public function setUp(): void { $data = [ diff --git a/php/tests/TokenFormatterBuilderTest.php b/php/tests/TokenFormatterBuilderTest.php index 961ac084f..c3b0e93c3 100644 --- a/php/tests/TokenFormatterBuilderTest.php +++ b/php/tests/TokenFormatterBuilderTest.php @@ -10,6 +10,7 @@ final class TokenFormatterBuilderTest extends TestCase { private TokenFormatterBuilder $tokenBuilder; + #[\Override] public function setUp(): void { $this->tokenBuilder = new TokenFormatterBuilder(); diff --git a/php/tests/TokenMatcherTest.php b/php/tests/TokenMatcherTest.php index 95494b299..358114b19 100644 --- a/php/tests/TokenMatcherTest.php +++ b/php/tests/TokenMatcherTest.php @@ -14,6 +14,7 @@ final class TokenMatcherTest extends TestCase { private TokenMatcherInterface $tokenMatcher; + #[\Override] public function setUp(): void { $this->tokenMatcher = new TokenMatcher(); From fd90bf720376bbd07af45a71a8932d07b37d91a1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:04:33 +0200 Subject: [PATCH 14/21] Fix some psalms --- php/bin/gherkin | 9 +++++++-- php/src/GherkinDialectProvider.php | 4 +--- php/src/StringUtils.php | 18 ++++-------------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/php/bin/gherkin b/php/bin/gherkin index f156ada32..25044c9ae 100755 --- a/php/bin/gherkin +++ b/php/bin/gherkin @@ -12,6 +12,7 @@ assert(is_array($argv), "Script must be run from the command line"); $options = ['predictable-ids', 'no-source', 'no-ast', 'no-pickles']; $selectedOptions = getopt('', $options, $restIndex); +assert(is_array($selectedOptions), "Could not get options"); $paths = array_slice($argv, $restIndex); @@ -20,7 +21,9 @@ $sources = ( /** @param list $paths */ function (array $paths) { foreach ($paths as $path) { - yield new Source(uri: $path, data: file_get_contents($path)); + $contents = file_get_contents($path); + assert(is_string($contents), "Could not read " . $path); + yield new Source(uri: $path, data: $contents); } } )($paths); @@ -33,6 +36,8 @@ $parser = new GherkinParser( ); $envelopes = $parser->parse($sources); -$output = fopen('php://stdout', 'w'); +$file = 'php://stdout'; +$output = fopen($file, 'w'); +assert(is_resource($output), "Could not open " . $file); $writer = NdJsonStreamWriter::fromFileHandle($output); $writer->writeEnvelopes($envelopes); diff --git a/php/src/GherkinDialectProvider.php b/php/src/GherkinDialectProvider.php index a890f865d..bd0d23091 100644 --- a/php/src/GherkinDialectProvider.php +++ b/php/src/GherkinDialectProvider.php @@ -32,9 +32,7 @@ public function __construct( * @var non-empty-array $data */ $contents = file_get_contents(self::JSON_PATH); - if(!is_string($contents)) { - throw new RuntimeException("Unable to read " . self::JSON_PATH); - } + assert(is_string($contents), "Could not read " . self::JSON_PATH); $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); $this->DIALECTS = $data; } catch (JsonException $e) { diff --git a/php/src/StringUtils.php b/php/src/StringUtils.php index 3830c4ad0..f6ba0372c 100644 --- a/php/src/StringUtils.php +++ b/php/src/StringUtils.php @@ -4,8 +4,6 @@ namespace Cucumber\Gherkin; -use RuntimeException; - /** * Keeps common string operations in one place using correct unicode functions * (and normalises naming with other implementations) @@ -37,9 +35,7 @@ public static function rtrim(string $string): string { $pattern = '/' . self::WHITESPACE_PATTERN . '$/u'; $value = preg_replace($pattern, '', $string); - if($value === null) { - throw new RuntimeException("invalid pattern " . $pattern); - } + assert(is_string($value), "Invalid pattern " . $pattern); return $value; } @@ -47,9 +43,7 @@ public static function rtrimKeepNewLines(string $string): string { $pattern = '/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u'; $value = preg_replace($pattern, '', $string); - if($value === null) { - throw new RuntimeException("invalid pattern " . $pattern); - } + assert(is_string($value), "Invalid pattern " . $pattern); return $value; } @@ -57,9 +51,7 @@ public static function ltrim(string $string): string { $pattern = '/^' . self::WHITESPACE_PATTERN . '/u'; $value = preg_replace($pattern, '', $string); - if($value === null) { - throw new RuntimeException("invalid pattern " . $pattern); - } + assert(is_string($value), "Invalid pattern " . $pattern); return $value; } @@ -67,9 +59,7 @@ public static function ltrimKeepNewLines(string $string): string { $pattern = '/^' . self::WHITESPACE_PATTERN_NO_NEWLINE . '/u'; $value = preg_replace($pattern, '', $string); - if($value === null) { - throw new RuntimeException("invalid pattern " . $pattern); - } + assert(is_string($value), "Invalid pattern " . $pattern); return $value; } From 32a3f9b7383f3985bd690c89b5035e74243bfa7f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:08:20 +0200 Subject: [PATCH 15/21] Fix some psalms --- php/bin/gherkin-generate-tokens | 4 +++- php/src/GherkinDialectProvider.php | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/php/bin/gherkin-generate-tokens b/php/bin/gherkin-generate-tokens index 61a3a1897..89318716c 100755 --- a/php/bin/gherkin-generate-tokens +++ b/php/bin/gherkin-generate-tokens @@ -17,9 +17,11 @@ $parser = new Parser(new TokenFormatterBuilder()); array_shift($argv); foreach($argv as $fileName) { + $contents = file_get_contents($fileName); + assert(is_string($contents), "Could not read " . $fileName); $result = $parser->parse( $fileName, - new StringTokenScanner(file_get_contents($fileName)), + new StringTokenScanner($contents), new TokenMatcher(), ); echo $result; diff --git a/php/src/GherkinDialectProvider.php b/php/src/GherkinDialectProvider.php index bd0d23091..cc4f31d08 100644 --- a/php/src/GherkinDialectProvider.php +++ b/php/src/GherkinDialectProvider.php @@ -33,6 +33,9 @@ public function __construct( */ $contents = file_get_contents(self::JSON_PATH); assert(is_string($contents), "Could not read " . self::JSON_PATH); + /** + * @var $data non-empty-array + */ $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); $this->DIALECTS = $data; } catch (JsonException $e) { From 8700240921962a0c02569c4c10caec3034a087eb Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:10:26 +0200 Subject: [PATCH 16/21] Fix some psalms --- php/src/GherkinDocumentBuilder.php | 2 +- php/src/StringUtils.php | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/php/src/GherkinDocumentBuilder.php b/php/src/GherkinDocumentBuilder.php index 60c8082f2..4e9ef1085 100644 --- a/php/src/GherkinDocumentBuilder.php +++ b/php/src/GherkinDocumentBuilder.php @@ -315,7 +315,7 @@ private function transformDescriptionNode(AstNode $node): string { $lineTokens = $node->getTokenMatches(TokenType::Other); - $lineText = preg_replace( + $lineText = (string) preg_replace( '/(\\n\\s*)*$/u', '', $this->joinMatchedTextWithLinebreaks($lineTokens), diff --git a/php/src/StringUtils.php b/php/src/StringUtils.php index f6ba0372c..a2c5b26e9 100644 --- a/php/src/StringUtils.php +++ b/php/src/StringUtils.php @@ -34,33 +34,25 @@ public static function substring(string $string, int $start, int $length = null) public static function rtrim(string $string): string { $pattern = '/' . self::WHITESPACE_PATTERN . '$/u'; - $value = preg_replace($pattern, '', $string); - assert(is_string($value), "Invalid pattern " . $pattern); - return $value; + return (string) preg_replace($pattern, '', $string); } public static function rtrimKeepNewLines(string $string): string { $pattern = '/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u'; - $value = preg_replace($pattern, '', $string); - assert(is_string($value), "Invalid pattern " . $pattern); - return $value; + return (string) preg_replace($pattern, '', $string); } public static function ltrim(string $string): string { $pattern = '/^' . self::WHITESPACE_PATTERN . '/u'; - $value = preg_replace($pattern, '', $string); - assert(is_string($value), "Invalid pattern " . $pattern); - return $value; + return (string) preg_replace($pattern, '', $string); } public static function ltrimKeepNewLines(string $string): string { $pattern = '/^' . self::WHITESPACE_PATTERN_NO_NEWLINE . '/u'; - $value = preg_replace($pattern, '', $string); - assert(is_string($value), "Invalid pattern " . $pattern); - return $value; + return (string) preg_replace($pattern, '', $string); } public static function trim(string $string): string @@ -72,8 +64,7 @@ public static function trim(string $string): string public static function replace(string $string, array $replacements): string { $patterns = array_map(fn ($p) => '/' . preg_quote($p) . '/u', array_keys($replacements)); - - return preg_replace($patterns, array_values($replacements), $string); + return (string) preg_replace($patterns, array_values($replacements), $string); } /** @return array */ From 050310cc34adf2ad8c736e07224925439f0f4c8c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:11:18 +0200 Subject: [PATCH 17/21] Fix some psalms --- php/src/StringGherkinLine.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/php/src/StringGherkinLine.php b/php/src/StringGherkinLine.php index 6b0cb4a82..ae6c8b856 100644 --- a/php/src/StringGherkinLine.php +++ b/php/src/StringGherkinLine.php @@ -23,11 +23,13 @@ public function __construct( $this->indent = StringUtils::symbolCount($lineText) - StringUtils::symbolCount(StringUtils::ltrim($lineText)); } + #[\Override] public function indent(): int { return $this->indent; } + #[\Override] public function getLineText(int $indentToRemove): string { if ($indentToRemove < 0 || $indentToRemove > $this->indent) { @@ -38,6 +40,7 @@ public function getLineText(int $indentToRemove): string } /** @param non-empty-string $keyword */ + #[\Override] public function startsWithTitleKeyword(string $keyword): bool { $textLength = StringUtils::symbolCount($keyword); @@ -51,22 +54,26 @@ public function startsWithTitleKeyword(string $keyword): bool ) === GherkinLanguageConstants::TITLE_KEYWORD_SEPARATOR; } + #[\Override] public function getRestTrimmed(int $length): string { return StringUtils::trim(StringUtils::substring($this->trimmedLineText, $length)); } + #[\Override] public function isEmpty(): bool { return StringUtils::symbolCount($this->trimmedLineText) === 0; } + #[\Override] public function startsWith(string $string): bool { return StringUtils::startsWith($this->trimmedLineText, $string); } /** @return list */ + #[\Override] public function getTableCells(): array { /** @@ -111,6 +118,7 @@ function ($groups) { } /** @return list */ + #[\Override] public function getTags(): array { $uncommentedLine = preg_replace('/\s' . preg_quote(GherkinLanguageConstants::COMMENT_PREFIX) . '.*$/u', '', $this->trimmedLineText); From cdcec9ba400b1cab4ede21d246045e4fcecce0da Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:12:15 +0200 Subject: [PATCH 18/21] Fix some psalms --- php/src/StringTokenScanner.php | 1 + php/src/TokenFormatterBuilder.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/php/src/StringTokenScanner.php b/php/src/StringTokenScanner.php index d7c079511..0e7801efe 100644 --- a/php/src/StringTokenScanner.php +++ b/php/src/StringTokenScanner.php @@ -24,6 +24,7 @@ public function __construct( ) { } + #[\Override] public function read(): Token { if (preg_match(self::FIRST_LINE_PATTERN, $this->source, $matches)) { diff --git a/php/src/TokenFormatterBuilder.php b/php/src/TokenFormatterBuilder.php index a96b4f978..443be3ca8 100644 --- a/php/src/TokenFormatterBuilder.php +++ b/php/src/TokenFormatterBuilder.php @@ -22,25 +22,30 @@ public function __construct() $this->tokenFormatter = new TokenFormatter(); } + #[\Override] public function build(Token $token): void { $this->lines[] = $this->tokenFormatter->formatToken($token); } + #[\Override] public function startRule(RuleType $ruleType): void { } + #[\Override] public function endRule(RuleType $ruleType): void { } + #[\Override] public function getResult(): string { // implode at the end is more efficient than repeated concat return implode("\n", [...$this->lines, '']); } + #[\Override] public function reset(string $uri): void { } From f9ff94c7609761f2acdaeb993b52caa32b181edc Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:13:04 +0200 Subject: [PATCH 19/21] Fix some psalms --- php/psalm.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/php/psalm.xml b/php/psalm.xml index 47ad1a129..f94934800 100644 --- a/php/psalm.xml +++ b/php/psalm.xml @@ -31,12 +31,6 @@ - - - - - - From 1ca31e8b51b7e25749503ba1e8caa9583828eeee Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:16:47 +0200 Subject: [PATCH 20/21] Fix some psalms --- php/src/GherkinDialectProvider.php | 7 ++----- php/src/StringGherkinLine.php | 2 +- php/src/TokenMatcher.php | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/php/src/GherkinDialectProvider.php b/php/src/GherkinDialectProvider.php index cc4f31d08..ade7887f7 100644 --- a/php/src/GherkinDialectProvider.php +++ b/php/src/GherkinDialectProvider.php @@ -25,17 +25,14 @@ public function __construct( private readonly string $defaultDialectName = 'en', ) { try { + $contents = file_get_contents(self::JSON_PATH); + assert(is_string($contents), "Could not read " . self::JSON_PATH); /** * Here we force the type checker to assume the decoded JSON has the correct * structure, rather than validating it. This is safe because it's not dynamic * * @var non-empty-array $data */ - $contents = file_get_contents(self::JSON_PATH); - assert(is_string($contents), "Could not read " . self::JSON_PATH); - /** - * @var $data non-empty-array - */ $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); $this->DIALECTS = $data; } catch (JsonException $e) { diff --git a/php/src/StringGherkinLine.php b/php/src/StringGherkinLine.php index ae6c8b856..72dcbc754 100644 --- a/php/src/StringGherkinLine.php +++ b/php/src/StringGherkinLine.php @@ -121,7 +121,7 @@ function ($groups) { #[\Override] public function getTags(): array { - $uncommentedLine = preg_replace('/\s' . preg_quote(GherkinLanguageConstants::COMMENT_PREFIX) . '.*$/u', '', $this->trimmedLineText); + $uncommentedLine = (string) preg_replace('/\s' . preg_quote(GherkinLanguageConstants::COMMENT_PREFIX) . '.*$/u', '', $this->trimmedLineText); /** * @var list $elements guaranteed by PREG_SPLIT_OFFSET_CAPTURE diff --git a/php/src/TokenMatcher.php b/php/src/TokenMatcher.php index 33aa91f26..5bc7505c8 100644 --- a/php/src/TokenMatcher.php +++ b/php/src/TokenMatcher.php @@ -23,6 +23,7 @@ public function __construct( $this->reset(); } + #[\Override] public function reset(): void { $this->currentDialect = $this->dialectProvider->getDefaultDialect(); From 96e1e50f9381405ed1671c8cc3e24ae9fad4fa70 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 30 Jun 2025 22:18:01 +0200 Subject: [PATCH 21/21] Fix some psalms --- php/src/StringGherkinLine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/src/StringGherkinLine.php b/php/src/StringGherkinLine.php index 72dcbc754..1cad6fd7e 100644 --- a/php/src/StringGherkinLine.php +++ b/php/src/StringGherkinLine.php @@ -98,7 +98,7 @@ function ($match) { // Match \N and then replace based on what X is // done this way so that \\n => \n once and isn't then recursively replaced again (or similar) - $unescaped = preg_replace_callback( + $unescaped = (string) preg_replace_callback( '/(\\\\.)/u', function ($groups) { return match ($groups[0]) {