Skip to content

Commit dd89f62

Browse files
authored
Upgrade the PHP static analysis to Psalm 6 (#504)
1 parent fc8c21f commit dd89f62

File tree

8 files changed

+77
-23
lines changed

8 files changed

+77
-23
lines changed

php/bin/gherkin

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,22 @@ $options = ['predictable-ids', 'no-source', 'no-ast', 'no-pickles'];
1313

1414
$selectedOptions = getopt('', $options, $restIndex);
1515

16+
assert($selectedOptions !== false, "getopt returns false only when it cannot find the argv array.");
17+
1618
$paths = array_slice($argv, $restIndex);
1719

1820
// lazily-read list of Sources
1921
$sources = (
2022
/** @param list<string> $paths */
2123
function (array $paths) {
2224
foreach ($paths as $path) {
23-
yield new Source(uri: $path, data: file_get_contents($path));
25+
$data = file_get_contents($path);
26+
27+
if ($data === false) {
28+
throw new RuntimeException(sprintf('Unable to read file "%s".', $path));
29+
}
30+
31+
yield new Source(uri: $path, data: $data);
2432
}
2533
}
2634
)($paths);
@@ -34,5 +42,6 @@ $parser = new GherkinParser(
3442
$envelopes = $parser->parse($sources);
3543

3644
$output = fopen('php://stdout', 'w');
45+
assert($output !== false);
3746
$writer = NdJsonStreamWriter::fromFileHandle($output);
3847
$writer->writeEnvelopes($envelopes);

php/bin/gherkin-generate-tokens

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ $parser = new Parser(new TokenFormatterBuilder());
1717
array_shift($argv);
1818

1919
foreach($argv as $fileName) {
20+
$input = file_get_contents($fileName);
21+
22+
if ($input === false) {
23+
throw new \RuntimeException(\sprintf('Could not read file "%s".', $fileName));
24+
}
25+
2026
$result = $parser->parse(
2127
$fileName,
22-
new StringTokenScanner(file_get_contents($fileName)),
28+
new StringTokenScanner($input),
2329
new TokenMatcher(),
2430
);
2531
echo $result;

php/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
},
2020
"require-dev": {
2121
"phpunit/phpunit": "^10.5 || ^11.0",
22-
"vimeo/psalm": "5.26.1",
22+
"vimeo/psalm": "6.13.1",
2323
"friendsofphp/php-cs-fixer": "^3.51",
24-
"psalm/plugin-phpunit": "^0.19.0"
24+
"psalm/plugin-phpunit": "^0.19.5"
2525
}
2626
}

php/psalm.xml

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
resolveFromConfigFile="true"
55
findUnusedBaselineEntry="true"
66
findUnusedCode="true"
7+
ensureOverrideAttribute="false"
78
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
89
xmlns="https://getpsalm.org/schema/config"
910
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
@@ -25,18 +26,6 @@
2526
<file name="src-generated/Parser.php"/>
2627
</errorLevel>
2728
</TypeDoesNotContainType>
28-
<InvalidReturnType>
29-
<errorLevel type="suppress">
30-
<!-- See https://github.com/vimeo/psalm/issues/8572 -->
31-
<file name="src/AstNode.php"/>
32-
</errorLevel>
33-
</InvalidReturnType>
34-
<InvalidReturnStatement>
35-
<errorLevel type="suppress">
36-
<!-- See https://github.com/vimeo/psalm/issues/8572 -->
37-
<file name="src/AstNode.php"/>
38-
</errorLevel>
39-
</InvalidReturnStatement>
4029
</issueHandlers>
4130
<plugins>
4231
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>

php/src/GherkinDialectProvider.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ public function __construct(
2727
try {
2828
/**
2929
* Here we force the type checker to assume the decoded JSON has the correct
30-
* structure, rather than validating it. This is safe because it's not dynamic
30+
* structure, rather than validating it. This is safe because it's not dynamic.
31+
* We also assume that reading the file won't fail as this is a file shipped in
32+
* the package.
3133
*
3234
* @var non-empty-array<non-empty-string, Dialect> $data
35+
* @psalm-suppress PossiblyFalseArgument
3336
*/
3437
$data = json_decode(file_get_contents(self::JSON_PATH), true, flags: JSON_THROW_ON_ERROR);
3538
$this->DIALECTS = $data;

php/src/GherkinDocumentBuilder.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Cucumber\Messages\TableRow;
2727
use Cucumber\Messages\Tag;
2828
use LogicException;
29+
use RuntimeException;
2930

3031
/**
3132
* @implements Builder<GherkinDocument>
@@ -316,6 +317,10 @@ private function transformDescriptionNode(AstNode $node): string
316317
$this->joinMatchedTextWithLinebreaks($lineTokens),
317318
);
318319

320+
if ($lineText === null) {
321+
throw new RuntimeException('Failed to trim the description: ' . preg_last_error_msg());
322+
}
323+
319324
return $lineText;
320325
}
321326

php/src/StringGherkinLine.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Cucumber\Gherkin;
66

7+
use RuntimeException;
8+
79
final class StringGherkinLine implements GherkinLine
810
{
911
// TODO: set this to 0 when/if we change to 0-indexed columns
@@ -93,7 +95,7 @@ function ($match) {
9395
// done this way so that \\n => \n once and isn't then recursively replaced again (or similar)
9496
$unescaped = preg_replace_callback(
9597
'/(\\\\.)/u',
96-
function ($groups) {
98+
function (array $groups) {
9799
return match ($groups[0]) {
98100
'\\n' => "\n",
99101
'\\\\' => '\\',
@@ -104,6 +106,10 @@ function ($groups) {
104106
$trimmedCell,
105107
);
106108

109+
if ($unescaped === null) {
110+
throw new \RuntimeException('Failed to decode escape sequences in doc string: ' . preg_last_error_msg());
111+
}
112+
107113
return new GherkinLineSpan($cellStart + $cellIndent + self::OFFSET, $unescaped);
108114
},
109115
$splitCells,
@@ -115,6 +121,10 @@ public function getTags(): array
115121
{
116122
$uncommentedLine = preg_replace('/\s' . preg_quote(GherkinLanguageConstants::COMMENT_PREFIX) . '.*$/u', '', $this->trimmedLineText);
117123

124+
if ($uncommentedLine === null) {
125+
throw new RuntimeException('Failed to strip comments: ' . preg_last_error_msg());
126+
}
127+
118128
/**
119129
* @var list<array{0:string, 1:int}> $elements guaranteed by PREG_SPLIT_OFFSET_CAPTURE
120130
*/

php/src/StringUtils.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Cucumber\Gherkin;
66

7+
use RuntimeException;
8+
79
/**
810
* Keeps common string operations in one place using correct unicode functions
911
* (and normalises naming with other implementations)
@@ -33,22 +35,46 @@ public static function substring(string $string, int $start, int $length = null)
3335

3436
public static function rtrim(string $string): string
3537
{
36-
return preg_replace('/' . self::WHITESPACE_PATTERN . '$/u', '', $string);
38+
$trimmed = preg_replace('/' . self::WHITESPACE_PATTERN . '$/u', '', $string);
39+
40+
if ($trimmed === null) {
41+
throw new RuntimeException('Failed to trim the string: ' . preg_last_error_msg());
42+
}
43+
44+
return $trimmed;
3745
}
3846

3947
public static function rtrimKeepNewLines(string $string): string
4048
{
41-
return preg_replace('/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u', '', $string);
49+
$trimmed = preg_replace('/' . self::WHITESPACE_PATTERN_NO_NEWLINE . '$/u', '', $string);
50+
51+
if ($trimmed === null) {
52+
throw new RuntimeException('Failed to trim the string: ' . preg_last_error_msg());
53+
}
54+
55+
return $trimmed;
4256
}
4357

4458
public static function ltrim(string $string): string
4559
{
46-
return preg_replace('/^'. self::WHITESPACE_PATTERN . '/u', '', $string);
60+
$trimmed = preg_replace('/^'. self::WHITESPACE_PATTERN . '/u', '', $string);
61+
62+
if ($trimmed === null) {
63+
throw new RuntimeException('Failed to trim the string: ' . preg_last_error_msg());
64+
}
65+
66+
return $trimmed;
4767
}
4868

4969
public static function ltrimKeepNewLines(string $string): string
5070
{
51-
return preg_replace('/^'. self::WHITESPACE_PATTERN_NO_NEWLINE . '/u', '', $string);
71+
$trimmed = preg_replace('/^'. self::WHITESPACE_PATTERN_NO_NEWLINE . '/u', '', $string);
72+
73+
if ($trimmed === null) {
74+
throw new RuntimeException('Failed to trim the string: ' . preg_last_error_msg());
75+
}
76+
77+
return $trimmed;
5278
}
5379

5480
public static function trim(string $string): string
@@ -61,7 +87,13 @@ public static function replace(string $string, array $replacements): string
6187
{
6288
$patterns = array_map(fn ($p) => '/' . preg_quote($p) . '/u', array_keys($replacements));
6389

64-
return preg_replace($patterns, array_values($replacements), $string);
90+
$replaced = preg_replace($patterns, array_values($replacements), $string);
91+
92+
if ($replaced === null) {
93+
throw new RuntimeException('Failed to replace patterns in string: ' . preg_last_error_msg());
94+
}
95+
96+
return $replaced;
6597
}
6698

6799
/** @return array<non-empty-string> */

0 commit comments

Comments
 (0)