Skip to content

Commit 140d16f

Browse files
authored
Support custom comments (#5)
1 parent 023c1e8 commit 140d16f

File tree

7 files changed

+172
-11
lines changed

7 files changed

+172
-11
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ composer require --dev shipmonk/phpstan-ignore-inliner
4141
vendor/bin/phpstan --error-format=json | vendor/bin/inline-phpstan-ignores
4242
```
4343

44+
## Cli options
45+
- `--comment`: Adds a comment to all inlined ignores, resulting in `// @phpstan-ignore empty.notAllowed (the comment)`
46+
4447
## Contributing
4548
- Check your code by `composer check`
4649
- Autofix coding-style by `composer fix:cs`

bin/inline-phpstan-ignores

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ $usage = "\n\nUsage:\n$ vendor/bin/phpstan --error-format=json | vendor/bin/inli
2222
try {
2323
$io = new Io();
2424
$input = $io->readInput();
25+
$comment = $io->readCliComment($argv);
2526
$errorsData = json_decode($input, associative: true, flags: JSON_THROW_ON_ERROR);
2627
$errors = $errorsData['files'] ?? throw new FailureException('No \'files\' key found on input JSON.');
2728

2829
$inliner = new InlineIgnoreInliner($io);
29-
$inliner->inlineErrors($errors);
30+
$inliner->inlineErrors($errors, $comment);
3031

3132
$errorsCount = count($errors);
3233
echo "Done, $errorsCount errors processed.\n";
34+
exit(0);
3335

3436
} catch (JsonException | FailureException $e) {
35-
echo $e->getMessage() . $usage;
37+
echo 'ERROR: ' . $e->getMessage() . $usage;
38+
exit(1);
3639
}

src/InlineIgnoreInliner.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public function __construct(private Io $io)
2121
* @throws FailureException
2222
*/
2323
public function inlineErrors(
24-
array $errors
24+
array $errors,
25+
?string $comment
2526
): void
2627
{
2728
foreach ($errors as $filePath => $fileErrors) {
@@ -40,10 +41,13 @@ public function inlineErrors(
4041

4142
$lineContent = rtrim($lines[$line - 1]);
4243
$lineEnding = substr($lines[$line - 1], strlen($lineContent));
44+
$resolvedComment = $comment === null
45+
? ''
46+
: " ($comment)";
4347

4448
$append = str_contains($lineContent, '// @phpstan-ignore ')
45-
? ', ' . $identifier
46-
: ' // @phpstan-ignore ' . $identifier;
49+
? ', ' . $identifier . $resolvedComment
50+
: ' // @phpstan-ignore ' . $identifier . $resolvedComment;
4751

4852
$lines[$line - 1] = $lineContent . $append . $lineEnding;
4953
$this->io->writeFile($trueFilePath, implode('', $lines));

src/Io.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,50 @@
22

33
namespace ShipMonk\PHPStan\Errors;
44

5+
use function array_slice;
56
use function file;
67
use function file_put_contents;
8+
use function getopt;
9+
use function in_array;
10+
use function is_array;
11+
use function is_string;
12+
use function str_starts_with;
713
use function stream_get_contents;
814
use const STDIN;
915

1016
class Io
1117
{
1218

19+
/**
20+
* @param array<string> $argv
21+
* @throws FailureException
22+
*/
23+
public function readCliComment(array $argv): ?string
24+
{
25+
foreach (array_slice($argv, 1) as $arg) {
26+
if (str_starts_with($arg, '--') && !str_starts_with($arg, '--comment')) {
27+
throw new FailureException('Unexpected option: ' . $arg);
28+
}
29+
}
30+
31+
$options = getopt('', ['comment:']);
32+
$comment = $options['comment'] ?? null;
33+
34+
if (is_string($comment)) {
35+
return $comment;
36+
}
37+
38+
if (is_array($comment)) {
39+
throw new FailureException('Only one comment can be provided.');
40+
}
41+
42+
if (in_array('--comment', $argv, true)) {
43+
throw new FailureException('Missing comment value for --comment option.');
44+
}
45+
46+
return null;
47+
}
48+
1349
/**
1450
* @throws FailureException
1551
*/

tests/InlineIgnoreInlinerTest.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ class InlineIgnoreInlinerTest extends TestCase
1616
{
1717

1818
#[DataProvider('lineEndingProvider')]
19-
public function testInlineErrors(string $lineEnding): void
19+
public function testInlineErrors(string $lineEnding, ?string $comment): void
2020
{
2121
$tmpFilePath = sys_get_temp_dir() . '/' . uniqid('ignore', true) . '.php';
2222
$tmpExpectedPath = sys_get_temp_dir() . '/' . uniqid('ignore-expected', true) . '.php';
2323

24+
$expectedFileName = $comment !== null
25+
? 'test.fixed.comment.php'
26+
: 'test.fixed.php';
27+
2428
$testContent = $this->getTestFileContent('test.php', $lineEnding);
25-
$expectedContent = $this->getTestFileContent('test.fixed.php', $lineEnding);
29+
$expectedContent = $this->getTestFileContent($expectedFileName, $lineEnding);
2630

2731
self::assertNotFalse(file_put_contents($tmpFilePath, $testContent));
2832
self::assertNotFalse(file_put_contents($tmpExpectedPath, $expectedContent));
@@ -43,7 +47,7 @@ public function testInlineErrors(string $lineEnding): void
4347
$testData = json_decode($testJson, associative: true)['files']; // @phpstan-ignore argument.type
4448

4549
$inliner = new InlineIgnoreInliner($ioMock);
46-
$inliner->inlineErrors($testData);
50+
$inliner->inlineErrors($testData, $comment);
4751

4852
self::assertFileEquals($tmpExpectedPath, $tmpFilePath);
4953
}
@@ -57,13 +61,15 @@ private function getTestFileContent(string $filename, string $lineEnding): strin
5761
}
5862

5963
/**
60-
* @return array<string, array{string}>
64+
* @return array<string, array{string, ?string}>
6165
*/
6266
public static function lineEndingProvider(): array
6367
{
6468
return [
65-
'Unix line endings' => ["\n"],
66-
'Windows line endings' => ["\r\n"],
69+
'Unix line endings' => ["\n", null],
70+
'Unix line endings + comment' => ["\n", 'some comment'],
71+
'Windows line endings' => ["\r\n", null],
72+
'Windows line endings + comment' => ["\r\n", 'some comment'],
6773
];
6874
}
6975

tests/IoTest.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ShipMonk\PHPStan\Errors;
4+
5+
use PHPUnit\Framework\Attributes\DataProvider;
6+
use PHPUnit\Framework\TestCase;
7+
use function fclose;
8+
use function fwrite;
9+
use function is_array;
10+
use function is_resource;
11+
use function proc_close;
12+
use function proc_open;
13+
use function stream_get_contents;
14+
15+
class IoTest extends TestCase
16+
{
17+
18+
/**
19+
* @param list<string> $args
20+
*/
21+
#[DataProvider('optionsProvider')]
22+
public function testValidCliOptions(int $exitCode, string $input, array $args, string $expectedOutput): void
23+
{
24+
$result = $this->runCliCommand($args, $input);
25+
self::assertSame($exitCode, $result['exitCode']);
26+
self::assertStringContainsString($expectedOutput, $result['stdout']);
27+
}
28+
29+
/**
30+
* @return array<string, array{int, string, array<string>, string}>
31+
*/
32+
public static function optionsProvider(): array
33+
{
34+
$validInput = '{"files": {}}';
35+
36+
return [
37+
'no input' => [1, '', [], 'ERROR: Nothing found on input'],
38+
'invalid json' => [1, 'invalid json', [], 'ERROR: Syntax error'],
39+
40+
'no options' => [0, $validInput, [], 'Done, 0 errors processed'],
41+
'with comment' => [0, $validInput, ['--comment=test comment'], 'Done, 0 errors processed'],
42+
'with single word comment' => [0, $validInput, ['--comment=test'], 'Done, 0 errors processed'],
43+
44+
'unexpected option' => [1, $validInput, ['--invalid'], 'ERROR: Unexpected option: --invalid'],
45+
'comment without value' => [1, $validInput, ['--comment'], 'ERROR: Missing comment value for --comment option'],
46+
'multiple comments' => [1, $validInput, ['--comment=first', '--comment=second'], 'ERROR: Only one comment can be provided'],
47+
];
48+
}
49+
50+
/**
51+
* @param list<string> $args
52+
* @return array{exitCode: int, stdout: string, stderr: string}
53+
*/
54+
private function runCliCommand(array $args, string $input): array
55+
{
56+
$binaryPath = __DIR__ . '/../bin/inline-phpstan-ignores';
57+
$command = ['php', $binaryPath, ...$args];
58+
59+
$process = proc_open(
60+
$command,
61+
[
62+
0 => ['pipe', 'r'], // stdin
63+
1 => ['pipe', 'w'], // stdout
64+
2 => ['pipe', 'w'], // stderr
65+
],
66+
$pipes,
67+
);
68+
69+
if (!is_resource($process)) {
70+
self::fail('Failed to start process');
71+
}
72+
73+
if (!is_array($pipes) || !isset($pipes[0], $pipes[1], $pipes[2])) {
74+
self::fail('Failed to create pipes');
75+
}
76+
77+
/** @var array{0: resource, 1: resource, 2: resource} $pipes */
78+
fwrite($pipes[0], $input);
79+
fclose($pipes[0]);
80+
81+
$stdout = stream_get_contents($pipes[1]);
82+
$stderr = stream_get_contents($pipes[2]);
83+
fclose($pipes[1]);
84+
fclose($pipes[2]);
85+
86+
$exitCode = proc_close($process);
87+
88+
self::assertNotFalse($stdout, 'Failed to read stdout');
89+
self::assertNotFalse($stderr, 'Failed to read stderr');
90+
91+
return [
92+
'exitCode' => $exitCode,
93+
'stdout' => $stdout,
94+
'stderr' => $stderr,
95+
];
96+
}
97+
98+
}

tests/data/test.fixed.comment.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types = 1);
2+
3+
class Dummy
4+
{
5+
6+
public function test($a, $b): void // @phpstan-ignore missingType.parameter, missingType.parameter (some comment)
7+
{
8+
return null; // @phpstan-ignore return.void (some comment)
9+
}
10+
11+
}

0 commit comments

Comments
 (0)