diff --git a/README.md b/README.md index 547a89c..6ce11e5 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,6 @@ They are more "strict" than the default PHPStan rules and some may be controvers - When throwing an exception inside a catch block, [you should pass the catched exception as the "previous" exception](http://bestpractices.thecodingmachine.com/php/error_handling.html#wrapping-an-exception-do-not-lose-the-previous-exception) - If you catch a `Throwable`, an `Exception` or a `RuntimeException`, you must rethrow the exception. -### Type-hinting related rules - -This is a PHP 7.1+ rule: - -- You should use type-hinting when possible -- If not possible, you should use a Docblock to specify the type -- If type-hinting against an array, you should use a Docblock to further explain the content of the array - -[More about type-hinting related rules...](doc/typehinting_rules.md) - ### Superglobal related rules - The use of [`$_GET`, `$_POST`, `$_FILES`, `$_COOKIE`, `$_SESSION`, `$_REQUEST` is forbidden](http://bestpractices.thecodingmachine.com/php/organize_your_code.html#stop-using-superglobals-). diff --git a/composer.json b/composer.json index 943e232..cddf05f 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ ], "require": { "php": "^7.1", - "phpstan/phpstan": "^0.11.7" + "phpstan/phpstan": "^0.12" }, "require-dev": { "phpunit/phpunit": "^7.1", @@ -24,19 +24,18 @@ }, "autoload-dev": { "classmap": [ - "tests/Rules/Exceptions/data/", - "tests/Rules/TypeHints/data/" + "tests/Rules/Exceptions/data/" ], "psr-4": { "TheCodingMachine\\PHPStan\\": "tests/" } }, "scripts": { - "phpstan": "phpstan analyse src -c phpstan.neon --level=5 --no-progress -vvv" + "phpstan": "phpstan analyse src -c phpstan.neon --level=6 --no-progress -vvv" }, "extra": { "branch-alias": { - "dev-master": "0.10-dev" + "dev-master": "0.12-dev" }, "phpstan": { "includes": [ diff --git a/doc/typehinting_rules.md b/doc/typehinting_rules.md deleted file mode 100644 index 04a98c9..0000000 --- a/doc/typehinting_rules.md +++ /dev/null @@ -1,140 +0,0 @@ -PHPDoc related rules -==================== - -This package contains a set of rules to help you remember to type-hint correctly your methods. - -Remember this is a PHP 7.1+ package so nullable type hints are available. - -The ideas with those rules are very simple: - -- You should use type-hinting when possible -- If not possible, you should use a Docblock to specify the type -- If type-hinting against an array, you should use a Docblock to further explain the content of the array - -## Missing type-hint detection - -This code snippet: - -```php -function foo($no_type_hint) -{ - // ... -} -``` - -will yield 2 warnings: - - In function "foo", parameter $no_type_hint has no type-hint and no @param annotation. - In function "foo", there is no return type and no @return annotation. - -You can fix this waring like this: - -```php -function foo(string $no_type_hint): void -{ - // ... -} -``` - -## Missing PHP type-hint detection - -This code snippet: - -```php -/** - * @param string|null $type_hintable - * @return string - */ -function foo($type_hintable) -{ - -} -``` - -will yield 2 warnings: - - In function "foo", parameter $type_hintable can be type-hinted to "?string". - In function "foo", a "string" return type can be added. - -Indeed! You specify a type in the PHP docblock while it could really be part of the method signature. - -You can fix this waring like this: - -```php -/** - * @param string|null $type_hintable - * @return string - */ -function foo(?string $type_hintable): string -{ - // ... -} -``` - -## Vague array type-hint detection - -This code snippet: - -```php -function foo(array $arr): array -{ - -} -``` - -will yield 2 warnings: - - In function "foo", parameter $arr type is "array". Please provide a @param annotation to further specify the type of the array. For instance: @param int[] $arr - In function "foo", return type is "array". Please provide a @param annotation to further specify the type of the array. For instance: @return int[] - -The "array" type-hinting is vague in PHP. You don't really know what kind of array you are dealing with. Is it an array of objects? An array of strings? An array of array of objects? - -You can fix this waring like this: - -```php -/** - * @param string[] $type_hintable - * @return SomeObject[] - */ -function foo(array $arr): array -{ - // ... -} -``` - -## Type mismatch detection - -This code snippet: - -```php -/** - * @param int $param - * @return int - */ -function mismatch(string $param): string -{ - -} -``` - -will yield 2 warnings: - - In function "mismatch", parameter $param type is type-hinted to "string" but the @param annotation says it is a "int". Please fix the @param annotation. - In function "mismatch", return type is type-hinted to "string" but the @return annotation says it is a "int". Please fix the @return annotation. - -Indeed, the docblock says a type is an int while the typehint says it's a string. - -Fix the PHP docblock like this: - -```php -/** - * @param string $param - * @return string - */ -function no_more_mismatch(string $param): string -{ - -} -``` - -(by the way, the docblock is completely redundant with the function declaration so unless you add some textual comment in it, you could completely remove it) diff --git a/phpstan-strict-rules.neon b/phpstan-strict-rules.neon index b607878..211335d 100644 --- a/phpstan-strict-rules.neon +++ b/phpstan-strict-rules.neon @@ -15,14 +15,6 @@ services: class: TheCodingMachine\PHPStan\Rules\Exceptions\MustRethrowRule tags: - phpstan.rules.rule - - - class: TheCodingMachine\PHPStan\Rules\TypeHints\MissingTypeHintInFunctionRule - tags: - - phpstan.rules.rule - - - class: TheCodingMachine\PHPStan\Rules\TypeHints\MissingTypeHintInMethodRule - tags: - - phpstan.rules.rule - class: TheCodingMachine\PHPStan\Rules\Superglobals\NoSuperglobalsRule tags: diff --git a/phpstan.neon b/phpstan.neon index b09fefe..a4ece3f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,2 @@ -parameters: - ignoreErrors: - - '#Access to an undefined property PhpParser\\Node\\FunctionLike::\$name.#' includes: - phpstan-strict-rules.neon diff --git a/src/Rules/Conditionals/SwitchMustContainDefaultRule.php b/src/Rules/Conditionals/SwitchMustContainDefaultRule.php index e869212..a0c5278 100644 --- a/src/Rules/Conditionals/SwitchMustContainDefaultRule.php +++ b/src/Rules/Conditionals/SwitchMustContainDefaultRule.php @@ -13,6 +13,8 @@ /** * A switch statement must always contain a "default" statement. + * + * @implements Rule */ class SwitchMustContainDefaultRule implements Rule { diff --git a/src/Rules/Exceptions/DoNotThrowExceptionBaseClassRule.php b/src/Rules/Exceptions/DoNotThrowExceptionBaseClassRule.php index f0c3228..578d48c 100644 --- a/src/Rules/Exceptions/DoNotThrowExceptionBaseClassRule.php +++ b/src/Rules/Exceptions/DoNotThrowExceptionBaseClassRule.php @@ -11,6 +11,8 @@ /** * This rule checks that the base \Exception class is never thrown. Instead, developers should subclass the \Exception * base class and throw the sub-type. + * + * @implements Rule */ class DoNotThrowExceptionBaseClassRule implements Rule { diff --git a/src/Rules/Exceptions/EmptyExceptionRule.php b/src/Rules/Exceptions/EmptyExceptionRule.php index b1a9cf9..fdef067 100644 --- a/src/Rules/Exceptions/EmptyExceptionRule.php +++ b/src/Rules/Exceptions/EmptyExceptionRule.php @@ -10,6 +10,9 @@ use PHPStan\Rules\Rule; use function strpos; +/** + * @implements Rule + */ class EmptyExceptionRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Exceptions/MustRethrowRule.php b/src/Rules/Exceptions/MustRethrowRule.php index 5d3e842..11393de 100644 --- a/src/Rules/Exceptions/MustRethrowRule.php +++ b/src/Rules/Exceptions/MustRethrowRule.php @@ -19,6 +19,8 @@ /** * When catching \Exception, \RuntimeException or \Throwable, the exception MUST be thrown again * (unless you are developing an exception handler...) + * + * @implements Rule */ class MustRethrowRule implements Rule { @@ -49,6 +51,9 @@ public function processNode(Node $node, Scope $scope): array // Let's visit and find a throw. $visitor = new class() extends NodeVisitorAbstract { + /** + * @var bool + */ private $throwFound = false; public function leaveNode(Node $node) @@ -56,6 +61,7 @@ public function leaveNode(Node $node) if ($node instanceof Node\Stmt\Throw_) { $this->throwFound = true; } + return null; } /** diff --git a/src/Rules/Exceptions/ThrowMustBundlePreviousExceptionRule.php b/src/Rules/Exceptions/ThrowMustBundlePreviousExceptionRule.php index 3793569..b725f9c 100644 --- a/src/Rules/Exceptions/ThrowMustBundlePreviousExceptionRule.php +++ b/src/Rules/Exceptions/ThrowMustBundlePreviousExceptionRule.php @@ -14,6 +14,8 @@ /** * When throwing into a catch block, checks that the previous exception is passed to the new "throw" clause * (the initial stack trace must not be lost). + * + * @implements Rule */ class ThrowMustBundlePreviousExceptionRule implements Rule { @@ -34,7 +36,13 @@ public function processNode(Node $node, Scope $scope): array * @var string */ private $catchedVariableName; + /** + * @var int + */ private $exceptionUsedCount = 0; + /** + * @var Node\Stmt\Throw_[] + */ private $unusedThrows = []; public function __construct(string $catchedVariableName) @@ -48,6 +56,7 @@ public function leaveNode(Node $node) if ($node->name === $this->catchedVariableName) { $this->exceptionUsedCount++; } + return null; } // If the variable is used in the context of a method call (like $e->getMessage()), the exception is not passed as a "previous exception". @@ -60,6 +69,7 @@ public function leaveNode(Node $node) if ($node instanceof Node\Stmt\Throw_ && $this->exceptionUsedCount === 0) { $this->unusedThrows[] = $node; } + return null; } /** diff --git a/src/Rules/Superglobals/NoSuperglobalsRule.php b/src/Rules/Superglobals/NoSuperglobalsRule.php index 6856663..2bd72fa 100644 --- a/src/Rules/Superglobals/NoSuperglobalsRule.php +++ b/src/Rules/Superglobals/NoSuperglobalsRule.php @@ -13,6 +13,8 @@ /** * This rule checks that no superglobals are used in code. + * + * @implements Rule */ class NoSuperglobalsRule implements Rule { diff --git a/src/Rules/TypeHints/AbstractMissingTypeHintRule.php b/src/Rules/TypeHints/AbstractMissingTypeHintRule.php deleted file mode 100644 index 1abf228..0000000 --- a/src/Rules/TypeHints/AbstractMissingTypeHintRule.php +++ /dev/null @@ -1,371 +0,0 @@ -broker = $broker; - } - - abstract public function getNodeType(): string; - - abstract public function isReturnIgnored(Node $node): bool; - - abstract protected function getReflection(Node\FunctionLike $function, Scope $scope, Broker $broker) : ParametersAcceptorWithPhpDocs; - - abstract protected function shouldSkip(Node\FunctionLike $function, Scope $scope): bool; - - /** - * @param \PhpParser\Node\Stmt\Function_|\PhpParser\Node\Stmt\ClassMethod $node - * @param \PHPStan\Analyser\Scope $scope - * @return string[] - */ - public function processNode(Node $node, Scope $scope): array - { - /*if ($node->getLine() < 0) { - // Fixes some problems with methods in anonymous class (the line number is poorly reported). - return []; - }*/ - - if ($this->shouldSkip($node, $scope)) { - return []; - } - - $parametersAcceptor = $this->getReflection($node, $scope, $this->broker); - - $errors = []; - - foreach ($parametersAcceptor->getParameters() as $parameter) { - if ($parameter instanceof PhpParameterReflection) { - $debugContext = new ParameterDebugContext($scope, $node, $parameter); - $result = $this->analyzeParameter($debugContext, $parameter); - - if ($result !== null) { - $errors[] = $result; - } - } - } - - if (!$this->isReturnIgnored($node)) { - $debugContext = new FunctionDebugContext($scope, $node); - $returnTypeError = $this->analyzeReturnType($debugContext, $parametersAcceptor); - if ($returnTypeError !== null) { - $errors[] = $returnTypeError; - } - } - - return $errors; - } - - /** - * Analyzes a parameter and returns the error string if something goes wrong or null if everything is ok. - * - * @param PhpParameterReflection $parameter - * @return null|string - */ - private function analyzeParameter(DebugContextInterface $context, PhpParameterReflection $parameter): ?string - { - //$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); - - $phpTypeHint = $parameter->getNativeType(); - //try { - $docBlockTypeHints = $parameter->getPhpDocType(); - /*} catch (\InvalidArgumentException $e) { - return sprintf('%s, for parameter $%s, invalid docblock @param encountered. %s', - $this->getContext($parameter), - $parameter->getName(), - $e->getMessage() - ); - }*/ - - if ($phpTypeHint instanceof MixedType && $phpTypeHint->isExplicitMixed() === false) { - return $this->analyzeWithoutTypehint($context, $docBlockTypeHints); - } else { - // If there is a type-hint, we have nothing to say unless it is an array. - if ($parameter->isVariadic()) { - // Hack: wrap the native type in an array is variadic - $phpTypeHint = new ArrayType(new IntegerType(), $phpTypeHint); - } - - return $this->analyzeWithTypehint($context, $phpTypeHint, $docBlockTypeHints); - } - } - - /** - * @return null|string - */ - private function analyzeReturnType(DebugContextInterface $debugContext, ParametersAcceptorWithPhpDocs $function): ?string - { - $phpTypeHint = $function->getNativeReturnType(); - $docBlockTypeHints = $function->getPhpDocReturnType(); - - // If there is a type-hint, we have nothing to say unless it is an array. - if ($phpTypeHint instanceof MixedType && $phpTypeHint->isExplicitMixed() === false) { - return $this->analyzeWithoutTypehint($debugContext, $docBlockTypeHints); - } else { - return $this->analyzeWithTypehint($debugContext, $phpTypeHint, $docBlockTypeHints); - } - } - - /** - * @param DebugContextInterface $debugContext - * @param Type $phpTypeHint - * @param Type $docBlockTypeHints - * @return null|string - */ - private function analyzeWithTypehint(DebugContextInterface $debugContext, Type $phpTypeHint, Type $docBlockTypeHints): ?string - { - $docblockWithoutNullable = $this->typesWithoutNullable($docBlockTypeHints); - - if (!$this->isTypeIterable($phpTypeHint)) { - // FIXME: this should be handled with the "accepts" method of types (and actually, this is already triggered by PHPStan 0.10) - - if ($docBlockTypeHints instanceof MixedType && $docBlockTypeHints->isExplicitMixed() === false) { - // No docblock. - return null; - } - - // Let's detect mismatches between docblock and PHP typehint - if ($docblockWithoutNullable instanceof UnionType) { - $docblocks = $docblockWithoutNullable->getTypes(); - } else { - $docblocks = [$docblockWithoutNullable]; - } - $phpTypeHintWithoutNullable = $this->typesWithoutNullable($phpTypeHint); - foreach ($docblocks as $docblockTypehint) { - if (get_class($docblockTypehint) !== get_class($phpTypeHintWithoutNullable)) { - if ($debugContext instanceof ParameterDebugContext) { - return sprintf('%s type is type-hinted to "%s" but the @param annotation says it is a "%s". Please fix the @param annotation.', (string) $debugContext, $phpTypeHint->describe(VerbosityLevel::typeOnly()), $docblockTypehint->describe(VerbosityLevel::typeOnly())); - } elseif (!$docblockTypehint instanceof MixedType || $docblockTypehint->isExplicitMixed()) { - return sprintf('%s return type is type-hinted to "%s" but the @return annotation says it is a "%s". Please fix the @return annotation.', (string) $debugContext, $phpTypeHint->describe(VerbosityLevel::typeOnly()), $docblockTypehint->describe(VerbosityLevel::typeOnly())); - } - } - } - - return null; - } - - if ($phpTypeHint instanceof ArrayType) { - if ($docblockWithoutNullable instanceof MixedType && !$docblockWithoutNullable->isExplicitMixed()) { - if ($debugContext instanceof ParameterDebugContext) { - return sprintf('%s type is "array". Please provide a @param annotation to further specify the type of the array. For instance: @param int[] $%s. More info: http://bit.ly/typehintarray', (string) $debugContext, $debugContext->getName()); - } else { - return sprintf('%s return type is "array". Please provide a @return annotation to further specify the type of the array. For instance: @return int[]. More info: http://bit.ly/typehintarray', (string) $debugContext); - } - } else { - if ($docblockWithoutNullable instanceof UnionType) { - $docblocks = $docblockWithoutNullable->getTypes(); - } else { - $docblocks = [$docblockWithoutNullable]; - } - foreach ($docblocks as $docblockTypehint) { - if (!$this->isTypeIterable($docblockTypehint)) { - if ($debugContext instanceof ParameterDebugContext) { - return sprintf('%s mismatching type-hints for parameter %s. PHP type hint is "array" and docblock type hint is %s.', (string) $debugContext, $debugContext->getName(), $docblockTypehint->describe(VerbosityLevel::typeOnly())); - } else { - return sprintf('%s mismatching type-hints for return type. PHP type hint is "array" and docblock declared return type is %s.', (string) $debugContext, $docblockTypehint->describe(VerbosityLevel::typeOnly())); - } - } - - if ($docblockTypehint instanceof ArrayType && $docblockTypehint->getKeyType() instanceof MixedType && $docblockTypehint->getItemType() instanceof MixedType && $docblockTypehint->getKeyType()->isExplicitMixed() && $docblockTypehint->getItemType()->isExplicitMixed()) { - if ($debugContext instanceof ParameterDebugContext) { - return sprintf('%s type is "array". Please provide a more specific @param annotation in the docblock. For instance: @param int[] $%s. Use @param mixed[] $%s if this is really an array of mixed values. More info: http://bit.ly/typehintarray', (string) $debugContext, $debugContext->getName(), $debugContext->getName()); - } else { - return sprintf('%s return type is "array". Please provide a more specific @return annotation. For instance: @return int[]. Use @return mixed[] if this is really an array of mixed values. More info: http://bit.ly/typehintarray', (string) $debugContext); - } - } - } - } - } - - return null; - } - - private function isTypeIterable(Type $phpTypeHint) : bool - { - return /*$phpTypeHint->isIterable()->maybe() ||*/ $phpTypeHint->isIterable()->yes(); - /*if ($phpTypeHint instanceof Array_ || $phpTypeHint instanceof Iterable_) { - return true; - } - if ($phpTypeHint instanceof Object_) { - // TODO: cache BetterReflection for better performance! - try { - $class = (new BetterReflection())->classReflector()->reflect((string) $phpTypeHint); - } catch (IdentifierNotFound $e) { - // Class not found? Let's not throw an error. It will be caught by other rules anyway. - return false; - } - if ($class->implementsInterface('Traversable')) { - return true; - } - } - - return false;*/ - } - - /** - * @param DebugContextInterface $debugContext - * @param Type $docBlockTypeHints - * @return null|string - */ - private function analyzeWithoutTypehint(DebugContextInterface $debugContext, Type $docBlockTypeHints): ?string - { - if ($docBlockTypeHints instanceof MixedType && $docBlockTypeHints->isExplicitMixed() === false) { - if ($debugContext instanceof ParameterDebugContext) { - return sprintf('%s has no type-hint and no @param annotation. More info: http://bit.ly/usetypehint', (string) $debugContext); - } else { - return sprintf('%s there is no return type and no @return annotation. More info: http://bit.ly/usetypehint', (string) $debugContext); - } - } - - $nativeTypehint = $this->isNativelyTypehintable($docBlockTypeHints); - - if ($nativeTypehint !== null) { - if ($debugContext instanceof ParameterDebugContext) { - return sprintf('%s can be type-hinted to "%s". More info: http://bit.ly/usetypehint', (string) $debugContext, $nativeTypehint); - } else { - return sprintf('%s a "%s" return type can be added. More info: http://bit.ly/usetypehint', (string) $debugContext, $nativeTypehint); - } - } - - return null; - } - - /** - * @param Type $docBlockTypeHints - * @return string|null - */ - private function isNativelyTypehintable(Type $docBlockTypeHints): ?string - { - if ($docBlockTypeHints instanceof UnionType) { - $count = count($docBlockTypeHints->getTypes()); - } else { - $count = 1; - } - - if ($count > 2) { - return null; - } - $isNullable = $this->isNullable($docBlockTypeHints); - if ($count === 2 && !$isNullable) { - return null; - } - - $type = $this->typesWithoutNullable($docBlockTypeHints); - // At this point, there is at most one element here - /*if (empty($type)) { - return null; - }*/ - - //$type = $types[0]; - - // "object" type-hint is not available in PHP 7.1 - if ($type instanceof ObjectWithoutClassType) { - // In PHP 7.2, this is true but not in PHP 7.1 - return null; - } - - if ($type instanceof ObjectType) { - return ($isNullable?'?':'').'\\'.$type->describe(VerbosityLevel::typeOnly()); - } - - if ($type instanceof ArrayType) { - return ($isNullable?'?':'').'array'; - } - - if ($this->isNativeType($type)) { - return ($isNullable?'?':'').$type->describe(VerbosityLevel::typeOnly()); - } - - // TODO: more definitions to add here - // Manage interface/classes - // Manage array of things => (cast to array) - - - return null; - } - - private function isNativeType(Type $type): bool - { - if ($type instanceof StringType - || $type instanceof IntegerType - || $type instanceof BooleanType - || $type instanceof FloatType - || $type instanceof CallableType - || $type instanceof IterableType - || $type instanceof VoidType - ) { - return true; - } - return false; - } - - /** - * @param Type $docBlockTypeHints - * @return bool - */ - private function isNullable(Type $docBlockTypeHints): bool - { - if ($docBlockTypeHints instanceof UnionType) { - foreach ($docBlockTypeHints->getTypes() as $docBlockTypeHint) { - if ($docBlockTypeHint instanceof NullType) { - return true; - } - } - } - return false; - } - - /** - * Removes "null" from the list of types. - * - * @param Type $docBlockTypeHints - * @return Type - */ - private function typesWithoutNullable(Type $docBlockTypeHints): Type - { - if ($docBlockTypeHints instanceof UnionType) { - $filteredTypes = array_values(array_filter($docBlockTypeHints->getTypes(), function (Type $item) { - return !$item instanceof NullType; - })); - if (\count($filteredTypes) === 1) { - return $filteredTypes[0]; - } - return new UnionType($filteredTypes); - } - return $docBlockTypeHints; - } -} diff --git a/src/Rules/TypeHints/DebugContextInterface.php b/src/Rules/TypeHints/DebugContextInterface.php deleted file mode 100644 index 759208f..0000000 --- a/src/Rules/TypeHints/DebugContextInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -function = $function; - $this->scope = $scope; - } - - public function __toString() - { - if ($this->function instanceof ClassMethod) { - if (!$this->scope->isInClass()) { - return 'Should not happen'; - } - - return sprintf('In method "%s::%s",', $this->scope->getClassReflection()->getDisplayName(), $this->function->name->name); - } - elseif ($this->function instanceof Function_) { - return sprintf('In function "%s",', $this->function->name->name); - } - return 'Should not happen'; - } -} \ No newline at end of file diff --git a/src/Rules/TypeHints/MissingTypeHintInFunctionRule.php b/src/Rules/TypeHints/MissingTypeHintInFunctionRule.php deleted file mode 100644 index 1b0a86a..0000000 --- a/src/Rules/TypeHints/MissingTypeHintInFunctionRule.php +++ /dev/null @@ -1,50 +0,0 @@ -name->name; - if (isset($function->namespacedName)) { - $functionName = (string) $function->namespacedName; - } - $functionNameName = new Node\Name($functionName); - if (!$broker->hasCustomFunction($functionNameName, null)) { - throw new \RuntimeException("Cannot find function '$functionName'"); - } - $functionReflection = $broker->getCustomFunction($functionNameName, null); - /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); - return $parametersAcceptor; - } - - protected function shouldSkip(Node\FunctionLike $function, Scope $scope): bool - { - return false; - } -} diff --git a/src/Rules/TypeHints/MissingTypeHintInMethodRule.php b/src/Rules/TypeHints/MissingTypeHintInMethodRule.php deleted file mode 100644 index 8c59690..0000000 --- a/src/Rules/TypeHints/MissingTypeHintInMethodRule.php +++ /dev/null @@ -1,101 +0,0 @@ - true, - '__destruct' => true, - '__call' => true, - '__callStatic' => true, - '__get' => true, - '__set' => true, - '__isset' => true, - '__unset' => true, - '__sleep' => true, - '__wakeup' => true, - '__toString' => true, - '__invoke' => true, - '__set_state' => true, - '__clone' => true, - '__debugInfo' => true - ]; - - public function getNodeType(): string - { - return Node\Stmt\ClassMethod::class; - } - - /** - * @param Node\Stmt\ClassMethod $node - * @return bool - */ - public function isReturnIgnored(Node $node): bool - { - return isset(self::RETURN_BLACKLIST[$node->name->name]); - } - - protected function getReflection(Node\FunctionLike $function, Scope $scope, Broker $broker) : ParametersAcceptorWithPhpDocs - { - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $nativeMethod = $scope->getClassReflection()->getNativeMethod($function->name->name); - if (!$nativeMethod instanceof PhpMethodReflection) { - throw new \PHPStan\ShouldNotHappenException(); - } - /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($nativeMethod->getVariants()); - return $parametersAcceptor; - } - - protected function shouldSkip(Node\FunctionLike $function, Scope $scope): bool - { - // We should skip if the method is inherited! - if (!$scope->isInClass()) { - throw new \PHPStan\ShouldNotHappenException(); - } - $nativeMethod = $scope->getClassReflection()->getNativeMethod($function->name->name); - - return $this->isInherited2($nativeMethod, $scope->getClassReflection()); - - } - - private function isInherited2(MethodReflection $method, ClassReflection $class = null): bool - { - if ($class === null) { - $class = $method->getDeclaringClass(); - } - $interfaces = $class->getInterfaces(); - foreach ($interfaces as $interface) { - if ($interface->hasMethod($method->getName())) { - return true; - } - } - - $parentClass = $class->getParentClass(); - if ($parentClass !== false) { - if ($parentClass->hasMethod($method->getName())) { - return true; - } - return $this->isInherited2($method, $parentClass); - } - - return false; - } -} diff --git a/src/Rules/TypeHints/ParameterDebugContext.php b/src/Rules/TypeHints/ParameterDebugContext.php deleted file mode 100644 index b184e31..0000000 --- a/src/Rules/TypeHints/ParameterDebugContext.php +++ /dev/null @@ -1,56 +0,0 @@ -function = $function; - $this->parameter = $parameter; - $this->scope = $scope; - } - - public function __toString() - { - if ($this->function instanceof ClassMethod) { - if (!$this->scope->isInClass()) { - return 'Should not happen'; - } - - return sprintf('In method "%s::%s", parameter $%s', $this->scope->getClassReflection()->getDisplayName(), $this->function->name->name, $this->parameter->getName()); - } - elseif ($this->function instanceof Function_) { - return sprintf('In function "%s", parameter $%s', $this->function->name->name, $this->parameter->getName()); - } - return 'Should not happen'; - } - - public function getName(): string - { - return $this->parameter->getName(); - } -} \ No newline at end of file diff --git a/tests/Rules/TypeHints/MissingTypeHintRuleInFunctionTest.php b/tests/Rules/TypeHints/MissingTypeHintRuleInFunctionTest.php deleted file mode 100644 index c04d0ec..0000000 --- a/tests/Rules/TypeHints/MissingTypeHintRuleInFunctionTest.php +++ /dev/null @@ -1,100 +0,0 @@ -createBroker() - ); - } - - public function testCheckCatchedException() - { - require_once __DIR__.'/data/typehints.php'; - - $this->analyse([__DIR__ . '/data/typehints.php'], [ - [ - 'In function "test", parameter $no_type_hint has no type-hint and no @param annotation. More info: http://bit.ly/usetypehint', - 3, - ], - [ - 'In function "test", there is no return type and no @return annotation. More info: http://bit.ly/usetypehint', - 3, - ], - [ - 'In function "test2", parameter $type_hintable can be type-hinted to "?string". More info: http://bit.ly/usetypehint', - 11, - ], - [ - 'In function "test2", a "string" return type can be added. More info: http://bit.ly/usetypehint', - 11, - ], - [ - 'In function "test3", parameter $type_hintable can be type-hinted to "\DateTimeInterface". More info: http://bit.ly/usetypehint', - 19, - ], - [ - 'In function "test3", a "\DateTimeInterface" return type can be added. More info: http://bit.ly/usetypehint', - 19, - ], - [ - 'In function "test4", parameter $type_hintable can be type-hinted to "array". More info: http://bit.ly/usetypehint', - 27, - ], - [ - 'In function "test4", a "array" return type can be added. More info: http://bit.ly/usetypehint', - 27, - ], - [ - 'In function "test6", parameter $better_type_hint type is "array". Please provide a @param annotation to further specify the type of the array. For instance: @param int[] $better_type_hint. More info: http://bit.ly/typehintarray', - 38, - ], - [ - 'In function "test6", return type is "array". Please provide a @return annotation to further specify the type of the array. For instance: @return int[]. More info: http://bit.ly/typehintarray', - 38, - ], - [ - 'In function "mismatch", parameter $param type is type-hinted to "string|null" but the @param annotation says it is a "int". Please fix the @param annotation.', - 46, - ], - [ - 'In function "mismatch", return type is type-hinted to "string" but the @return annotation says it is a "int". Please fix the @return annotation.', - 46, - ], - [ - 'In function "test8", parameter $any_array type is "array". Please provide a more specific @param annotation in the docblock. For instance: @param int[] $any_array. Use @param mixed[] $any_array if this is really an array of mixed values. More info: http://bit.ly/typehintarray', - 62, - ], - [ - 'In function "test8", return type is "array". Please provide a more specific @return annotation. For instance: @return int[]. Use @return mixed[] if this is really an array of mixed values. More info: http://bit.ly/typehintarray', - 62, - ], - [ - 'In function "test10", parameter $id has no type-hint and no @param annotation. More info: http://bit.ly/usetypehint', - 76, - ], - [ - 'In function "test13", parameter $type_hintable type is type-hinted to "ClassDoesNotExist" but the @param annotation says it is a "array". Please fix the @param annotation.', - 97, - ], - [ - 'In function "test15", parameter $foo type is "array". Please provide a @param annotation to further specify the type of the array. For instance: @param int[] $foo. More info: http://bit.ly/typehintarray', - 110, - ], - [ - 'In function "test15", mismatching type-hints for return type. PHP type hint is "array" and docblock declared return type is a.', - 110, - ], - [ - 'In function "test19", a "void" return type can be added. More info: http://bit.ly/usetypehint', - 139, - ] - - ]); - } -} diff --git a/tests/Rules/TypeHints/MissingTypeHintRuleInMethodTest.php b/tests/Rules/TypeHints/MissingTypeHintRuleInMethodTest.php deleted file mode 100644 index 6e2db8c..0000000 --- a/tests/Rules/TypeHints/MissingTypeHintRuleInMethodTest.php +++ /dev/null @@ -1,32 +0,0 @@ -createBroker() - ); - } - - public function testCheckCatchedException() - { - require_once __DIR__.'/data/typehints_in_methods.php'; - - $this->analyse([__DIR__ . '/data/typehints_in_methods.php'], [ - [ - 'In method "TheCodingMachine\PHPStan\Rules\TypeHints\data\Foo::test", parameter $no_type_hint has no type-hint and no @param annotation. More info: http://bit.ly/usetypehint', - 9, - ], - [ - 'In method "TheCodingMachine\PHPStan\Rules\TypeHints\data\BazClass::notInherited", parameter $no_type_hint has no type-hint and no @param annotation. More info: http://bit.ly/usetypehint', - 37, - ], - - ]); - } -} diff --git a/tests/Rules/TypeHints/data/StubClass.php b/tests/Rules/TypeHints/data/StubClass.php deleted file mode 100644 index 6e04dd1..0000000 --- a/tests/Rules/TypeHints/data/StubClass.php +++ /dev/null @@ -1,19 +0,0 @@ -