Skip to content

Commit bd4c8fc

Browse files
authored
Merge pull request #30 from exussum12/docblockErrors
First pass at using related errors in phpcs
2 parents 027879f + 3496eaa commit bd4c8fc

15 files changed

+454
-20
lines changed

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ matrix:
1717
- php: hhvm
1818
fast_finish: true
1919
before_script:
20+
- composer global require squizlabs/php_codesniffer
21+
- composer global require phpmd/phpmd
2022
- sh -c "[ -z $UPDATE_COVERAGE ] && phpenv config-rm xdebug.ini || true"
2123
script:
22-
- ./build.sh
24+
- PATH=$HOME/.composer/vendor/bin:$PATH ./build.sh

CreatePhar.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
$phar->addFile('autoload.php');
1313
$phar->addFile('bin/diffFilter');
1414

15-
$code = realpath(__DIR__.'/src');
15+
$code = realpath(__DIR__ . '/src');
1616
$codeLength = strlen($code);
1717
$directory = new RecursiveDirectoryIterator(
1818
$code,

build.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ git log origin/master... | grep -q SKIP_BUILD && exit 0
77

88
composer install --dev
99
git diff $(git merge-base origin/master HEAD) > diff.txt
10-
./vendor/bin/phpcs --standard=psr2 src
11-
./vendor/bin/phpcs --standard=psr2 --ignore=bootstrap.php,fixtures/* tests
10+
phpcs --standard=psr2 src
11+
phpcs --standard=psr2 --ignore=bootstrap.php,fixtures/* tests
1212

13-
./vendor/bin/phpmd src xml cleancode,codesize,controversial,unusedcode
14-
./vendor/bin/phpmd tests xml cleancode,codesize,controversial,unusedcode
13+
phpmd src text cleancode,codesize,controversial,unusedcode
14+
phpmd tests text cleancode,codesize,controversial,unusedcode --exclude fixtures
1515

1616
./vendor/bin/phpunit
1717

composer.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
"name": "exussum12/coverage-checker",
33
"description": "Allows checking the code coverage of a single pull request",
44
"require-dev": {
5-
"phpunit/phpunit": "^5.7",
6-
"squizlabs/php_codesniffer": "^3.1",
7-
"phpmd/phpmd": "^2.6"
5+
"phpunit/phpunit": "^5.7"
86
},
97
"license": "MIT",
108
"authors": [
@@ -19,5 +17,8 @@
1917
"exussum12\\CoverageChecker\\tests\\": "tests/"
2018
}
2119
},
22-
"bin": ["bin/phpunitDiffFilter","bin/phpcsDiffFilter", "bin/phpmdDiffFilter", "bin/diffFilter"]
20+
"bin": ["bin/phpunitDiffFilter","bin/phpcsDiffFilter", "bin/phpmdDiffFilter", "bin/diffFilter"],
21+
"require": {
22+
"nikic/php-parser": "^3.1"
23+
}
2324
}

src/CodeLimits.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
namespace exussum12\CoverageChecker;
3+
4+
class CodeLimits
5+
{
6+
protected $startLine;
7+
protected $endLine;
8+
9+
public function __construct($startLine, $endLine)
10+
{
11+
$this->startLine = $startLine;
12+
$this->endLine = $endLine;
13+
}
14+
15+
public function getStartLine()
16+
{
17+
return $this->startLine;
18+
}
19+
20+
public function getEndLine()
21+
{
22+
return $this->endLine;
23+
}
24+
}

src/FileParser.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
namespace exussum12\CoverageChecker;
3+
4+
use PhpParser\Error;
5+
use PhpParser\Node;
6+
use PhpParser\Node\FunctionLike;
7+
use PhpParser\Node\Stmt\ClassLike;
8+
use PhpParser\Node\Stmt\Namespace_;
9+
use PhpParser\Parser;
10+
use PhpParser\ParserFactory;
11+
12+
class FileParser
13+
{
14+
protected $sourceCode = '';
15+
protected $classes = [];
16+
protected $functions = [];
17+
18+
public function __construct($sourceCode)
19+
{
20+
$this->sourceCode = $sourceCode;
21+
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
22+
$this->parse($parser);
23+
}
24+
25+
/**
26+
* @return CodeLimits[]
27+
*/
28+
public function getClassLimits()
29+
{
30+
return $this->classes;
31+
}
32+
33+
/**
34+
* @return CodeLimits[]
35+
*/
36+
public function getFunctionLimits()
37+
{
38+
return $this->functions;
39+
}
40+
41+
protected function parse(Parser $parser)
42+
{
43+
try {
44+
$ast = $parser->parse($this->sourceCode);
45+
} catch (Error $exception) {
46+
return;
47+
}
48+
49+
foreach ($ast as $node) {
50+
$this->handleNode($node);
51+
}
52+
}
53+
54+
protected function getCodeLimits(Node $node)
55+
{
56+
$startLine = $node->getAttribute('startLine');
57+
$endLine = $node->getAttribute('endLine');
58+
if ($node->getDocComment()) {
59+
$startLine = $node->getDocComment()->getLine();
60+
}
61+
62+
return new CodeLimits($startLine, $endLine);
63+
}
64+
65+
protected function addClass($classLimits)
66+
{
67+
$this->classes[] = $classLimits;
68+
}
69+
70+
protected function addFunction($classLimits)
71+
{
72+
$this->functions[] = $classLimits;
73+
}
74+
75+
protected function handleClass(ClassLike $node)
76+
{
77+
$this->addClass($this->getCodeLimits($node));
78+
79+
foreach ($node->getMethods() as $function) {
80+
$this->handleNode($function);
81+
}
82+
}
83+
84+
protected function handleFunction(FunctionLike $node)
85+
{
86+
$this->addFunction($this->getCodeLimits($node));
87+
}
88+
89+
private function handleNamespace(Namespace_ $node)
90+
{
91+
foreach ($node->stmts as $part) {
92+
$this->handleNode($part);
93+
}
94+
}
95+
96+
/**
97+
* @param $node
98+
*/
99+
protected function handleNode($node)
100+
{
101+
$type = $node->getType();
102+
if ($type == 'Stmt_Namespace') {
103+
$this->handleNamespace($node);
104+
}
105+
106+
if ($type == 'Stmt_Class') {
107+
$this->handleClass($node);
108+
}
109+
110+
if ($type == "Stmt_Function" || $type == "Stmt_ClassMethod") {
111+
$this->handleFunction($node);
112+
}
113+
}
114+
}

