1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Add error message when psalter asked to work beyond its abilities; allow fixing all issues and output list of fixable issues (#1687)

* Oputput error message when asked to fix non-fixable issue

* Document ability to fix function PossiblyUndefinedGlobalVariable

* Add --issues=all option to fix all possible issues

* Add --list-supported-issues option to psalter

* Fix psalm issues

* Add newline to end of psalter help output

* Adjust messages output from psalter

* Use fwrite(STDERR, instead of die( for issue list related errors in psalter

* Restore missing exits in psalter
This commit is contained in:
Barney Laurance 2019-05-27 15:05:15 +01:00 committed by Matthew Brown
parent 5861476765
commit 04ef20da1f
4 changed files with 141 additions and 7 deletions

View File

@ -36,7 +36,8 @@ The above example plugin converts all unnecessarily qualified classnames in your
## Supported fixes
This initial release provides support for the following alterations, corresponding to the names of issues Psalm finds:
This initial release provides support for the following alterations, corresponding to the names of issues Psalm finds.
To fix all of these at once, run `vendor/bin/psalter --issues=all`
### MissingReturnType
@ -252,6 +253,34 @@ function foo() : string {
Running `vendor/bin/psalter --issues=PossiblyUndefinedVariable` on
```php
function foo()
{
if (rand(0, 1)) {
$a = 5;
}
echo $a;
}
```
gives
```php
function foo()
{
$a = null;
if (rand(0, 1)) {
$a = 5;
}
echo $a;
}
```
### PossiblyUndefinedGlobalVariable
Running `vendor/bin/psalter --issues=PossiblyUndefinedGlobalVariable` on
```php
if (rand(0, 1)) {
$a = 5;

View File

@ -0,0 +1,8 @@
<?php
namespace Psalm\Exception;
class UnsupportedIssueToFixException extends \Exception
{
}

View File

@ -4,15 +4,32 @@ namespace Psalm\Internal\Analyzer;
use Psalm\Codebase;
use Psalm\Config;
use Psalm\Context;
use Psalm\Exception\UnsupportedIssueToFixException;
use Psalm\Internal\LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter};
use Psalm\Internal\Provider\ClassLikeStorageProvider;
use Psalm\Internal\Provider\FileProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\ParserCacheProvider;
use Psalm\Internal\Provider\Providers;
use Psalm\Issue\InvalidFalsableReturnType;
use Psalm\Issue\InvalidNullableReturnType;
use Psalm\Issue\InvalidReturnType;
use Psalm\Issue\LessSpecificReturnType;
use Psalm\Issue\MismatchingDocblockParamType;
use Psalm\Issue\MismatchingDocblockReturnType;
use Psalm\Issue\MissingClosureReturnType;
use Psalm\Issue\MissingParamType;
use Psalm\Issue\MissingReturnType;
use Psalm\Issue\PossiblyUndefinedGlobalVariable;
use Psalm\Issue\PossiblyUndefinedVariable;
use Psalm\Issue\PossiblyUnusedMethod;
use Psalm\Issue\PossiblyUnusedProperty;
use Psalm\Issue\UnusedMethod;
use Psalm\Issue\UnusedProperty;
use Psalm\Progress\Progress;
use Psalm\Progress\VoidProgress;
use Psalm\Type;
use Psalm\Issue\CodeIssue;
/**
* @internal
@ -156,6 +173,27 @@ class ProjectAnalyzer
self::TYPE_TEXT,
];
/**
* @var array<int, class-string<CodeIssue>>
*/
const SUPPORTED_ISSUES_TO_FIX = [
InvalidFalsableReturnType::class,
InvalidNullableReturnType::class,
InvalidReturnType::class,
LessSpecificReturnType::class,
MismatchingDocblockParamType::class,
MismatchingDocblockReturnType::class,
MissingClosureReturnType::class,
MissingParamType::class,
MissingReturnType::class,
PossiblyUndefinedGlobalVariable::class,
PossiblyUndefinedVariable::class,
PossiblyUnusedMethod::class,
PossiblyUnusedProperty::class,
UnusedMethod::class,
UnusedProperty::class,
];
/**
* @param bool $use_color
* @param bool $show_info
@ -795,14 +833,34 @@ class ProjectAnalyzer
/**
* @param array<string, bool> $issues
* @throws UnsupportedIssueToFixException
*
* @return void
*/
public function setIssuesToFix(array $issues)
{
$supported_issues_to_fix = static::getSupportedIssuesToFix();
$unsupportedIssues = array_diff(array_keys($issues), $supported_issues_to_fix);
if (! empty($unsupportedIssues)) {
throw new UnsupportedIssueToFixException(
'Psalm doesn\'t know how to fix issue(s): ' . implode(', ', $unsupportedIssues) . PHP_EOL
. 'Supported issues to fix are: ' . implode(',', $supported_issues_to_fix)
);
}
$this->issues_to_fix = $issues;
}
public function setAllIssuesToFix(): void
{
/** @var array<string, true> $keyed_issues */
$keyed_issues = array_fill_keys(static::getSupportedIssuesToFix(), true);
$this->setIssuesToFix($keyed_issues);
}
/**
* @return array<string, bool>
*
@ -954,4 +1012,19 @@ class ProjectAnalyzer
throw new \LogicException('failed to detect number of CPUs!');
}
/**
* @return array<string>
*/
public static function getSupportedIssuesToFix(): array
{
return array_map(
/** @param class-string $issue_class */
function (string $issue_class): string {
$parts = explode('\\', $issue_class);
return end($parts);
},
self::SUPPORTED_ISSUES_TO_FIX
);
}
}

View File

@ -21,7 +21,7 @@ $args = array_slice($argv, 1);
$valid_short_options = ['f:', 'm', 'h', 'r:'];
$valid_long_options = [
'help', 'debug', 'debug-by-line', 'config:', 'file:', 'root:',
'plugin:', 'issues:', 'php-version:', 'dry-run', 'safe-types',
'plugin:', 'issues:', 'list-supported-issues', 'php-version:', 'dry-run', 'safe-types',
'find-unused-code', 'threads:', 'codeowner:',
'allow-backwards-incompatible-changes:',
];
@ -116,7 +116,11 @@ Options:
--php-version=PHP_MAJOR_VERSION.PHP_MINOR_VERSION
--issues=IssueType1,IssueType2
If any issues can be fixed automatically, Psalm will update the codebase
If any issues can be fixed automatically, Psalm will update the codebase. To fix as many issues as possible,
use --issues=all
--list-supported-issues
Display the list of issues that psalter knows how to fix
--find-unused-code
Include unused code as a candidate for removal
@ -129,14 +133,19 @@ Options:
--allow-backwards-incompatible-changes=BOOL
Allow Psalm modify method signatures that could break code outside the project. Defaults to true.
HELP;
exit;
}
if (!isset($options['issues']) && (!isset($options['plugin']) || $options['plugin'] === false)) {
die('Please specify the issues you want to fix with --issues=IssueOne,IssueTwo '
. 'or provide a plugin that has its own manipulations with --plugin=path/to/plugin.php' . PHP_EOL);
if (!isset($options['issues']) &&
!isset($options['list-supported-issues']) &&
(!isset($options['plugin']) || $options['plugin'] === false)
) {
fwrite(STDERR, 'Please specify the issues you want to fix with --issues=IssueOne,IssueTwo or --issues=all, ' .
'or provide a plugin that has its own manipulations with --plugin=path/to/plugin.php' . PHP_EOL);
exit(1);
}
if (isset($options['root'])) {
@ -189,6 +198,11 @@ $providers = new Psalm\Internal\Provider\Providers(
new Psalm\Internal\Provider\ClassLikeStorageCacheProvider($config)
);
if (array_key_exists('list-supported-issues', $options)) {
echo implode(',', ProjectAnalyzer::getSupportedIssuesToFix()) . PHP_EOL;
exit();
}
$debug = array_key_exists('debug', $options);
$progress = $debug
? new DebugProgress()
@ -373,7 +387,17 @@ $project_analyzer->alterCodeAfterCompletion(
array_key_exists('dry-run', $options),
array_key_exists('safe-types', $options)
);
$project_analyzer->setIssuesToFix($keyed_issues);
if ($keyed_issues === ['all' => true]) {
$project_analyzer->setAllIssuesToFix();
} else {
try {
$project_analyzer->setIssuesToFix($keyed_issues);
} catch (\Psalm\Exception\UnsupportedIssueToFixException $e) {
fwrite(STDERR, $e->getMessage() . PHP_EOL);
exit(1);
}
}
$start_time = microtime(true);