Skip to content

Commit 37eaf7e

Browse files
johnbillionszepeviktorherndlm
authored
Add PHPStan bleeding edge compatibility (#98)
Co-authored-by: Viktor Szépe <viktor@szepe.net> Co-authored-by: Martin Herndl <martin@herndl.org>
1 parent 056b058 commit 37eaf7e

File tree

10 files changed

+123
-43
lines changed

10 files changed

+123
-43
lines changed

.travis.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@ dist: "bionic"
1010
php:
1111
- "8.0"
1212
- "7.4"
13-
- "7.1"
13+
- "7.2"
14+
15+
jobs:
16+
include:
17+
- php: "8.1"
18+
script:
19+
- "composer test:syntax -- --no-progress"
20+
- "composer test:phpunit -- --verbose"
21+
# - "composer test:cs -- -s"
22+
- "composer test:phpstan -- --ansi --memory-limit=1G --no-progress"
1423

1524
cache:
1625
directories:

composer.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111
"phpstan"
1212
],
1313
"require": {
14-
"php": "^7.1 || ^8.0",
14+
"php": "^7.2 || ^8.0",
1515
"php-stubs/wordpress-stubs": "^4.7 || ^5.0",
16-
"phpstan/phpstan": "^1.0",
16+
"phpstan/phpstan": "^1.6",
1717
"symfony/polyfill-php73": "^1.12.0"
1818
},
1919
"require-dev": {
20-
"composer/composer": "^2.1.12",
20+
"composer/composer": "^2.1.14",
2121
"dealerdirect/phpcodesniffer-composer-installer": "^0.7",
2222
"php-parallel-lint/php-parallel-lint": "^1.1",
23-
"phpstan/phpstan-strict-rules": "^1.0",
24-
"phpunit/phpunit": "^7 || ^9",
23+
"phpstan/phpstan-strict-rules": "^1.2",
24+
"phpunit/phpunit": "^8 || ^9",
2525
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.6"
2626
},
2727
"autoload": {

extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ services:
7575
class: SzepeViktor\PHPStan\WordPress\TermExistsDynamicFunctionReturnTypeExtension
7676
tags:
7777
- phpstan.broker.dynamicFunctionReturnTypeExtension
78+
-
79+
class: SzepeViktor\PHPStan\WordPress\HookDocsVisitor
80+
tags:
81+
- phpstan.parser.richParserNodeVisitor
7882
rules:
7983
- SzepeViktor\PHPStan\WordPress\HookDocsRule
8084
- SzepeViktor\PHPStan\WordPress\IsWpErrorRule

src/HookDocBlock.php

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,7 @@ public function getNullableHookDocBlock(FuncCall $functionCall, Scope $scope): ?
4949

5050
private static function getNullableNodeComment(FuncCall $node): ?\PhpParser\Comment\Doc
5151
{
52-
$startLine = $node->getStartLine();
53-
54-
while ($node !== null && $node->getStartLine() === $startLine) {
55-
// Fetch the docblock from the node.
56-
$comment = $node->getDocComment();
57-
58-
if ($comment !== null) {
59-
return $comment;
60-
}
61-
62-
/** @var \PhpParser\Node|null */
63-
$node = $node->getAttribute('parent');
64-
}
65-
66-
return null;
52+
/** @var \PhpParser\Comment\Doc|null */
53+
return $node->getAttribute('latestDocComment');
6754
}
6855
}

src/HookDocsVisitor.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/**
4+
* Custom node visitor to fetch the docblock for a function call.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace SzepeViktor\PHPStan\WordPress;
10+
11+
use PhpParser\Node;
12+
13+
final class HookDocsVisitor extends \PhpParser\NodeVisitorAbstract
14+
{
15+
/** @var int|null */
16+
protected $latestStartLine = null;
17+
18+
/** @var \PhpParser\Comment\Doc|null */
19+
protected $latestDocComment = null;
20+
21+
// phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter
22+
public function beforeTraverse(array $nodes): ?array
23+
{
24+
$this->latestStartLine = null;
25+
$this->latestDocComment = null;
26+
27+
return null;
28+
}
29+
30+
public function enterNode(Node $node): ?Node
31+
{
32+
if ($node->getStartLine() !== $this->latestStartLine) {
33+
$this->latestDocComment = null;
34+
}
35+
36+
$this->latestStartLine = $node->getStartLine();
37+
38+
$doc = $node->getDocComment();
39+
40+
if ($doc !== null) {
41+
$this->latestDocComment = $doc;
42+
}
43+
44+
$node->setAttribute('latestDocComment', $this->latestDocComment);
45+
46+
return null;
47+
}
48+
}

