Skip to content
This repository was archived by the owner on Feb 15, 2025. It is now read-only.

Commit 52f4ade

Browse files
committed
Add --only-signature mode and ability to use cache file.
1 parent e82e378 commit 52f4ade

File tree

3 files changed

+142
-22
lines changed

3 files changed

+142
-22
lines changed

PhpDocblockChecker/CheckerCommand.php

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class CheckerCommand extends Command
4141
*/
4242
protected $warnings = [];
4343

44+
/**
45+
* @var array
46+
*/
47+
protected $infos = [];
48+
4449
/**
4550
* @var array
4651
*/
@@ -61,11 +66,26 @@ class CheckerCommand extends Command
6166
*/
6267
protected $skipSignatures = false;
6368

69+
/**
70+
* @var bool
71+
*/
72+
protected $onlySignatures = false;
73+
6474
/**
6575
* @var bool
6676
*/
6777
protected $fromStdin = false;
6878

79+
/**
80+
* @var string
81+
*/
82+
protected $cacheFile;
83+
84+
/**
85+
* @var array
86+
*/
87+
protected $cache;
88+
6989
/**
7090
* @var OutputInterface
7191
*/
@@ -87,11 +107,13 @@ protected function configure()
87107
->addOption('skip-classes', null, InputOption::VALUE_NONE, 'Don\'t check classes for docblocks.')
88108
->addOption('skip-methods', null, InputOption::VALUE_NONE, 'Don\'t check methods for docblocks.')
89109
->addOption('skip-signatures', null, InputOption::VALUE_NONE, 'Don\'t check docblocks against method signatures.')
110+
->addOption('only-signatures', null, InputOption::VALUE_NONE, 'Ignore missing docblocks where method doesn\'t have parameters or return type.')
90111
->addOption('json', 'j', InputOption::VALUE_NONE, 'Output JSON instead of a log.')
91112
->addOption('files-per-line', 'l', InputOption::VALUE_REQUIRED, 'Number of files per line in progress', 50)
92113
->addOption('fail-on-warnings', 'w', InputOption::VALUE_NONE, 'Consider the check failed if any warnings are produced.')
93114
->addOption('info-only', 'i', InputOption::VALUE_NONE, 'Information-only mode, just show summary.')
94-
->addOption('from-stdin', null, InputOption::VALUE_NONE, 'Use list of files from stdin (e.g. git diff)');
115+
->addOption('from-stdin', null, InputOption::VALUE_NONE, 'Use list of files from stdin (e.g. git diff)')
116+
->addOption('cache-file', null, InputOption::VALUE_REQUIRED, 'Cache analysis of files based on filemtime.');
95117
}
96118