src/PhpCsLoader.php

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
namespace exussum12\CoverageChecker;
33

44
use InvalidArgumentException;
5+
use PhpParser\Error;
6+
use PhpParser\ParserFactory;
7+
use exussum12\CoverageChecker\Exceptions\FileNotFound;
58
use stdClass;
69

710
/**
@@ -28,6 +31,12 @@ class PhpCsLoader implements FileChecker
2831
'ERROR',
2932
];
3033

34+
protected $lookupErrorPrefix = [
35+
'Squiz.Commenting.FileComment',
36+
'Squiz.Commenting.ClassComment',
37+
'Squiz.Commenting.FunctionComment',
38+
];
39+
3140
/**
3241
* @var array
3342
*/
@@ -42,6 +51,17 @@ class PhpCsLoader implements FileChecker
4251
*/
4352
protected $invalidFiles = [];
4453

54+
/**
55+
* @var array
56+
*/
57+
protected $invalidRanges = [];
58+
59+
60+
/**
61+
* @var FileParser[]
62+
*/
63+
protected $parsedFiles = [];
64+
4565
/**
4666
* PhpCsLoader constructor.
4767
* @param string $filePath the file path to the json output from phpcs
@@ -68,10 +88,11 @@ public function parseLines()
6888
}
6989
}
7090

71-
return array_merge(
91+
return array_unique(array_merge(
7292
array_keys($this->invalidLines),
73-
array_keys($this->invalidFiles)
74-
);
93+
array_keys($this->invalidFiles),
94+
array_keys($this->invalidRanges)
95+
));
7596
}
7697

7798
/**
@@ -88,6 +109,8 @@ public function getErrorsOnLine($file, $lineNumber)
88109
$errors = array_merge($errors, $this->invalidLines[$file][$lineNumber]);
89110
}
90111

112+
$errors = array_merge($errors, $this->getRangeErrors($file, $lineNumber));
113+
91114
return $errors;
92115
}
93116

@@ -100,8 +123,14 @@ protected function addInvalidLine($file, $message)
100123
if (!in_array($message->type, $this->failOnTypes)) {
101124
return;
102125
}
126+
103127
$line = $message->line;
104128

129+
if ($error = $this->messageStartsWith($message->source, $this->lookupErrorPrefix)) {
130+
$this->handleLookupError($file, $message, $error);
131+
return;
132+
}
133+
105134
if (!isset($this->invalidLines[$file][$line])) {
106135
$this->invalidLines[$file][$line] = [];
107136
}
@@ -113,6 +142,51 @@ protected function addInvalidLine($file, $message)
113142
}
114143
}
115144

145+
/**
146+
* @param $message
147+
* @param array $list
148+
* @return bool|string
149+
*/
150+
protected function messageStartsWith($message, array $list)
151+
{
152+
foreach ($list as $item) {
153+
if (strpos($message, $item) === 0) {
154+
return $item;
155+
}
156+
}
157+
return false;
158+
}
159+
160+
protected function handleLookupError($file, $message, $error)
161+
{
162+
if ($error == 'Squiz.Commenting.FileComment') {
163+
$this->invalidFiles[$file][] = $message->message;
164+
}
165+
try {
166+
$fileParser = $this->getFileParser($file);
167+
$lookup = $this->getMessageRanges($error, $fileParser);
168+
169+
$this->addRangeError($file, $lookup, $message);
170+
} catch (FileNotFound $exception) {
171+
error_log("Can't find file, may have missed an error");
172+
}
173+
}
174+
175+
protected function getFileParser($filename)
176+
{
177+
if (!isset($this->parsedFiles[$filename])) {
178+
if (!file_exists($filename)) {
179+
throw new FileNotFound();
180+
}
181+
182+
$this->parsedFiles[$filename] = new FileParser(
183+
file_get_contents($filename)
184+
);
185+
}
186+
187+
return $this->parsedFiles[$filename];
188+
}
189+
116190
/**
117191
* {@inheritdoc}
118192
*/
@@ -129,4 +203,59 @@ public static function getDescription()
129203
return 'Parses the json report format of phpcs, this mode ' .
130204
'only reports errors as violations';
131205
}
206+
207+
/**
208+
* @param string $file
209+
* @param CodeLimits[] $lookup
210+
* @param stdClass $message
211+
*/
212+
protected function addRangeError($file, $lookup, $message)
213+
{
214+
$line = $message->line;
215+
foreach ($lookup as $limit) {
216+
if ($line >= $limit->getStartLine() && $line <= $limit->getEndLine()) {
217+
$this->invalidRanges[$file][] = [
218+
'from' => $limit->getStartLine(),
219+
'to' => $limit->getEndLine(),
220+
'message' => $message->message,
221+
];
222+
}
223+
}
224+
}
225+
226+
/**
227+
* @param string $error
228+
* @param FileParser $fileParser
229+
* @return mixed
230+
*/
231+
protected function getMessageRanges($error, $fileParser)
232+
{
233+
if ($error == 'Squiz.Commenting.ClassComment') {
234+
return $fileParser->getClassLimits();
235+
}
236+
237+
return $fileParser->getFunctionLimits();
238+
}
239+
240+
/**
241+
* @param string $file
242+
* @param int $lineNumber
243+
* @return array errors on the line
244+
*/
245+
protected function getRangeErrors($file, $lineNumber)
246+
{
247+
$errors = [];
248+
249+
if (!empty($this->invalidRanges[$file])) {
250+
foreach ($this->invalidRanges[$file] as $invalidRange) {
251+
$inRange = $lineNumber >= $invalidRange['from'] &&
252+
$lineNumber <= $invalidRange['to'];
253+
if ($inRange) {
254+
$errors[] = $invalidRange['message'];
255+
}
256+
}
257+
}
258+
259+
return $errors;
260+
}
132261
}

0 commit comments

Comments
 (0)