diff --git a/.travis.yml b/.travis.yml index 56bb67c..1f64ea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,16 @@ dist: "bionic" php: - "8.0" - "7.4" - - "7.1" + - "7.2" + +jobs: + include: + - php: "8.1" + script: + - "composer test:syntax -- --no-progress" + - "composer test:phpunit -- --verbose" + # - "composer test:cs -- -s" + - "composer test:phpstan -- --ansi --memory-limit=1G --no-progress" cache: directories: diff --git a/composer.json b/composer.json index 1475172..8d026e4 100644 --- a/composer.json +++ b/composer.json @@ -11,17 +11,17 @@ "phpstan" ], "require": { - "php": "^7.1 || ^8.0", + "php": "^7.2 || ^8.0", "php-stubs/wordpress-stubs": "^4.7 || ^5.0", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan": "^1.6", "symfony/polyfill-php73": "^1.12.0" }, "require-dev": { - "composer/composer": "^2.1.12", + "composer/composer": "^2.1.14", "dealerdirect/phpcodesniffer-composer-installer": "^0.7", "php-parallel-lint/php-parallel-lint": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^7 || ^9", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^8 || ^9", "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.6" }, "autoload": { diff --git a/extension.neon b/extension.neon index ff73ac9..4a33257 100644 --- a/extension.neon +++ b/extension.neon @@ -75,6 +75,10 @@ services: class: SzepeViktor\PHPStan\WordPress\TermExistsDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: SzepeViktor\PHPStan\WordPress\HookDocsVisitor + tags: + - phpstan.parser.richParserNodeVisitor rules: - SzepeViktor\PHPStan\WordPress\HookDocsRule - SzepeViktor\PHPStan\WordPress\IsWpErrorRule diff --git a/src/HookDocBlock.php b/src/HookDocBlock.php index e920f6a..eacd765 100644 --- a/src/HookDocBlock.php +++ b/src/HookDocBlock.php @@ -49,20 +49,7 @@ public function getNullableHookDocBlock(FuncCall $functionCall, Scope $scope): ? private static function getNullableNodeComment(FuncCall $node): ?\PhpParser\Comment\Doc { - $startLine = $node->getStartLine(); - - while ($node !== null && $node->getStartLine() === $startLine) { - // Fetch the docblock from the node. - $comment = $node->getDocComment(); - - if ($comment !== null) { - return $comment; - } - - /** @var \PhpParser\Node|null */ - $node = $node->getAttribute('parent'); - } - - return null; + /** @var \PhpParser\Comment\Doc|null */ + return $node->getAttribute('latestDocComment'); } } diff --git a/src/HookDocsVisitor.php b/src/HookDocsVisitor.php new file mode 100644 index 0000000..7465361 --- /dev/null +++ b/src/HookDocsVisitor.php @@ -0,0 +1,48 @@ +latestStartLine = null; + $this->latestDocComment = null; + + return null; + } + + public function enterNode(Node $node): ?Node + { + if ($node->getStartLine() !== $this->latestStartLine) { + $this->latestDocComment = null; + } + + $this->latestStartLine = $node->getStartLine(); + + $doc = $node->getDocComment(); + + if ($doc !== null) { + $this->latestDocComment = $doc; + } + + $node->setAttribute('latestDocComment', $this->latestDocComment); + + return null; + } +} diff --git a/tests/HookDocsRuleTest.php b/tests/HookDocsRuleTest.php index f8db4f2..48007fc 100644 --- a/tests/HookDocsRuleTest.php +++ b/tests/HookDocsRuleTest.php @@ -38,45 +38,55 @@ public function testRule(): void [ [ 'Expected 2 @param tags, found 1.', - 22, + 19, ], [ 'Expected 2 @param tags, found 3.', - 31, + 28, ], [ '@param string $one does not accept actual type of parameter: int|string.', - 43, + 40, ], [ '@param string $one does not accept actual type of parameter: int.', - 53, + 50, ], [ '@param tag must not be named $this. Choose a descriptive alias, for example $instance.', - 82, + 79, ], [ 'Expected 2 @param tags, found 1.', - 97, + 94, ], [ - '@param ChildTestClass $one does not accept actual type of parameter: ParentTestClass.', - 134, + '@param SzepeViktor\PHPStan\WordPress\Tests\ChildTestClass $one does not accept actual type of parameter: SzepeViktor\PHPStan\WordPress\Tests\ParentTestClass.', + 131, ], [ '@param string $one does not accept actual type of parameter: string|null.', - 155, + 152, ], [ 'One or more @param tags has an invalid name or invalid syntax.', - 170, + 167, ], [ 'One or more @param tags has an invalid name or invalid syntax.', - 206, + 203, + ], + [ + 'Expected 2 @param tags, found 1.', + 214, ], ] ); } + + public static function getAdditionalConfigFiles(): array + { + // Path to your project's phpstan.neon, or extension.neon in case of custom extension packages. + return [dirname(__DIR__) . '/extension.neon']; + } } diff --git a/tests/IsWpErrorRuleTest.php b/tests/IsWpErrorRuleTest.php index 660021a..9e91ff6 100644 --- a/tests/IsWpErrorRuleTest.php +++ b/tests/IsWpErrorRuleTest.php @@ -51,4 +51,10 @@ public function testRule(): void ] ); } + + public static function getAdditionalConfigFiles(): array + { + // Path to your project's phpstan.neon, or extension.neon in case of custom extension packages. + return [dirname(__DIR__) . '/extension.neon']; + } } diff --git a/tests/data/apply_filters.php b/tests/data/apply_filters.php index 698f42f..1a07fbb 100644 --- a/tests/data/apply_filters.php +++ b/tests/data/apply_filters.php @@ -9,7 +9,6 @@ use function PHPStan\Testing\assertType; use function apply_filters; -use function returnValue; $value = apply_filters('filter', 'Hello, World'); assertType('mixed', $value); diff --git a/tests/data/hook-docs.php b/tests/data/hook-docs.php index 597bbfb..e718e7b 100644 --- a/tests/data/hook-docs.php +++ b/tests/data/hook-docs.php @@ -4,9 +4,6 @@ namespace SzepeViktor\PHPStan\WordPress\Tests; -use ChildTestClass; -use ParentTestClass; - // phpcs:disable Squiz.NamingConventions.ValidFunctionName.NotCamelCaps,Squiz.NamingConventions.ValidVariableName.NotCamelCaps,Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps $one = 1; @@ -87,7 +84,7 @@ function wide_param_type(string $one) * @param \stdClass $instance Instance. * @param array $args Args */ -do_action('action', $this, $args); +do_action('action', $this, []); /** * 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) /** * This param tag is for a super class of the variable, which is fine. * - * @param \ParentTestClass $one First parameter. + * @param ParentTestClass $one First parameter. */ $args = apply_filters('filter', $one); } @@ -119,7 +116,7 @@ function correct_interface_param_type(ChildTestClass $one) /** * This param tag is for the interface of the variable, which is fine. * - * @param \TestInterface $one First parameter. + * @param TestInterface $one First parameter. */ $args = apply_filters('filter', $one); } @@ -129,7 +126,7 @@ function incorrect_inherited_param_type(ParentTestClass $one) /** * This param tag is for a child class of the variable. Oh no. * - * @param \ChildTestClass $one First parameter. + * @param ChildTestClass $one First parameter. */ $args = apply_filters('filter', $one); } @@ -203,8 +200,26 @@ function incorrect_nullable_param_type(?string $one = null) * @param bool has_site_pending_automated_transfer( $this->blog_id ) * @param int $blog_id Blog identifier. */ -return apply_filters( +$value = apply_filters( 'filter', false, $this->blog_id ); + +/** + * This filter is wrapped inside another function call, which is weird but ok. Its param count is incorrect. + * + * @param int $number + */ +$value = intval(apply_filters('filter', 123, $foo)); + +/** + * This is a docblock for an unrelated function. + * + * It exists to ensure the undocumented filter below does not have its docblock inherited from this function. + * + * @param bool $yes + */ +function foo( bool $yes ) {} + +$value = apply_filters('filter', 123, $foo); diff --git a/tests/functions.php b/tests/functions.php index 1b30d85..269d7c1 100644 --- a/tests/functions.php +++ b/tests/functions.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace SzepeViktor\PHPStan\WordPress\Tests; + /** * Returns the passed value. * @@ -18,10 +20,10 @@ interface TestInterface { } -class ParentTestClass implements \TestInterface +class ParentTestClass implements TestInterface { } -class ChildTestClass extends \ParentTestClass +class ChildTestClass extends ParentTestClass { }