97119
/**
@@ -111,7 +133,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
111133
$this->skipClasses = $input->getOption('skip-classes');
112134
$this->skipMethods = $input->getOption('skip-methods');
113135
$this->skipSignatures = $input->getOption('skip-signatures');
136+
$this->onlySignatures = $input->getOption('only-signatures');
114137
$this->fromStdin = $input->getOption('from-stdin');
138+
$this->cacheFile = $input->getOption('cache-file');
115139
$failOnWarnings = $input->getOption('fail-on-warnings');
116140
$startTime = microtime(true);
117141

@@ -125,6 +149,16 @@ protected function execute(InputInterface $input, OutputInterface $output)
125149
$this->basePath .= '/';
126150
}
127151

152+
// Fix conflicting options:
153+
if ($this->onlySignatures) {
154+
$this->skipSignatures = false;
155+
}
156+
157+
// Load cache from file if set:
158+
if (!empty($this->cacheFile) && file_exists($this->cacheFile)) {
159+
$this->cache = json_decode(file_get_contents($this->cacheFile), true);
160+
}
161+
128162
// Get files to check:
129163
$files = [];
130164

@@ -182,6 +216,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
182216
$this->output->write('<info>' . number_format($this->passed) . ' Passed</info>');
183217
$this->output->write(' / <fg=red>'.number_format(count($this->errors)).' Errors</>');
184218
$this->output->write(' / <fg=yellow>'.number_format(count($this->warnings)).' Warnings</>');
219+
$this->output->write(' / <fg=blue>'.number_format(count($this->infos)).' Info</>');
185220

186221
$this->output->writeln('');
187222

@@ -204,6 +239,25 @@ protected function execute(InputInterface $input, OutputInterface $output)
204239
}
205240
}
206241

242+
if (count($this->infos) && !$input->getOption('info-only')) {
243+
$this->output->writeln('');
244+
$this->output->writeln('');
245+
246+
foreach ($this->infos as $info) {
247+
$this->output->write('<fg=blue>INFO </> ' . $info['file'] . ':' . $info['line'] . ' - ');
248+
249+
if ($info['type'] == 'class') {
250+
$this->output->write('Class <info>' . $info['class'] . '</info> is missing a docblock.');
251+
}
252+
253+
if ($info['type'] == 'method') {
254+
$this->output->write('Method <info>' . $info['method'] . '</info> is missing a docblock.');
255+
}
256+
257+
$this->output->writeln('');
258+
}
259+
}
260+
207261
if (count($this->warnings) && !$input->getOption('info-only')) {
208262
foreach ($this->warnings as $error) {
209263
$this->output->write('<fg=yellow>WARNING </> ');
@@ -236,6 +290,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
236290
print json_encode(array_merge($this->errors, $this->warnings));
237291
}
238292

293+
294+
// Write to cache file:
295+
if (!empty($this->cacheFile)) {
296+
@file_put_contents($this->cacheFile, json_encode($this->cache));
297+
}
298+
239299
return count($this->errors) || ($failOnWarnings && count($this->warnings)) ? 1 : 0;
240300
}
241301

@@ -301,22 +361,36 @@ protected function processStdin(array &$worklist = [])
301361

302362
/**
303363
* Check a specific PHP file for errors.
304-
* @param string $file
364+
* @param string $fileName
305365
* @return array
306366
*/
307-
protected function processFile($file)
367+
protected function processFile($fileName)
308368
{
309369
$errors = false;
310370
$warnings = false;
311-
$processor = new FileProcessor($this->basePath . $file);
371+
$fullPath = $this->basePath . $fileName;
372+
373+
if (empty($this->cache[$fullPath]) || filemtime($fullPath) > $this->cache[$fullPath]['mtime']) {
374+
$processor = new FileProcessor($fullPath);
375+
376+
$file = [];
377+
$file['mtime'] = filemtime($fullPath);
378+
$file['classes'] = $processor->getClasses();
379+
$file['methods'] = $processor->getMethods();
380+
381+
$this->cache[$fullPath] = $file;
382+
383+
}
384+
385+
$file = $this->cache[$fullPath];
312386

313387
if (!$this->skipClasses) {
314-
foreach ($processor->getClasses() as $name => $class) {
388+
foreach ($file['classes'] as $name => $class) {
315389
if (is_null($class['docblock'])) {
316390
$errors = true;
317391
$this->errors[] = [
318392
'type' => 'class',
319-
'file' => $file,
393+
'file' => $fileName,
320394
'class' => $name,
321395
'line' => $class['line'],
322396
];
@@ -325,22 +399,41 @@ protected function processFile($file)
325399
}
326400

327401
if (!$this->skipMethods) {
328-
foreach ($processor->getMethods() as $name => $method) {
402+
foreach ($file['methods'] as $name => $method) {
403+
$treatAsError = true;
404+
405+
if ($this->onlySignatures) {
406+
if ((empty($method['params']) || 0 === count($method['params'])) && false === $method['has_return']) {
407+
$treatAsError = false;
408+
}
409+
}
410+
329411
if (is_null($method['docblock'])) {
330-
$errors = true;
331-
$this->errors[] = [
332-
'type' => 'method',
333-
'file' => $file,
334-
'class' => $name,
335-
'method' => $name,
336-
'line' => $method['line'],
337-
];
412+
if (true === $treatAsError) {
413+
$errors = true;
414+
415+
$this->errors[] = [
416+
'type' => 'method',
417+
'file' => $fileName,
418+
'class' => $name,
419+
'method' => $name,
420+
'line' => $method['line'],
421+
];
422+
} else {
423+
$this->infos[] = [
424+
'type' => 'method',
425+
'file' => $fileName,
426+
'class' => $name,
427+
'method' => $name,
428+
'line' => $method['line'],
429+
];
430+
}
338431
}
339432
}
340433
}
341434

342435
if (!$this->skipSignatures) {
343-
foreach ($processor->getMethods() as $name => $method) {
436+
foreach ($file['methods'] as $name => $method) {
344437
// If the docblock is inherited, we can't check for params and return types:
345438
if (isset($method['docblock']['inherit']) && $method['docblock']['inherit']) {
346439
continue;
@@ -352,7 +445,7 @@ protected function processFile($file)
352445
$warnings = true;
353446
$this->warnings[] = [
354447
'type' => 'param-missing',
355-
'file' => $file,
448+
'file' => $fileName,
356449
'class' => $name,
357450
'method' => $name,
358451
'line' => $method['line'],
@@ -372,7 +465,7 @@ protected function processFile($file)
372465
$warnings = true;
373466
$this->warnings[] = [
374467
'type' => 'param-mismatch',
375-
'file' => $file,
468+
'file' => $fileName,
376469
'class' => $name,
377470
'method' => $name,
378471
'line' => $method['line'],
@@ -392,7 +485,7 @@ protected function processFile($file)
392485
$warnings = true;
393486
$this->warnings[] = [
394487
'type' => 'return-missing',
395-
'file' => $file,
488+
'file' => $fileName,
396489
'class' => $name,
397490
'method' => $name,
398491
'line' => $method['line'],
@@ -404,7 +497,7 @@ protected function processFile($file)
404497
$warnings = true;
405498
$this->warnings[] = [
406499
'type' => 'return-mismatch',
407-
'file' => $file,
500+
'file' => $fileName,
408501
'class' => $name,
409502
'method' => $name,
410503
'line' => $method['line'],
@@ -419,7 +512,7 @@ protected function processFile($file)
419512
$warnings = true;
420513
$this->warnings[] = [
421514
'type' => 'return-mismatch',
422-
'file' => $file,
515+
'file' => $fileName,
423516
'class' => $name,
424517
'method' => $name,
425518
'line' => $method['line'],

PhpDocblockChecker/FileProcessor.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ protected function processStatements(array $statements, $prefix = '')
125125
'return' => $type,
126126
'params' => [],
127127
'docblock' => $this->getDocblock($method, $uses),
128+
'has_return' => isset($method->stmts) ? $this->statementsContainReturn($method->stmts) : false,
128129
];
129130

130131
foreach ($method->params as $param) {
@@ -144,7 +145,7 @@ protected function processStatements(array $statements, $prefix = '')
144145

145146
$type = substr($type, 0, 1) == '\\' ? substr($type, 1) : $type;
146147

147-
if (!is_null($type) && ('null' === $param->default->name->parts[0] || $param->type instanceof NullableType)) {
148+
if ((isset($param->default->name->parts) && !is_null($type) && ('null' === $param->default->name->parts[0]) || $param->type instanceof NullableType)) {
148149
$type = $type . '|null';
149150
}
150151

@@ -157,6 +158,30 @@ protected function processStatements(array $statements, $prefix = '')
157158
}
158159
}
159160

161+
/**
162+
* Recursively search an array of statements for a return statement.
163+
* @param array $statements
164+
* @return bool
165+
*/
166+
protected function statementsContainReturn(array $statements)
167+
{
168+
foreach ($statements as $statement) {
169+
if ($statement instanceof Stmt\Return_) {
170+
return true;
171+
}
172+
173+
if (empty($statement->stmts)) {
174+
continue;
175+
}
176+
177+
if ($this->statementsContainReturn($statement->stmts)) {
178+
return true;
179+
}
180+
}
181+
182+
return false;
183+
}
184+
160185
/**
161186
* Find and parse a docblock for a given class or method.
162187
* @param Stmt $stmt

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ Short | Long | Description
3131
-h | --help | Display help message.
3232
-x | --exclude=EXCLUDE | Files and directories to exclude.
3333
-d | --directory=DIRECTORY | Directory to scan. [default: "./"]
34+
none | --cache-file=FILE | Use cache file to speed up processing.
3435
none | --from-stdin | Use list of files provided via stdin
3536
none | --skip-classes | Don't check classes for docblocks.
3637
none | --skip-methods | Don't check methods for docblocks.
3738
none | --skip-signatures | Don't check docblocks against method signatures.
39+
none | --only-signatures | Only check methods that have parameters or returns.
3840
-j | --json | Output JSON instead of a log.
3941
-l | --files-per-line=FILES-PER-LINE | Number of files per line in progress [default: 50]
4042
-w | --fail-on-warnings | Consider the check failed if any warnings are produced.

0 commit comments

Comments
 (0)