Skip to content

Commit b0b05d2

Browse files
committed
Files can have defined minimal php version for linting
If the system's PHP version doesn't match the rule then the file is skipped. The rule must be on the first line in the file, right after the opening php tag ```php <?php // lint >= 5.4 ```
1 parent 055c3a6 commit b0b05d2

File tree

11 files changed

+163
-18
lines changed

11 files changed

+163
-18
lines changed

bin/skip-linting.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
$file = end($_SERVER['argv']);
4+
if ($file === $_SERVER['PHP_SELF'] || !file_exists($file)) {
5+
exit(1);
6+
}
7+
8+
$f = fopen($file, 'r');
9+
$firstLine = fgets($f);
10+
@fclose($f);
11+
12+
if (!preg_match('~<?php\\s*\\/\\/\s*lint\s*([^\d\s]+)\s*([^\s]+)\s*~i', $firstLine, $m)) {
13+
exit(0);
14+
}
15+
16+
exit(isset($m[2]) && version_compare(PHP_VERSION, $m[2], $m[1]) != 1 ? 2 : 0);

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"php": ">=5.3.3"
88
},
99
"require-dev": {
10-
"nette/tester": "v1.1.0"
10+
"nette/tester": "v1.1.0",
11+
"jakub-onderka/php-console-highlighter": "@dev"
1112
},
1213
"suggest": {
1314
"jakub-onderka/php-console-highlighter": "Highlight syntax in code snippet"

src/Manager.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public function run(Settings $settings = null)
7474
$parallelLint->setProcessCallback(function ($status, $file) use ($output) {
7575
if ($status === ParallelLint::STATUS_OK) {
7676
$output->ok();
77-
} else if ($status === ParallelLint::STATUS_ERROR) {
77+
} elseif ($status === ParallelLint::STATUS_SKIP) {
78+
$output->skip();
79+
} elseif ($status === ParallelLint::STATUS_ERROR) {
7880
$output->error();
7981
} else {
8082
$output->fail();
@@ -87,14 +89,21 @@ public function run(Settings $settings = null)
8789

8890
$testTime = round($result->getTestTime(), 1);
8991
$message = "Checked {$result->getCheckedFiles()} files in $testTime second, ";
92+
93+
if ($result->getSkippedFiles() > 0) {
94+
$message .= "skipped {$result->getSkippedFiles()} ";
95+
$message .= ($result->getSkippedFiles() === 1 ? 'file' : 'files');
96+
$message .= ", ";
97+
}
98+
9099
if ($result->hasSyntaxError()) {
91100
$message .= "no syntax error found";
92101
} else {
93102
$message .= "syntax error found in {$result->getFilesWithSyntaxError()} ";
94103
$message .= ($result->getFilesWithSyntaxError() === 1 ? 'file' : 'files');
95104
}
96105

97-
$output->writeLine($message, $result->hasSyntaxError() === 0 ? Output::TYPE_OK : Output::TYPE_ERROR);
106+
$output->writeLine($message, $result->hasSyntaxError() ? Output::TYPE_ERROR : Output::TYPE_OK);
98107

99108
if ($result->hasError()) {
100109
$errorFormatter = new ErrorFormatter($settings->colors, $translateTokens);
@@ -169,7 +178,7 @@ protected function getFilesFromPaths(array $paths, array $extensions, array $exc
169178
foreach ($paths as $path) {
170179
if (is_file($path)) {
171180
$files[] = $path;
172-
} else if (is_dir($path)) {
181+
} elseif (is_dir($path)) {
173182
$iterator = new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS);
174183
if (!empty($excluded)) {
175184
$iterator = new RecursiveDirectoryFilterIterator($iterator, $excluded);

src/Output.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
class Output
3434
{
3535
const TYPE_DEFAULT = 'default',
36+
TYPE_SKIP = 'skip',
3637
TYPE_ERROR = 'error',
3738
TYPE_OK = 'ok';
3839

@@ -62,6 +63,12 @@ public function ok()
6263
$this->progress();
6364
}
6465

66+
public function skip()
67+
{
68+
$this->write('S', self::TYPE_SKIP);
69+
$this->progress();
70+
}
71+
6572
public function error()
6673
{
6774
$this->write('X', self::TYPE_ERROR);
@@ -160,6 +167,10 @@ public function write($string, $type = self::TYPE_DEFAULT)
160167
parent::write($this->colors->apply('bg_green', $string));
161168
break;
162169

170+
case self::TYPE_SKIP:
171+
parent::write($this->colors->apply('bg_yellow', $string));
172+
break;
173+
163174
case self::TYPE_ERROR:
164175
parent::write($this->colors->apply('bg_red', $string));
165176
break;

src/ParallelLint.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class Result
4141
/** @var int */
4242
private $filesWithSyntaxError;
4343

44+
/** @var int */
45+
private $skippedFiles;
46+
4447
/** @var float */
4548
private $testTime;
4649

@@ -50,11 +53,12 @@ class Result
5053
* @param int $filesWithSyntaxError
5154
* @param float $testTime
5255
*/
53-
public function __construct(array $errors, $checkedFiles, $filesWithSyntaxError, $testTime)
56+
public function __construct(array $errors, $checkedFiles, $filesWithSyntaxError, $skippedFiles, $testTime)
5457
{
5558
$this->errors = $errors;
5659
$this->checkedFiles = $checkedFiles;
5760
$this->filesWithSyntaxError = $filesWithSyntaxError;
61+
$this->skippedFiles = $skippedFiles;
5862
$this->testTime = $testTime;
5963
}
6064

@@ -82,6 +86,14 @@ public function getCheckedFiles()
8286
return $this->checkedFiles;
8387
}
8488

89+
/**
90+
* @return int
91+
*/
92+
public function getSkippedFiles()
93+
{
94+
return $this->skippedFiles;
95+
}
96+
8597
/**
8698
* @return int
8799
*/
@@ -110,6 +122,7 @@ public function getTestTime()
110122
class ParallelLint
111123
{
112124
const STATUS_OK = 'ok',
125+
STATUS_SKIP = 'skip',
113126
STATUS_FAIL = 'fail',
114127
STATUS_ERROR = 'error';
115128

@@ -143,10 +156,8 @@ public function lint(array $files)
143156
$processCallback = is_callable($this->processCallback) ? $this->processCallback : function() {};
144157

145158
/** @var LintProcess[] $running */
146-
$running = array();
147-
$errors = array();
148-
$checkedFiles = 0;
149-
$filesWithSyntaxError = 0;
159+
$errors = $running = array();
160+
$skippedFiles = $checkedFiles = $filesWithSyntaxError = 0;
150161

151162
$startTime = microtime(true);
152163

@@ -172,7 +183,10 @@ public function lint(array $files)
172183
$processCallback(self::STATUS_FAIL, $file);
173184
} else {
174185
$checkedFiles++;
175-
if ($process->hasSyntaxError()) {
186+
if ($process->isSkipped()) {
187+
$skippedFiles++;
188+
$processCallback(self::STATUS_SKIP, $file);
189+
} elseif ($process->hasSyntaxError()) {
176190
$errors[] = new SyntaxError($file, $process->getSyntaxError());
177191
$filesWithSyntaxError++;
178192
$processCallback(self::STATUS_ERROR, $file);
@@ -186,7 +200,7 @@ public function lint(array $files)
186200

187201
$testTime = microtime(true) - $startTime;
188202

189-
return new Result($errors, $checkedFiles, $filesWithSyntaxError, $testTime);
203+
return new Result($errors, $checkedFiles, $filesWithSyntaxError, $skippedFiles, $testTime);
190204
}
191205

192206
/**
@@ -283,4 +297,4 @@ public function setShortTagEnabled($shortTagEnabled)
283297

284298
return $this;
285299
}
286-
}
300+
}

src/Process.php

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,15 @@ public function __construct($cmdLine)
8484
*/
8585
public function isFinished()
8686
{
87+
if ($this->statusCode !== NULL) {
88+
return true;
89+
}
90+
8791
$status = proc_get_status($this->process);
8892

8993
if ($status['running']) {
9094
return false;
91-
} else if ($this->statusCode === null) {
95+
} elseif ($this->statusCode === null) {
9296
$this->statusCode = (int) $status['exitcode'];
9397
}
9498

@@ -159,6 +163,10 @@ protected function terminateAndReadOutput()
159163

160164
class LintProcess extends Process
161165
{
166+
167+
/** @var SkipLintProcess */
168+
protected $skipProcess;
169+
162170
/**
163171
* @param string $phpExecutable
164172
* @param string $fileToCheck Path to file to check
@@ -174,6 +182,8 @@ public function __construct($phpExecutable, $fileToCheck, $aspTags = false, $sho
174182
throw new \InvalidArgumentException("File to check must be set.");
175183
}
176184

185+
$this->skipProcess = new SkipLintProcess($phpExecutable, $fileToCheck, $aspTags, $shortTag);
186+
177187
$cmdLine = escapeshellarg($phpExecutable);
178188
$cmdLine .= ' -d asp_tags=' . ($aspTags ? 'On' : 'Off');
179189
$cmdLine .= ' -d short_open_tag=' . ($shortTag ? 'On' : 'Off');
@@ -183,6 +193,20 @@ public function __construct($phpExecutable, $fileToCheck, $aspTags = false, $sho
183193
parent::__construct($cmdLine);
184194
}
185195

196+
/**
197+
* @return bool
198+
*/
199+
public function isFinished()
200+
{
201+
return parent::isFinished() && $this->skipProcess->isFinished();
202+
}
203+
204+
protected function terminateAndReadOutput()
205+
{
206+
$this->skipProcess->terminateAndReadOutput();
207+
parent::terminateAndReadOutput();
208+
}
209+
186210
/**
187211
* @return bool
188212
*/
@@ -209,10 +233,56 @@ public function getSyntaxError()
209233
*/
210234
public function isFail()
211235
{
212-
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
213-
return $this->getStatusCode() === 1;
214-
} else {
215-
return parent::isFail();
236+
$fail = defined('PHP_WINDOWS_VERSION_MAJOR') ? $this->getStatusCode() === 1 : parent::isFail();
237+
if (!$fail) {
238+
return FALSE;
216239
}
240+
241+
return !$this->skipProcess->isSkipped();
242+
}
243+
244+
/**
245+
* @return bool
246+
*/
247+
public function isSkipped()
248+
{
249+
return $this->skipProcess->isSkipped();
217250
}
218251
}
252+
253+
254+
255+
class SkipLintProcess extends Process
256+
{
257+
258+
/**
259+
* @param string $phpExecutable
260+
* @param string $fileToCheck Path to file to check
261+
* @param bool $aspTags
262+
* @param bool $shortTag
263+
*/
264+
public function __construct($phpExecutable, $fileToCheck, $aspTags = false, $shortTag = false)
265+
{
266+
if (empty($phpExecutable)) {
267+
throw new \InvalidArgumentException("PHP executable must be set.");
268+
}
269+
if (empty($fileToCheck)) {
270+
throw new \InvalidArgumentException("File to check must be set.");
271+
}
272+
273+
$cmdLine = escapeshellarg($phpExecutable);
274+
$cmdLine .= ' -d asp_tags=' . ($aspTags ? 'On' : 'Off');
275+
$cmdLine .= ' -d short_open_tag=' . ($shortTag ? 'On' : 'Off');
276+
$cmdLine .= ' -d error_reporting=E_ALL';
277+
$cmdLine .= ' -n ' . escapeshellarg(__DIR__ . '/../bin/skip-linting.php');
278+
$cmdLine .= ' ' . escapeshellarg($fileToCheck);
279+
280+
parent::__construct($cmdLine);
281+
}
282+
283+
public function isSkipped()
284+
{
285+
return $this->getStatusCode() === 2;
286+
}
287+
288+
}

tests/Manager.run.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?php
22

3+
/**
4+
* @testCase
5+
*/
6+
37
require_once __DIR__ . '/../vendor/nette/tester/Tester/bootstrap.php';
48
require_once __DIR__ . '/../src/Error.php';
59
require_once __DIR__ . '/../src/ErrorFormatter.php';

tests/ParallelLint.lint.phpt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?php
22

3+
/**
4+
* @testCase
5+
*/
6+
37
require_once __DIR__ . '/../vendor/nette/tester/Tester/bootstrap.php';
48
require_once __DIR__ . '/../src/Error.php';
59
require_once __DIR__ . '/../src/ParallelLint.php';
@@ -105,4 +109,4 @@ class ParallelLintLintTest extends Tester\TestCase
105109
}
106110

107111
$testCase = new ParallelLintLintTest;
108-
$testCase->run();
112+
$testCase->run();

tests/Settings.parseArguments.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?php
22

3+
/**
4+
* @testCase
5+
*/
6+
37
require __DIR__ . '/../vendor/nette/tester/Tester/bootstrap.php';
48
require_once __DIR__ . '/../src/Settings.php';
59

tests/skip-on-5.3/class.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
class ExampleClass
4+
{
5+
6+
}

tests/skip-on-5.3/trait.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php // lint >= 5.4
2+
3+
trait ExampleTrait
4+
{
5+
6+
}

0 commit comments

Comments
 (0)