-
-
Save codyphobe/2d258228e4bd147a1cc1ccdf7954dde9 to your computer and use it in GitHub Desktop.
CI test that fails when any `FIXME` comments are found
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Tests\Project; | |
use RecursiveCallbackFilterIterator; | |
use RecursiveDirectoryIterator; | |
use RecursiveIteratorIterator; | |
use SplFileInfo; | |
use Tests\Constraints; | |
use Tests\TestCase; | |
class FixMeTest extends TestCase | |
{ | |
/** | |
* These are directories that won't be checked | |
* | |
* @var array | |
*/ | |
protected $exclude = [ | |
'.git', | |
'.idea', | |
'docs', | |
'node_modules', | |
'public', | |
'storage', | |
'vendor', | |
'tests/Project/FixMeTest.php', | |
'tests/Constraints/HasNoFixmes.php', | |
]; | |
/** | |
* Only check these extensions | |
* | |
* @var array | |
*/ | |
protected $extensions = [ | |
'.php', | |
'.blade.php', | |
'.js', | |
'.jsx', | |
'.less', | |
]; | |
/** | |
* @var string | |
*/ | |
protected $baseDirectory; | |
/** | |
* Set up base directory | |
*/ | |
protected function setUp(): void | |
{ | |
parent::setUp(); | |
foreach ($this->exclude as $key => $value) { | |
$this->exclude[$key] = $this->app->basePath($value); | |
} | |
} | |
/** | |
* Ensure that there are no "FIXME" statements in the project | |
*/ | |
public function test_project_has_no_fixme_comments(): void | |
{ | |
$directory_iterator = new RecursiveDirectoryIterator( | |
$this->app->basePath(), | |
RecursiveDirectoryIterator::SKIP_DOTS | |
); | |
$filtered_iterator = new RecursiveIteratorIterator( | |
new RecursiveCallbackFilterIterator($directory_iterator, function(SplFileInfo $file, $key, RecursiveDirectoryIterator $iterator) { | |
// Exclude root directories | |
if (starts_with($file->getPathname(), $this->exclude)) { | |
return false; | |
} | |
// Allow iteration | |
if ($iterator->hasChildren()) { | |
return true; | |
} | |
// Only include our file types | |
if (! ends_with($file->getFilename(), $this->extensions)) { | |
return false; | |
} | |
return $file->isFile(); | |
}) | |
); | |
$this->assertThat($filtered_iterator, new Constraints\HasNoFixmes($this->app->basePath())); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Tests\Constraints; | |
use PHPUnit\Framework\Constraint\Constraint; | |
class HasNoFixmes extends Constraint | |
{ | |
protected string $base_path; | |
protected $fixme_comments = []; | |
protected $ran = false; | |
public function __construct(string $base_path) | |
{ | |
$this->base_path = $base_path; | |
} | |
public function toString(): string | |
{ | |
return 'has no FIXME comments'; | |
} | |
protected function matches($iterator): bool | |
{ | |
if (! $this->ran) { | |
foreach ($iterator as $pathname => $file_info) { | |
$content = file_get_contents($pathname); | |
if (stripos($content, 'fixme') === false) { | |
continue; | |
} | |
$comments = stripos($pathname, '.php') === false | |
? $this->readGenericComments($content) | |
: $this->readPhpComments($content); | |
if (count($comments)) { | |
$this->fixme_comments[$pathname] = (object) [ | |
'filename' => str_replace($this->base_path, '', $pathname), | |
'comments' => $comments, | |
]; | |
} | |
} | |
$this->ran = true; | |
} | |
return empty($this->fixme_comments); | |
} | |
protected function readPhpComments(string $code): array | |
{ | |
$tokens = token_get_all($code); | |
return collect($tokens) | |
->filter(function($token) { | |
return is_array($token); | |
}) | |
->map(function($token) { | |
return (object) [ | |
'type' => $token[0], | |
'source_code' => $token[1], | |
'line_number' => $token[2], | |
]; | |
}) | |
->filter(function($token) { | |
return T_COMMENT === $token->type || T_DOC_COMMENT === $token->type; | |
}) | |
->filter(function($token) { | |
return stripos($token->source_code, 'fixme') !== false; | |
}) | |
->map(function($token) { | |
$trim_syntax = '~(^\s*(?://\s?|/\*\s?|\*\s)|\*/\s*$|FIXME:?\s?)~m'; | |
$comment = trim(preg_replace($trim_syntax, '', $token->source_code)); | |
return (object) [ | |
'line_number' => $token->line_number, | |
'comment' => $comment, | |
]; | |
}) | |
->toArray(); | |
} | |
protected function readGenericComments(string $code): array | |
{ | |
return collect(explode(PHP_EOL, $code)) | |
->map(function($code, $line_number) { | |
return (object) [ | |
'source_code' => (string) $code, | |
'line_number' => $line_number, | |
]; | |
}) | |
->filter(function($line) { | |
return stripos($line->source_code, 'fixme') !== false; | |
}) | |
->map(function($line) { | |
$trim_syntax = '~(^\s*(?://\s?|/\*\s?|\*\s)|\*/\s*$|FIXME:?\s?)~m'; | |
$comment = trim(preg_replace($trim_syntax, '', $line->source_code)); | |
return (object) [ | |
'line_number' => $line->line_number, | |
'comment' => (string) $comment, | |
]; | |
}) | |
->toArray(); | |
} | |
protected function failureDescription($other): string | |
{ | |
return 'the project has no FIXME comments'; | |
} | |
protected function additionalFailureDescription($other): string | |
{ | |
return collect($this->fixme_comments) | |
->map(function($file) { | |
$comments = collect($file->comments) | |
->map(function($comment) { | |
$text = str_replace("\n", ' ', trim($comment->comment)); | |
return empty($text) | |
? "Line {$comment->line_number}" | |
: "{$comment->line_number}: {$text}"; | |
}); | |
if (1 === count($comments)) { | |
return " - {$file->filename}: {$comments->first()}"; | |
} | |
return " - {$file->filename}\n + ".$comments->implode("\n + "); | |
}) | |
->implode("\n"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment