vkcom / noverify Goto Github PK
View Code? Open in Web Editor NEWPretty fast linter (code static analysis utility) for PHP
License: MIT License
Pretty fast linter (code static analysis utility) for PHP
License: MIT License
<?php
declare(strict_types=1);
class Example
{
protected const TEST = 23;
}
echo Example::TEST;
All OK
Error
Every good linter has config file for example
.noverifyrc
where you can add all exclude/include rules
also good option add
--generate-rcfile
to create example
good example here
https://github.com/PyCQA/pylint/blob/master/pylintrc
<?php
declare(strict_types=1);
trait Example
{
private static $property = 'some';
protected function some(): string
{
return self::$property;
}
}
ERROR Property does not exist at ...\Example.php:11
return self::$property;
^^^^^^^^^
All OK
<?php
declare(strict_types=1);
class Example implements \IteratorAggregate
{
public function getIterator(): array
{
return [1, 2, 3];
}
}
foreach (new Example() as $i) {
echo $i;
}
All OK
Error: Objects returned by Example::getIterator() must be traversable or implement interface Iterator
// Warn that "void" is not a valid for input parameter.
/**
* @param void $x
* @return void
*/
function f($x) {}
No warnings.
Warning about void used as a type for "param"/"property" annotation.
<?php
$baseText = <<<'EOT'
ا
EOT;
If there is ا symbol in EOT directive - noverify will be fallen down.
Please, find below console output:
https://justpaste.it/4fzm4
All OK
for example in pylint we have this comments
import config.logging_settings # pylint: disable=W0611
import config.logging_settings # pylint: disable=unused-import
Sometimes you have strange cases, where linter is not good, but it works
for example old, legacy code
I want some kind of this exclude line by special comment with disable name
$this->gmail->sendEmail($email, $subject);
Here I got:
ERROR Call to undefined method {}->sendEmail()
I want
$this->gmail->sendEmail($email, subject); // noverify: disable=call-undefined-method
Add normal -version, not this one
PHP Linter
Built on
OS
Commit
There are issues with low severity level (e.g. 'MAYBE').
Is there way to specify minimal severity level to filter issues with lower severity?
$ads_ids = array_keys($arr);
foreach ($ads_ids as $num => $ad_id) {
if ($num + 1 < count($ads_ids)) {
$second_ad_id = $ads_ids[$num + 1];
$arr[$ad_id]->a = $arr[$second_ad_id]->b;
}
}
UNUSED Unused variable ad_id (use $_ to ignore this inspection) at
foreach ($ads_ids as $num => $ad_id) {
No errors.
class TestClass
{
public function get(): string
{
return '.';
}
}
function a(TestClass ...$testClasses): string
{
$result = '';
foreach ($testClasses as $testClass) {
$result .= $testClass->get();
}
return $result;
}
echo a(new TestClass()), PHP_EOL;
noverify -stubs-dir=~/sources/phpstorm-stubs -cache-dir=~/.cache/noverify -exclude '(vendor|cache)' ~/sources/php-tmp/nover/
2019/03/01 20:23:27.819049 Indexing [~/sources/php-tmp/nover/]
2019/03/01 20:23:27.820374 Linting
ERROR Call to undefined method {foreach_value}::get() at ~/sources/php-tmp/nover/test.php:22
$result .= $testClass->get();
^^^
2019/03/01 20:23:27.821266 Found 1 critical reports
<?php
$config = [ 'key1' => 'a', 'key2' => 'b', 'key1' => 'c' ];
All ok.
Duplicate key 'key1' in array.
Proposal:
-enable
option to turn on checks by a list of permitted checks.-disable
option to disable a list of checks from the default set.function f($xs = array()) {}
No warnings.
Warning about old array()
syntax.
class Foo
{
/** @return $this */
public function bar()
{
return $this;
}
/** @return static */
public function baz()
{
return new static();
}
}
For static
, NoVerify behaves like it's self
, which is not correct (since we need late static binding in those cases instead of an early one).
For $this
it infers the "identity" type of $this
, which is not useful. Whether it should map it into self
or static
is an open question though. I would stick to static
in this case, but it could be more conservative to use self
there, at least for now.
static
resolved into derived type on the use spots.
$this
is either ignored since NoVerify can infer actual return types for return $this
or mapped into some other type. It's also possible to handle $this
in a special way, but it doesn't seem to yield any benefits over other forms (static/self).
See also yiisoft/yii2#7045.
1)Execute go get -u github.com/VKCOM/noverify
2)Execute
noverify -stubs-dir=D:\\Libs\\phpstorm-stubs -cache-dir=D:\\tmp\\noverify D:\\Dev\\php\\RobotSharpApi -exclude='vendor/'
Many similar mistakes:
Failed parsing D:\Libs\phpstorm-stubs\zmq\zmq.php: rename D:\tmp\noverify\D_\Libs\phpstorm-stubs\zmq\zmq.php.4687de6d3e2dce931ca5e9470c5b8079.tmp D:\tmp\noverify\D_\Libs\phpstorm-stubs\zmq\zmq.php.4687de6d3e2dce931ca5e9470c5b8079: The process cannot access the file because it is being used by another process.
Failed parsing D:\Dev\php\TestProject\src\Api\ApiLogin.php: rename D:\tmp\noverify\D_\Dev\php\TestProject\src\Api\ApiLogin.php.e3e9f90751d8aba6be7c13558e537d91.tmp D:\tmp\noverify\D_\Dev\php\TestProject\src\Api\ApiLogin.php.e3e9f90751d8aba6be7c13558e537d91: The process cannot access the file because it is being used by another process.
No error
For example, these (for starters):
linter, php, go, golang, noverify
noverify
topic is useful for repos with custom noverify checks.
This way, one can find them by topics search on GitHub.
(https://help.github.com/en/articles/classifying-your-repository-with-topics)
interface TestInterface
{
const TEST = '1';
}
class TestClass implements TestInterface
{
public function get()
{
return self::TEST;
}
}
noverify -stubs-dir=~/sources/phpstorm-stubs -cache-dir=$HOME/.cache/noverify ~/php-tmp/nover/
2019/03/01 20:03:50.984902 Started
2019/03/01 20:03:51.429256 Indexing [~/sources/php-tmp/nover/]
2019/03/01 20:03:51.430637 Linting
ERROR Class constant does not exist at ~/sources/php-tmp/nover/test.php:12
return self::TEST;
^^^^
2019/03/01 20:03:51.431517 Found 1 critical reports
<?php
switch ($something) {
case "one":
$a++;
// fallthrough
case "two":
$a++;
break;
}
Case without break
All ok with comment.
This is an epic-like (progress tracking) issue for porting checks that are implemented in php -l
, but are not included in noverify.
The main benefit is to make it possible to remove php -l
from the pipeline and use noverify as a drop-in replacement.
Ideally, there is only one or two exit points and all other code paths do panic in case of emergency instead of exit.
The problem with exit is that it will prevent all prepared defers to run.
Imagine we executed a bunch of defers inside main, then some of them along the way, during the linter normal work, and we reached exit call. All defers are lost.
Panics can be caught or they can even be left unrecovered, so we get the stack trace of whereabouts of an unexpected condition.
I want to add memprofile and cpuprofile flags, so profiling is more akin to other Go tools, but that is simpler with defers. First step is to remove log.Fatal/os.Exit from the cmd.Main
.
function f($xs) {
foreach ($xs as $v) {
if ($v == 10) {
return 10;
}
}
exit; // Exits only condition inside a loop never fires
}
f([10]);
echo 123; // <- marked as unreachable
deadCode: Unreachable code
for echo
statement.
No warning.
Generated code can be detected by some common header patterns, like:
// CODE IS GENERATED, DO NOT EDIT!
Those files should be indexed, but not linted by default.
Each modern project contains a vendor
directory. If include the src
directory, then the linter produces a lot of false positive errors associated with external dependencies. If add the whole project, the linter will start to produce errors in the dependencies (from the vendor directory).
Add "-exclude=./vendor -exclude=./tests -exclude=./resources" (etc) arguments for exclude directories from the report.
<? echo "اُردُو\n"; ?>
The linter has a syntax error and is unable to examine the file.
ERROR syntax: Syntax error: syntax error: unexpected $unk at /path/to/utf8.php:2
If the text is inside a class method, it can also show up as an "Empty root node" error.
There should be no error.
This appears to be a php-parser issue that was fixed, so the solution would be to upgrade the vendored php-parser library to the latest version.
<?php
declare(strict_types=1);
function a($a): \Generator
{
yield $a;
}
a(42)->send(42);
ERROR Call to undefined method {}::send() at ...\Example.php:15
a(42)->send(42);
^^^^
All OK
interface TestInterface
{
public function getCreatedAt(): \DateTimeInterface;
}
interface TestExInterface extends TestInterface
{
}
function a(TestExInterface $testInterface): string
{
return $testInterface->getCreatedAt()->format('U');
}
2019/03/04 11:37:47.663297 Indexing [~/sources/php-tmp/nover/]
2019/03/04 11:37:47.670811 Linting
ERROR Call to undefined method {\TestExInterface}->getCreatedAt() at ~/sources/php-tmp/nover/test.php:22
return $testInterface->getCreatedAt()->format('U');
^^^^^^^^^^^^
ERROR Call to undefined method {}->format() at ~/sources/php-tmp/nover/test.php:22
return $testInterface->getCreatedAt()->format('U');
^^^^^^
2019/03/04 11:37:47.671781 Found 2 critical reports
All OK
noverify -stubs-dir=~/sources/phpstorm-stubs -cache-dir=~/.cache/noverify -exclude '(vendor|cache)' ~/sources/php-tmp/nover/
First off this project is looking very promising, I have tried this as a languageserver in vim and it is significantly faster/usable than any other php language servers I've come across 👍
I've found that functions in anonymous classes are not picked up correctly:
<?php
$foo = new class() extends \DateTime
{
public function foo() { }
};
$foo->foo();
$foo->getTimestamp();
ERROR undefined: Call to undefined method {}->foo() at Example.php:8
$foo->foo();
^^^
ERROR undefined: Call to undefined method {}->getTimestamp() at Example.php:9
$foo->getTimestamp();
^^^^^^^^^^^^
No errors to be reported as foo()
and getTimestamp()
are valid functions
In a new branch:
Goals:
<?php
class Magic {
/** @return Magic */
public function __get($key) {
return $this;
}
public function method() {}
}
$m = new Magic();
$m->method();
$m->foo->method();
$m->foo->bar->method();
Gives several warnings:
ERROR undefined: Call to undefined method {}->method() at /home/isharipov/CODE/php/hello.php:14
$m->foo->method();
^^^^^^
ERROR undefined: Property {}->bar does not exist at /home/isharipov/CODE/php/hello.php:15
$m->foo->bar->method();
^^^
ERROR undefined: Call to undefined method {}->method() at /home/isharipov/CODE/php/hello.php:15
$m->foo->bar->method();
The problem is that we treat __get
as something that returns unknown type. Sometimes that type is known and we can use that to avoid false positives that are given in the case above.
No warnings generated.
Note: Phpstorm handles the described case correctly
$enalbe = true;
No warnings.
Warning about a typo in enalbe
(should be enable
).
See https://github.com/client9/misspell.
We can start from checking only function parameter names.
To avoid some unexpected breakage in the future.
Travis CI fits our needs well (I think).
<?php
declare(strict_types=1);
class Example
{
public function method()
{
return 42;
}
}
(function() {
$this->method();
})->call(new Example());
ERROR Undefined variable: this at ...\Example.php:14
$this->method();
^^^^^
ERROR Call to undefined method {}::method() at ...\Example.php:14
$this->method();
^^^^^^
ERROR Call to undefined method {}::call() at ...\Example.php:15
})->call(new Example());
^^^^
2019/03/02 10:31:06.650272 Found 3 critical reports
All OK
1)Execute go get -u github.com/VKCOM/noverify
2)Execute
-stubs-dir=D:\Libs\phpstorm-stubs -cache-dir=D:\tmp\noverify -exclude='(vendor|test)' D:\Dev\php\TestProject
Folders from the exception get into the report
All OK
Use -exclude=(vendor|test) or -exclude="(vendor|test)"
<?php
declare(strict_types=1);
class Example
{
public function __invoke($argument)
{
return 42;
}
}
(new Example())();
All OK
Error: Too few arguments to function Example::__invoke()
As an example of other Go PHP project that can be installed via composer is roadrunner.
But I guess we need to manage binary releases first.
go get -u github.com/VKCOM/noverify
git clone [email protected]:JetBrains/phpstorm-stubs.git D:\\php\\phpstorm-stubs
noverify -stubs-dir=D:\\php\\phpstorm-stubs -cache-dir=./cache ./src
Failed parsing D:\php\phpstorm-stubs\zend\zend_f.php: open .cache\D:\php\phpstorm-stubs\zend\zend_f.php.e3a8c59daf7cb0d1249aa10a581ba9a9.tmp: The filename, directory name, or volume label syntax is incorrect.
All OK
Add support for relative paths
<?php
declare(strict_types=1);
class Element {
public function get(): int {
return 0;
}
}
class Test {
private $arr = [];
public function test($key) {
if (isset($this->arr[$key]) && $this->arr[$key] instanceof Element) {
return $this->arr[$key]->get();
}
}
}
ERROR Call to undefined method {}->get()
All ok
<?php
define('SOME_CONSTANT', 100500);
$param = $argv[1];
$value = mcGet('some_key');
if (!$data) {
if ($param) {
$data = SOME_CONSTANT;
} else {
$data = SOME_CONSTANT;
}
}
echo $data . PHP_EOL;
function mcGet($key) {
return null;
}
In this case $data has same value in all if cases
I want add check to add alert for unnecessary if or same set variable in all if branches
Currently, an unreliable method is used.
We may also want to accept autogen files patterns instead of recognizing only a small set of possible comment formats.
interface TestClassInterface
{
public function getCreatedAt(): \DateTimeInterface;
}
function a(TestClassInterface $testClass): string
{
return $testClass->getCreatedAt()->format('U');
}
noverify -stubs-dir=~/sources/phpstorm-stubs -cache-dir=~/.cache/noverify -exclude '(vendor|cache)' ~/sources/php-tmp/nover/
2019/03/01 20:12:50.227083 Started
2019/03/01 20:12:50.581958 Indexing [~/sources/php-tmp/nover/]
2019/03/01 20:12:50.583191 Linting
ERROR Call to undefined method {}::format() at ~/sources/php-tmp/nover/test.php:20
return $testClass->getCreatedAt()->format('U');
^^^^^^
2019/03/01 20:12:50.584107 Found 1 critical reports
PHP only has 1 floating point type - float
.
All of these produce float
-typed values:
<?php
var_dump((float)1);
var_dump((double)1);
var_dump((real)1);
Since we do suggest using float
inside phpdoc, linter should also infer types as float
, otherwise we can't match suggested float
with double
.
The entire fix is to change "double" strings to "float" inside solver code (I guess that's enough).
<?php
$x = 0;
do {
test($t);
$t=1;
$x++;
}while($x <= 2);
function test($tmp){
echo "test".$tmp."\n";
}
ERROR Undefined variable: t at 1.php:6
test($t);
^^
INFO Unreachable code at 1.php:7
$t=1;
^^^^^
Unreachable code ??? not really
Several users reported that NoVerify is too noisy by default and has some false positives that are hard to fix inside linter without introducing false negatives.
The proposal is to add CI-friendly mode that sacrifices some reports for the less false negative rate. More strict (currently the only one) mode can be enabled later when the majority of the project complies to a linter.
Right now I don't know the best way to do it and adding another configuration flag may be sub-optimal. Maybe we need to change default behavior accordingly and only after that consider whether we need very strict mode (needs more feedback and investigation). Discussions are welcome.
Below are some specific complaints, grouped by the check name.
Don't warn on undefined method/property if the type is unknown. In other words, don't say something like property {}->foo does not exist
. For the most strict mode (current) it could be nice to warn that type might not always be what the user expects and that it can't be inferred from the source code, but for CI it's more trouble than it worth.
Don't warn on special global variables like $argv
in global scope. 90% of their usages are inside scripts that are not included from other scripts, so global
statement can be considered too pedantic there. For strict mode it's OK though.
global $a;
global $b;
switch ($a) {
case $b['key']: // <- this case is not examined
// ...
}
noverify gives "unused b" warning because it doesn't walk case clauses.
No warning about unused variable, since it's used inside switch case.
<?php
declare(strict_types=1);
class Magic
{
public static function __callStatic($name, $arguments): int
{
return 42;
}
}
Magic::some();
ERROR Call to undefined method \Magic::some() at ...\Example.php:14
Magic::some();
^^^^
All OK
When an if condition contains an isset that uses a "variable variable", linter.(*BlockWalker).handleIf
panics because it expects a *node.Identifier
for the variable name instead of a *expr.Variable
.
function get_global_var($name) {
global $$name;
if (isset($$name)) { // Panic occurs while parsing this line
return $$name;
}
return null;
}
Panic while parsing <filename>: interface conversion: node.Node is *expr.Variable, not *node.Identifier
No panic.
mysql('a', 'b', 'c');
All OK
Notice: mysql
function is deprecated since 5.3.0
You can analyze @deprecated
annotations from phpstorm-stubs (and not only). In addition, @internal
annotations can be read.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.