Skip to content

Commit 83312f6

Browse files
authored
Generic/Syntax: add support for inspecting code passed via STDIN (#1151)
* Generic/Syntax: add support for inspecting code passed via STDIN This commit improves the Generic.PHP.Syntax sniff to make it work when code is passed via STDIN. Before, any code passed this way would cause a false negative as the sniff was not taking into account that `$phpcsFile->getFilename()` might return `STDIN` instead of an actual file name. The test is not run on Windows as STDIN is currently not supported on Windows.
1 parent b1bf062 commit 83312f6

File tree

2 files changed

+140
-4
lines changed

2 files changed

+140
-4
lines changed

src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@ public function process(File $phpcsFile, $stackPtr)
5656
$this->phpPath = Config::getExecutablePath('php');
5757
}
5858

59-
$fileName = escapeshellarg($phpcsFile->getFilename());
60-
$cmd = Common::escapeshellcmd($this->phpPath)." -l -d display_errors=1 -d error_prepend_string='' $fileName 2>&1";
61-
$output = shell_exec($cmd);
62-
$matches = [];
59+
$cmd = $this->getPhpLintCommand($phpcsFile);
60+
$output = shell_exec($cmd);
61+
$matches = [];
6362
if (preg_match('/^.*error:(.*) in .* on line ([0-9]+)/m', trim($output), $matches) === 1) {
6463
$error = trim($matches[1]);
6564
$line = (int) $matches[2];
@@ -72,4 +71,34 @@ public function process(File $phpcsFile, $stackPtr)
7271
}//end process()
7372

7473

74+
/**
75+
* Returns the command used to lint PHP code.
76+
*
77+
* Uses a different command when the content is provided via STDIN.
78+
*
79+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The File object.
80+
*
81+
* @return string The command used to lint PHP code.
82+
*/
83+
private function getPhpLintCommand(File $phpcsFile)
84+
{
85+
if ($phpcsFile->getFilename() === 'STDIN') {
86+
$content = $phpcsFile->getTokensAsString(0, $phpcsFile->numTokens);
87+
return sprintf(
88+
"echo %s | %s -l -d display_errors=1 -d error_prepend_string='' 2>&1",
89+
escapeshellarg($content),
90+
Common::escapeshellcmd($this->phpPath)
91+
);
92+
}
93+
94+
$fileName = escapeshellarg($phpcsFile->getFilename());
95+
return sprintf(
96+
"%s -l -d display_errors=1 -d error_prepend_string='' %s 2>&1",
97+
Common::escapeshellcmd($this->phpPath),
98+
$fileName
99+
);
100+
101+
}//end getPhpLintCommand()
102+
103+
75104
}//end class

src/Standards/Generic/Tests/PHP/SyntaxUnitTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
namespace PHP_CodeSniffer\Standards\Generic\Tests\PHP;
1212

13+
use PHP_CodeSniffer\Files\DummyFile;
14+
use PHP_CodeSniffer\Ruleset;
15+
use PHP_CodeSniffer\Tests\ConfigDouble;
1316
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
1417

1518
/**
@@ -60,4 +63,108 @@ public function getWarningList()
6063
}//end getWarningList()
6164

6265

66+
/**
67+
* Test the sniff checks syntax when file contents are passed via STDIN.
68+
*
69+
* Note: this test doesn't run on Windows as PHPCS currently doesn't support STDIN on this OS.
70+
*
71+
* @param string $content The content to test.
72+
* @param int $errorCount The expected number of errors.
73+
* @param array $expectedErrors The expected errors.
74+
*
75+
* @dataProvider dataStdIn
76+
* @requires OS ^(?!WIN).*
77+
*
78+
* @return void
79+
*/
80+
public function testStdIn($content, $errorCount, $expectedErrors)
81+
{
82+
$config = new ConfigDouble();
83+
$config->standards = ['Generic'];
84+
$config->sniffs = ['Generic.PHP.Syntax'];
85+
86+
$ruleset = new Ruleset($config);
87+
88+
$file = new DummyFile($content, $ruleset, $config);
89+
$file->process();
90+
91+
$this->assertSame(
92+
$errorCount,
93+
$file->getErrorCount(),
94+
'Error count does not match expected value'
95+
);
96+
$this->assertSame(
97+
0,
98+
$file->getWarningCount(),
99+
'Warning count does not match expected value'
100+
);
101+
$this->assertSame(
102+
$expectedErrors,
103+
$file->getErrors(),
104+
'Error list does not match expected errors'
105+
);
106+
107+
}//end testStdIn()
108+
109+
110+
/**
111+
* Data provider for testStdIn().
112+
*
113+
* @return array[]
114+
*/
115+
public function dataStdIn()
116+
{
117+
// The error message changed in PHP 8+.
118+
if (PHP_VERSION_ID >= 80000) {
119+
$errorMessage = 'PHP syntax error: syntax error, unexpected token ";", expecting "]"';
120+
} else {
121+
$errorMessage = 'PHP syntax error: syntax error, unexpected \';\', expecting \']\'';
122+
}
123+
124+
return [
125+
'No syntax errors' => [
126+
'<?php $array = [1, 2, 3];',
127+
0,
128+
[],
129+
],
130+
'One syntax error' => [
131+
'<?php $array = [1, 2, 3; // Missing closing bracket.',
132+
1,
133+
[
134+
1 => [
135+
1 => [
136+
0 => [
137+
'message' => $errorMessage,
138+
'source' => 'Generic.PHP.Syntax.PHPSyntax',
139+
'listener' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\PHP\\SyntaxSniff',
140+
'severity' => 5,
141+
'fixable' => false,
142+
],
143+
],
144+
],
145+
],
146+
],
147+
'Single error reported even when there is more than one syntax error in the file' => [
148+
'<?php $array = [1, 2, 3; // Missing closing bracket.
149+
$anotherArray = [4, 5, 6; // Another missing closing bracket.',
150+
1,
151+
[
152+
1 => [
153+
1 => [
154+
0 => [
155+
'message' => $errorMessage,
156+
'source' => 'Generic.PHP.Syntax.PHPSyntax',
157+
'listener' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\PHP\\SyntaxSniff',
158+
'severity' => 5,
159+
'fixable' => false,
160+
],
161+
],
162+
],
163+
],
164+
],
165+
];
166+
167+
}//end dataStdIn()
168+
169+
63170
}//end class

0 commit comments

Comments
 (0)