From 7dd771225013212aa68bc0014a006b6aac63c7de Mon Sep 17 00:00:00 2001 From: MacFJA Date: Sun, 8 Dec 2019 19:11:44 +0100 Subject: [PATCH 1/2] Close #13 - Add Phan --- .phan/config.php | 360 ++++++++++++++++++++++++++ .phpqa.yml | 2 + app/report/phan.xsl | 181 +++++++++++++ bin/phpqa-extensions.php | 4 +- composer.json | 6 +- src/Tools/Analyzer/Phan.php | 114 ++++++++ src/Tools/Analyzer/PhpAssumptions.php | 1 + src/ToolsFinder.php | 4 +- 8 files changed, 666 insertions(+), 6 deletions(-) create mode 100644 .phan/config.php create mode 100644 app/report/phan.xsl create mode 100644 src/Tools/Analyzer/Phan.php diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000..b9fdd18 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,360 @@ + NULL, + + // If enabled, missing properties will be created when + // they are first seen. If false, we'll report an + // error message if there is an attempt to write + // to a class property that wasn't explicitly + // defined. + 'allow_missing_properties' => false, + + // If enabled, null can be cast to any type and any + // type can be cast to null. Setting this to true + // will cut down on false positives. + 'null_casts_as_any_type' => false, + + // If enabled, allow null to be cast as any array-like type. + // + // This is an incremental step in migrating away from `null_casts_as_any_type`. + // If `null_casts_as_any_type` is true, this has no effect. + 'null_casts_as_array' => true, + + // If enabled, allow any array-like type to be cast to null. + // This is an incremental step in migrating away from `null_casts_as_any_type`. + // If `null_casts_as_any_type` is true, this has no effect. + 'array_casts_as_null' => true, + + // If enabled, scalars (int, float, bool, string, null) + // are treated as if they can cast to each other. + // This does not affect checks of array keys. See `scalar_array_key_cast`. + 'scalar_implicit_cast' => false, + + // If enabled, any scalar array keys (int, string) + // are treated as if they can cast to each other. + // E.g. `array` can cast to `array` and vice versa. + // Normally, a scalar type such as int could only cast to/from int and mixed. + 'scalar_array_key_cast' => true, + + // If this has entries, scalars (int, float, bool, string, null) + // are allowed to perform the casts listed. + // + // E.g. `['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]` + // allows casting null to a string, but not vice versa. + // (subset of `scalar_implicit_cast`) + 'scalar_implicit_partial' => [], + + // If enabled, Phan will warn if **any** type in a method invocation's object + // is definitely not an object, + // or if **any** type in an invoked expression is not a callable. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_method_checking' => true, + + // If enabled, Phan will warn if **any** type in the argument's union type + // cannot be cast to a type in the parameter's expected union type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_param_checking' => false, + + // If enabled, Phan will warn if **any** type in a returned value's union type + // cannot be cast to the declared return type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_return_checking' => true, + + // If enabled, Phan will warn if **any** type in a property assignment's union type + // cannot be cast to a type in the property's declared union type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_property_checking' => true, + + // If true, seemingly undeclared variables in the global + // scope will be ignored. + // + // This is useful for projects with complicated cross-file + // globals that you have no hope of fixing. + 'ignore_undeclared_variables_in_global_scope' => true, + + // Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for, + // but aren't available in the codebase, or the internal functions used to run Phan + // (may lead to false positives if an extension isn't loaded) + // + // If this is true(default), then Phan will not warn. + 'ignore_undeclared_functions_with_known_signatures' => true, + + // Backwards Compatibility Checking. This is slow + // and expensive, but you should consider running + // it before upgrading your version of PHP to a + // new version that has backward compatibility + // breaks. + // + // If you are migrating from PHP 5 to PHP 7, + // you should also look into using + // [php7cc (no longer maintained)](https://github.com/sstalle/php7cc) + // and [php7mar](https://github.com/Alexia/php7mar), + // which have different backwards compatibility checks. + 'backward_compatibility_checks' => false, + + // If true, check to make sure the return type declared + // in the doc-block (if any) matches the return type + // declared in the method signature. + 'check_docblock_signature_return_type_match' => false, + + // If true, make narrowed types from phpdoc params override + // the real types from the signature, when real types exist. + // (E.g. allows specifying desired lists of subclasses, + // or to indicate a preference for non-nullable types over nullable types) + // + // Affects analysis of the body of the method and the param types passed in by callers. + // + // (*Requires `check_docblock_signature_param_type_match` to be true*) + 'prefer_narrowed_phpdoc_param_type' => true, + + // (*Requires `check_docblock_signature_return_type_match` to be true*) + // + // If true, make narrowed types from phpdoc returns override + // the real types from the signature, when real types exist. + // + // (E.g. allows specifying desired lists of subclasses, + // or to indicate a preference for non-nullable types over nullable types) + // + // This setting affects the analysis of return statements in the body of the method and the return types passed in by callers. + 'prefer_narrowed_phpdoc_return_type' => true, + + // If enabled, check all methods that override a + // parent method to make sure its signature is + // compatible with the parent's. + // + // This check can add quite a bit of time to the analysis. + // + // This will also check if final methods are overridden, etc. + 'analyze_signature_compatibility' => true, + + // This setting maps case-insensitive strings to union types. + // + // This is useful if a project uses phpdoc that differs from the phpdoc2 standard. + // + // If the corresponding value is the empty string, + // then Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`) + // + // If the corresponding value is not empty, + // then Phan will act as though it saw the corresponding UnionTypes(s) + // when the keys show up in a UnionType of `@param`, `@return`, `@var`, `@property`, etc. + // + // This matches the **entire string**, not parts of the string. + // (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting) + // + // (These are not aliases, this setting is ignored outside of doc comments). + // (Phan does not check if classes with these names exist) + // + // Example setting: `['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']` + 'phpdoc_type_mapping' => [], + + // Set to true in order to attempt to detect dead + // (unreferenced) code. Keep in mind that the + // results will only be a guess given that classes, + // properties, constants and methods can be referenced + // as variables (like `$class->$property` or + // `$class->$method()`) in ways that we're unable + // to make sense of. + 'dead_code_detection' => false, + + // Set to true in order to attempt to detect unused variables. + // `dead_code_detection` will also enable unused variable detection. + // + // This has a few known false positives, e.g. for loops or branches. + 'unused_variable_detection' => true, + + // If true, this runs a quick version of checks that takes less + // time at the cost of not running as thorough + // of an analysis. You should consider setting this + // to true only when you wish you had more **undiagnosed** issues + // to fix in your code base. + // + // In quick-mode the scanner doesn't rescan a function + // or a method's code block every time a call is seen. + // This means that the problem here won't be detected: + // + // ```php + // false, + + // If true, then before analysis, try to simplify AST into a form + // which improves Phan's type inference in edge cases. + // + // This may conflict with `dead_code_detection`. + // When this is true, this slows down analysis slightly. + // + // E.g. rewrites `if ($a = value() && $a > 0) {...}` + // into `$a = value(); if ($a) { if ($a > 0) {...}}` + 'simplify_ast' => true, + + // Enable or disable support for generic templated + // class types. + 'generic_types_enabled' => true, + + // Override to hardcode existence and types of (non-builtin) globals in the global scope. + // Class names should be prefixed with `\`. + // + // (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`) + 'globals_type_map' => [], + + // The minimum severity level to report on. This can be + // set to `Issue::SEVERITY_LOW`, `Issue::SEVERITY_NORMAL` or + // `Issue::SEVERITY_CRITICAL`. Setting it to only + // critical issues is a good place to start on a big + // sloppy mature code base. + 'minimum_severity' => Issue::SEVERITY_LOW, + + // Add any issue types (such as `'PhanUndeclaredMethod'`) + // to this black-list to inhibit them from being reported. + 'suppress_issue_types' => [], + + // A regular expression to match files to be excluded + // from parsing and analysis and will not be read at all. + // + // This is useful for excluding groups of test or example + // directories/files, unanalyzable files, or files that + // can't be removed for whatever reason. + // (e.g. `'@Test\.php$@'`, or `'@vendor/.*/(tests|Tests)/@'`) + 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', + + // A file list that defines files that will be excluded + // from parsing and analysis and will not be read at all. + // + // This is useful for excluding hopelessly unanalyzable + // files that can't be removed for whatever reason. + 'exclude_file_list' => [], + + // A directory list that defines files that will be excluded + // from static analysis, but whose class and method + // information should be included. + // + // Generally, you'll want to include the directories for + // third-party code (such as "vendor/") in this list. + // + // n.b.: If you'd like to parse but not analyze 3rd + // party code, directories containing that code + // should be added to the `directory_list` as well as + // to `exclude_analysis_directory_list`. + 'exclude_analysis_directory_list' => [ + 'vendor/', + ], + + // Enable this to enable checks of require/include statements referring to valid paths. + 'enable_include_path_checks' => true, + + // The number of processes to fork off during the analysis + // phase. + 'processes' => 1, + + // List of case-insensitive file extensions supported by Phan. + // (e.g. `['php', 'html', 'htm']`) + 'analyzed_file_extensions' => [ + 'php', + ], + + // You can put paths to stubs of internal extensions in this config option. + // If the corresponding extension is **not** loaded, then Phan will use the stubs instead. + // Phan will continue using its detailed type annotations, + // but load the constants, classes, functions, and classes (and their Reflection types) + // from these stub files (doubling as valid php files). + // Use a different extension from php to avoid accidentally loading these. + // The `tools/make_stubs` script can be used to generate your own stubs (compatible with php 7.0+ right now) + // + // (e.g. `['xdebug' => '.phan/internal_stubs/xdebug.phan_php']`) + 'autoload_internal_extension_signatures' => [], + + // A list of plugin files to execute. + // + // Plugins which are bundled with Phan can be added here by providing their name (e.g. `'AlwaysReturnPlugin'`) + // + // Documentation about available bundled plugins can be found [here](https://github.com/phan/phan/tree/master/.phan/plugins). + // + // Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. `'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'`) + 'plugins' => [ + 'AlwaysReturnPlugin', + 'PregRegexCheckerPlugin', + 'UnreachableCodePlugin', + ], + + // A list of directories that should be parsed for class and + // method information. After excluding the directories + // defined in `exclude_analysis_directory_list`, the remaining + // files will be statically analyzed for errors. + // + // Thus, both first-party and third-party code being used by + // your application should be included in this list. + 'directory_list' => [ + 'src', + 'vendor', + ], + + // A list of individual files to include in analysis + // with a path relative to the root directory of the + // project. + 'file_list' => [ + 'bin/phpqa-extensions.php', + ], +]; diff --git a/.phpqa.yml b/.phpqa.yml index 0acfaac..0c7e242 100644 --- a/.phpqa.yml +++ b/.phpqa.yml @@ -2,7 +2,9 @@ tool: phpa: MacFJA\PHPQAExtensions\Tools\Analyzer\PhpAssumptions phpmnd: MacFJA\PHPQAExtensions\Tools\Analyzer\PhpMagicNumber phpca: MacFJA\PHPQAExtensions\Tools\Analyzer\PhpCodeAnalyzer + phan: MacFJA\PHPQAExtensions\Tools\Analyzer\Phan report: phpa: app/report/phpassumptions.xsl phpmnd: app/report/phpmagicnumber.xsl phpca: app/report/php-code-analyzer.xsl + phan: app/report/phan.xsl diff --git a/app/report/phan.xsl b/app/report/phan.xsl new file mode 100644 index 0000000..4b9a278 --- /dev/null +++ b/app/report/phan.xsl @@ -0,0 +1,181 @@ + + + + + + + + + + + + Phan report + + + + + + +
+ +

Phan report

+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryErrorsWarningsFiles
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ErrorLineColumn
+ + + error + + + warning + + + +
+ +
+
+
+
+ + + + + + + +
+
\ No newline at end of file diff --git a/bin/phpqa-extensions.php b/bin/phpqa-extensions.php index 71f9493..549a637 100644 --- a/bin/phpqa-extensions.php +++ b/bin/phpqa-extensions.php @@ -26,7 +26,7 @@ if (array_key_exists('tools', $options)) { $style->title('List of available tools'); - + /** @var array> $rows */ $rows = []; foreach ($availables as $item) { $rows[] = [ @@ -48,7 +48,7 @@ foreach ($toAdd as $name) { $tool = $toolsFinder->findToolFrom($name); - if ($tool == null) { + if (!is_array($tool)) { $style->warning(sprintf('"%s" is not an available tool.', $name)); continue; } diff --git a/composer.json b/composer.json index 1d5d908..98dee79 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,13 @@ "require-dev": { "povils/phpmnd": "^2.0", "rskuipers/php-assumptions": "^0.7.0", - "wapmorgan/php-code-analyzer": "^1.0.5" + "wapmorgan/php-code-analyzer": "^1.0.5", + "phan/phan": "^1.2" }, "suggest": { "povils/phpmnd": "PHP Magic Number Detector", "rskuipers/php-assumptions": "Tool to detect assumptions", - "wapmorgan/php-code-analyzer": "PhpCodeAnalyzer finds usage of non-built-in extensions in your php code." + "wapmorgan/php-code-analyzer": "PhpCodeAnalyzer finds usage of non-built-in extensions in your php code.", + "phan/phan": "Phan is a static analyzer for PHP. Phan prefers to avoid false-positives and attempts to prove incorrectness rather than correctness." } } diff --git a/src/Tools/Analyzer/Phan.php b/src/Tools/Analyzer/Phan.php new file mode 100644 index 0000000..411312b --- /dev/null +++ b/src/Tools/Analyzer/Phan.php @@ -0,0 +1,114 @@ + '=', + 'outputMode' => OutputMode::HANDLED_BY_TOOL, + 'xml' => ['phan.xml'], + 'errorsXPath' => [ + # ignoreWarnings => xpath + false => '//checkstyle/file/error', + true => '//checkstyle/file/error[@severity="error"]', + ], + 'composer' => 'phan/phan', + 'internalClass' => '\Phan\Phan', + ); + + public function __invoke() + { + $this->tool->errorsType = $this->config->value('phpcs.ignoreWarnings') === true; + $args = [$this->multiArg($this->options->getAnalyzedDirs(), 'directory')]; + $args['allow-polyfill-parser'] = null; + $args['exclude-directory-list'] = implode(',', $this->options->ignore->psalm()['directory']); + if ($this->options->isSavedToFiles) { + $args['output'] = $this->options->rawFile('phan.xml'); + $args['output-mode'] = 'checkstyle'; + } + + return $args; + } + + protected function multiArg($value, $optionPrefix) + { + if (count($value) === 0) { + return ''; + } + + $fullOptionPrefix = '--'.$optionPrefix.static::getToolSettings()['optionSeparator']; + + return $fullOptionPrefix . implode(' ' . $fullOptionPrefix, $value); + } + + /** + * Get composer vendor/package name + * + * @return string + */ + public static function getComposer() + { + return static::getToolSettings()['composer']; + } + + /** + * Get the tool display name + * + * @return string + */ + public static function getToolName() + { + return 'Phan'; + } + + /** + * Get tool report xsl filename (or {@code null} for cli) + * + * @return string|null + */ + public static function getReportName() + { + return __DIR__.'/../../../app/report/phan.xsl'; + } + + /** + * Get tool cli name + * + * @return string + */ + public static function getCliName() + { + return 'phan'; + } + + /** + * Get the FQCN of the tool + * (use to check if the tool exist trough the autoloader) + * + * @return string + */ + public static function getInternalClass() + { + return 'Phan\Phan'; + } + + /** + * Return the global tool settings. + * + * @return array + */ + public static function getToolSettings() + { + return self::$SETTINGS; + } +} diff --git a/src/Tools/Analyzer/PhpAssumptions.php b/src/Tools/Analyzer/PhpAssumptions.php index 5a793a7..6e8eefa 100644 --- a/src/Tools/Analyzer/PhpAssumptions.php +++ b/src/Tools/Analyzer/PhpAssumptions.php @@ -21,6 +21,7 @@ class PhpAssumptions extends \Edge\QA\Tools\Tool implements ToolDefinition public function __invoke() { + $args = []; $args['format'] = 'pretty'; if ($this->options->isSavedToFiles) { $args['format'] = 'xml'; diff --git a/src/ToolsFinder.php b/src/ToolsFinder.php index adc820e..6778070 100644 --- a/src/ToolsFinder.php +++ b/src/ToolsFinder.php @@ -9,13 +9,13 @@ */ class ToolsFinder { - /** @var array */ + /** @var array> */ protected $available = []; /** * Get the list of tool available. (Tools implemented) * - * @return array + * @return array> */ public function getAvailableTools() { From 229afd3e1354be7004bbfc6ab9cb3270e6c647bf Mon Sep 17 00:00:00 2001 From: MacFJA Date: Wed, 19 Feb 2020 20:42:19 +0100 Subject: [PATCH 2/2] Update version constraint of Symfony/Yaml and Symfony/Console Close #12 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 98dee79..2eafe0a 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,9 @@ }, "require": { "edgedesign/phpqa": "^1.18", - "symfony/yaml": "^3.4", + "symfony/yaml": "^3.4|^4.0|^5.0", "wikimedia/relpath": "^2.1", - "symfony/console": "3.*|4.*" + "symfony/console": "3.*|4.*|5.*" }, "require-dev": { "povils/phpmnd": "^2.0",