tests/HookDocsRuleTest.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,45 +38,55 @@ public function testRule(): void
3838
[
3939
[
4040
'Expected 2 @param tags, found 1.',
41-
22,
41+
19,
4242
],
4343
[
4444
'Expected 2 @param tags, found 3.',
45-
31,
45+
28,
4646
],
4747
[
4848
'@param string $one does not accept actual type of parameter: int|string.',
49-
43,
49+
40,
5050
],
5151
[
5252
'@param string $one does not accept actual type of parameter: int.',
53-
53,
53+
50,
5454
],
5555
[
5656
'@param tag must not be named $this. Choose a descriptive alias, for example $instance.',
57-
82,
57+
79,
5858
],
5959
[
6060
'Expected 2 @param tags, found 1.',
61-
97,
61+
94,
6262
],
6363
[
64-
'@param ChildTestClass $one does not accept actual type of parameter: ParentTestClass.',
65-
134,
64+
'@param SzepeViktor\PHPStan\WordPress\Tests\ChildTestClass $one does not accept actual type of parameter: SzepeViktor\PHPStan\WordPress\Tests\ParentTestClass.',
65+
131,
6666
],
6767
[
6868
'@param string $one does not accept actual type of parameter: string|null.',
69-
155,
69+
152,
7070
],
7171
[
7272
'One or more @param tags has an invalid name or invalid syntax.',
73-
170,
73+
167,
7474
],
7575
[
7676
'One or more @param tags has an invalid name or invalid syntax.',
77-
206,
77+
203,
78+
],
79+
[
80+
'Expected 2 @param tags, found 1.',
81+
214,
7882
],
7983
]
8084
);
8185
}
86+
87+
public static function getAdditionalConfigFiles(): array
88+
{
89+
// Path to your project's phpstan.neon, or extension.neon in case of custom extension packages.
90+
return [dirname(__DIR__) . '/extension.neon'];
91+
}
8292
}

tests/IsWpErrorRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,10 @@ public function testRule(): void
5151
]
5252
);
5353
}
54+
55+
public static function getAdditionalConfigFiles(): array
56+
{
57+
// Path to your project's phpstan.neon, or extension.neon in case of custom extension packages.
58+
return [dirname(__DIR__) . '/extension.neon'];
59+
}
5460
}

tests/data/apply_filters.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
use function PHPStan\Testing\assertType;
1111
use function apply_filters;
12-
use function returnValue;
1312

1413
$value = apply_filters('filter', 'Hello, World');
1514
assertType('mixed', $value);

tests/data/hook-docs.php

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
namespace SzepeViktor\PHPStan\WordPress\Tests;
66

7-
use ChildTestClass;
8-
use ParentTestClass;
9-
107
// phpcs:disable Squiz.NamingConventions.ValidFunctionName.NotCamelCaps,Squiz.NamingConventions.ValidVariableName.NotCamelCaps,Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
118

129
$one = 1;
@@ -87,7 +84,7 @@ function wide_param_type(string $one)
8784
* @param \stdClass $instance Instance.
8885
* @param array $args Args
8986
*/
90-
do_action('action', $this, $args);
87+
do_action('action', $this, []);
9188

9289
/**
9390
* This param tag renames the `$this` variable, which is fine, but still has a missing param tag.
@@ -109,7 +106,7 @@ function correct_inherited_param_type(ChildTestClass $one)
109106
/**
110107
* This param tag is for a super class of the variable, which is fine.
111108
*
112-
* @param \ParentTestClass $one First parameter.
109+
* @param ParentTestClass $one First parameter.
113110
*/
114111
$args = apply_filters('filter', $one);
115112
}
@@ -119,7 +116,7 @@ function correct_interface_param_type(ChildTestClass $one)
119116
/**
120117
* This param tag is for the interface of the variable, which is fine.
121118
*
122-
* @param \TestInterface $one First parameter.
119+
* @param TestInterface $one First parameter.
123120
*/
124121
$args = apply_filters('filter', $one);
125122
}
@@ -129,7 +126,7 @@ function incorrect_inherited_param_type(ParentTestClass $one)
129126
/**
130127
* This param tag is for a child class of the variable. Oh no.
131128
*
132-
* @param \ChildTestClass $one First parameter.
129+
* @param ChildTestClass $one First parameter.
133130
*/
134131
$args = apply_filters('filter', $one);
135132
}
@@ -203,8 +200,26 @@ function incorrect_nullable_param_type(?string $one = null)
203200
* @param bool has_site_pending_automated_transfer( $this->blog_id )
204201
* @param int $blog_id Blog identifier.
205202
*/
206-
return apply_filters(
203+
$value = apply_filters(
207204
'filter',
208205
false,
209206
$this->blog_id
210207
);
208+
209+
/**
210+
* This filter is wrapped inside another function call, which is weird but ok. Its param count is incorrect.
211+
*
212+
* @param int $number
213+
*/
214+
$value = intval(apply_filters('filter', 123, $foo));
215+
216+
/**
217+
* This is a docblock for an unrelated function.
218+
*
219+
* It exists to ensure the undocumented filter below does not have its docblock inherited from this function.
220+
*
221+
* @param bool $yes
222+
*/
223+
function foo( bool $yes ) {}
224+
225+
$value = apply_filters('filter', 123, $foo);

tests/functions.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
declare(strict_types=1);
44

5+
namespace SzepeViktor\PHPStan\WordPress\Tests;
6+
57
/**
68
* Returns the passed value.
79
*
@@ -18,10 +20,10 @@ interface TestInterface
1820
{
1921
}
2022

21-
class ParentTestClass implements \TestInterface
23+
class ParentTestClass implements TestInterface
2224
{
2325
}
2426

25-
class ChildTestClass extends \ParentTestClass
27+
class ChildTestClass extends ParentTestClass
2628
{
2729
}

0 commit comments

Comments
 (0)