diff --git a/Makefile b/Makefile index ad8e29ff..9a04cb0b 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ csfix: ## it launches cs fix PHP_CS_FIXER_IGNORE_ENV=1 bin/php-cs-fixer fix -v psalm: ## it launches psalm - bin/psalm.phar + bin/psalm.phar --no-cache build: ## it launches all the build composer install diff --git a/src/Analyzer/FileVisitor.php b/src/Analyzer/FileVisitor.php index db2ffcd7..7ffedda9 100644 --- a/src/Analyzer/FileVisitor.php +++ b/src/Analyzer/FileVisitor.php @@ -29,30 +29,43 @@ public function enterNode(Node $node): void { $this->handleClassNode($node); + // handles anonymous class definition like new class() {} $this->handleAnonClassNode($node); + // handles enum definition $this->handleEnumNode($node); + // handles interface definition like interface MyInterface {} + $this->handleInterfaceNode($node); + + // handles trait definition like trait MyTrait {} + $this->handleTraitNode($node); + + // handles code like $constantValue = StaticClass::constant; $this->handleStaticClassConstantNode($node); + // handles code like $static = StaticClass::foo(); $this->handleStaticClassCallsNode($node); + // handles code lik $a instanceof MyClass $this->handleInstanceOf($node); + // handles code like $a = new MyClass(); $this->handleNewExpression($node); + // handles code like public MyClass $myClass; $this->handleTypedProperty($node); + // handles docblock like /** @var MyClass $myClass */ $this->handleDocComment($node); + // handles code like public function myMethod(MyClass $myClass) {} $this->handleParamDependency($node); - $this->handleInterfaceNode($node); - - $this->handleTraitNode($node); - + // handles code like public function myMethod(): MyClass {} $this->handleReturnTypeDependency($node); + // handles attribute definition like #[MyAttribute] $this->handleAttributeNode($node); } @@ -145,117 +158,97 @@ private function handleAnonClassNode(Node $node): void private function handleEnumNode(Node $node): void { - if ($node instanceof Node\Stmt\Enum_ && null !== $node->namespacedName) { - $this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString()); - $this->classDescriptionBuilder->setEnum(true); + if (!($node instanceof Node\Stmt\Enum_)) { + return; + } - foreach ($node->implements as $interface) { - $this->classDescriptionBuilder - ->addInterface($interface->toString(), $interface->getLine()); - } + if (null == $node->namespacedName) { + return; + } + + $this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString()); + $this->classDescriptionBuilder->setEnum(true); + + foreach ($node->implements as $interface) { + $this->classDescriptionBuilder + ->addInterface($interface->toString(), $interface->getLine()); } } private function handleStaticClassConstantNode(Node $node): void { - /** - * adding static classes as dependencies - * $constantValue = StaticClass::constant;. - * - * @see FileVisitorTest::test_it_should_return_errors_for_const_outside_namespace - */ - if ( - $node instanceof Node\Expr\ClassConstFetch - && method_exists($node->class, 'toString') - ) { - if ($this->isSelfOrStaticOrParent($node->class->toString())) { - return; - } + if (!($node instanceof Node\Expr\ClassConstFetch)) { + return; + } - $this->classDescriptionBuilder - ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); + if (!($node->class instanceof Node\Name\FullyQualified)) { + return; } + + $this->classDescriptionBuilder + ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); } private function handleStaticClassCallsNode(Node $node): void { - /** - * adding static function classes as dependencies - * $static = StaticClass::foo();. - * - * @see FileVisitorTest::test_should_returns_all_dependencies - */ - if ( - $node instanceof Node\Expr\StaticCall - && method_exists($node->class, 'toString') - ) { - if ($this->isSelfOrStaticOrParent($node->class->toString())) { - return; - } + if (!($node instanceof Node\Expr\StaticCall)) { + return; + } - $this->classDescriptionBuilder - ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); + if (!($node->class instanceof Node\Name\FullyQualified)) { + return; } + + $this->classDescriptionBuilder + ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); } private function handleInstanceOf(Node $node): void { - if ( - $node instanceof Node\Expr\Instanceof_ - && method_exists($node->class, 'toString') - ) { - if ($this->isSelfOrStaticOrParent($node->class->toString())) { - return; - } - $this->classDescriptionBuilder - ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); + if (!($node instanceof Node\Expr\Instanceof_)) { + return; + } + + if (!($node->class instanceof Node\Name\FullyQualified)) { + return; } + + $this->classDescriptionBuilder + ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); } private function handleNewExpression(Node $node): void { - if ( - $node instanceof Node\Expr\New_ - && !($node->class instanceof Node\Expr\Variable) - ) { - if ((method_exists($node->class, 'isAnonymous') && true === $node->class->isAnonymous()) - || !method_exists($node->class, 'toString') - ) { - return; - } - - if ($this->isSelfOrStaticOrParent($node->class->toString())) { - return; - } + if (!($node instanceof Node\Expr\New_)) { + return; + } - $this->classDescriptionBuilder - ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); + if (!($node->class instanceof Node\Name\FullyQualified)) { + return; } + + $this->classDescriptionBuilder + ->addDependency(new ClassDependency($node->class->toString(), $node->getLine())); } private function handleTypedProperty(Node $node): void { - if ($node instanceof Node\Stmt\Property) { - if (null === $node->type) { - return; - } - - $type = $node->type; - if ($type instanceof NullableType) { - $type = $type->type; - } - - if (!method_exists($type, 'toString') || $this->isBuiltInType($type->toString())) { - return; - } - - try { - $this->classDescriptionBuilder - ->addDependency(new ClassDependency($type->toString(), $node->getLine())); - } catch (\Exception $e) { - // Silently ignore - } + if (!($node instanceof Node\Stmt\Property)) { + return; + } + + if (null === $node->type) { + return; + } + + $type = $node->type instanceof NullableType ? $node->type->type : $node->type; + + if (!($type instanceof Node\Name\FullyQualified)) { + return; } + + $this->classDescriptionBuilder + ->addDependency(new ClassDependency($type->toString(), $node->getLine())); } private function handleDocComment(Node $node): void @@ -278,92 +271,82 @@ private function handleParamDependency(Node $node): void private function handleInterfaceNode(Node $node): void { - if ($node instanceof Node\Stmt\Interface_) { - if (null === $node->namespacedName) { - return; - } + if (!($node instanceof Node\Stmt\Interface_)) { + return; + } - $this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString()); - $this->classDescriptionBuilder->setInterface(true); + if (null === $node->namespacedName) { + return; + } - foreach ($node->extends as $interface) { - $this->classDescriptionBuilder - ->addExtends($interface->toString(), $interface->getLine()); - } + $this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString()); + $this->classDescriptionBuilder->setInterface(true); + + foreach ($node->extends as $interface) { + $this->classDescriptionBuilder + ->addExtends($interface->toString(), $interface->getLine()); } } private function handleTraitNode(Node $node): void { - if ($node instanceof Node\Stmt\Trait_) { - if (null === $node->namespacedName) { - return; - } + if (!($node instanceof Node\Stmt\Trait_)) { + return; + } - $this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString()); - $this->classDescriptionBuilder->setTrait(true); + if (null === $node->namespacedName) { + return; } + + $this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString()); + $this->classDescriptionBuilder->setTrait(true); } private function handleReturnTypeDependency(Node $node): void { - if ($node instanceof Node\Stmt\ClassMethod) { - $returnType = $node->returnType; - if ($returnType instanceof Node\Name\FullyQualified) { - $this->classDescriptionBuilder - ->addDependency(new ClassDependency($returnType->toString(), $returnType->getLine())); - } + if (!($node instanceof Node\Stmt\ClassMethod)) { + return; } - } - private function handleAttributeNode(Node $node): void - { - if ($node instanceof Node\Attribute) { - $nodeName = $node->name; + $returnType = $node->returnType; - if ($nodeName instanceof Node\Name\FullyQualified) { - $this->classDescriptionBuilder - ->addAttribute($node->name->toString(), $node->getLine()); - } + if (!($returnType instanceof Node\Name\FullyQualified)) { + return; } - } - private function isSelfOrStaticOrParent(string $dependencyClass): bool - { - return 'self' === $dependencyClass || 'static' === $dependencyClass || 'parent' === $dependencyClass; + $this->classDescriptionBuilder + ->addDependency(new ClassDependency($returnType->toString(), $returnType->getLine())); } - private function addParamDependency(Node\Param $node): void + private function handleAttributeNode(Node $node): void { - if (null === $node->type || $node->type instanceof Node\Identifier) { + if (!($node instanceof Node\Attribute)) { return; } - $type = $node->type; - if ($type instanceof NullableType) { - /** @var NullableType * */ - $nullableType = $type; - $type = $nullableType->type; - } + $nodeName = $node->name; - if (method_exists($type, 'isSpecialClassName') && true === $type->isSpecialClassName()) { + if (!($nodeName instanceof Node\Name\FullyQualified)) { return; } - if (!method_exists($type, 'toString')) { + $this->classDescriptionBuilder + ->addAttribute($node->name->toString(), $node->getLine()); + } + + private function addParamDependency(Node\Param $node): void + { + if (null === $node->type || $node->type instanceof Node\Identifier) { return; } - if ($this->isBuiltInType($type->toString())) { + $type = $node->type instanceof NullableType ? $node->type->type : $node->type; + + if (!($type instanceof Node\Name\FullyQualified)) { return; } $this->classDescriptionBuilder ->addDependency(new ClassDependency($type->toString(), $node->getLine())); } - - private function isBuiltInType(string $typeName): bool - { - return \in_array($typeName, ['bool', 'int', 'float', 'string', 'array', 'resource', 'object', 'null']); - } } diff --git a/tests/Unit/Analyzer/FileParser/CanParseClassPropertiesTest.php b/tests/Unit/Analyzer/FileParser/CanParseClassPropertiesTest.php index 41a630a2..91d6d94a 100644 --- a/tests/Unit/Analyzer/FileParser/CanParseClassPropertiesTest.php +++ b/tests/Unit/Analyzer/FileParser/CanParseClassPropertiesTest.php @@ -51,6 +51,10 @@ public function test_it_parse_typed_nullable_property(): void class ApplicationLevelDto { public ?NotBlank $foo; + + public ?string $bar; + + public self $baz; } EOF; @@ -59,12 +63,11 @@ class ApplicationLevelDto $cd = $fp->getClassDescriptions(); - $violations = new Violations(); - - $notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']); - $notHaveDependencyOutsideNamespace->evaluate($cd[0], $violations, 'we want to add this rule for our software'); + $cd = $fp->getClassDescriptions(); + $dep = $cd[0]->getDependencies(); - self::assertCount(1, $violations); + self::assertCount(1, $dep); + self::assertEquals('Symfony\Component\Validator\Constraints\NotBlank', $dep[0]->getFQCN()->toString()); } public function test_it_parse_scalar_typed_property(): void @@ -87,13 +90,9 @@ class ApplicationLevelDto $fp->parse($code, 'relativePathName'); $cd = $fp->getClassDescriptions(); + $dep = $cd[0]->getDependencies(); - $violations = new Violations(); - - $notHaveDependencyOutsideNamespace = new NotHaveDependencyOutsideNamespace('MyProject\AppBundle\Application'); - $notHaveDependencyOutsideNamespace->evaluate($cd[0], $violations, 'we want to add this rule for our software'); - - self::assertCount(0, $violations); + self::assertCount(0, $dep); } public function test_it_parse_nullable_scalar_typed_property(): void @@ -137,9 +136,11 @@ public function test_it_parse_arrays_as_scalar_types(): void class MyClass { private array $field1; - public function __construct(array $field1) + public function __construct(array $field1, int $field2, self $field3) { $this->field1 = $field1; + $this->field2 = $field2; + $this->field3 = $field3; } } EOF; @@ -148,12 +149,8 @@ public function __construct(array $field1) $fp->parse($code, 'relativePathName'); $cd = $fp->getClassDescriptions(); + $dep = $cd[0]->getDependencies(); - $violations = new Violations(); - - $notHaveDependenciesOutside = new NotHaveDependencyOutsideNamespace('App\Domain'); - $notHaveDependenciesOutside->evaluate($cd[0], $violations, 'we want to add this rule for our software'); - - self::assertCount(0, $violations); + self::assertCount(0, $dep); } } diff --git a/tests/Unit/Analyzer/FileParser/CanParseClassTest.php b/tests/Unit/Analyzer/FileParser/CanParseClassTest.php index 6e48c009..8b0f0beb 100644 --- a/tests/Unit/Analyzer/FileParser/CanParseClassTest.php +++ b/tests/Unit/Analyzer/FileParser/CanParseClassTest.php @@ -51,6 +51,33 @@ public function __construct(Request $request) self::assertEquals('path/to/class.php', $violations->get(1)->getFilePath()); } + public function test_should_parse_instanceof(): void + { + $code = <<< 'EOF' + parse($code, 'relativePathName'); + $cd = $fp->getClassDescriptions(); + + self::assertCount(1, $cd); + self::assertCount(1, $cd[0]->getDependencies()); + self::assertEquals('Foo\Bar\MyClass', $cd[0]->getDependencies()[0]->getFQCN()->toString()); + } + public function test_should_create_a_class_description(): void { $code = <<< 'EOF' @@ -275,6 +302,8 @@ public function __construct(Request $request, ?Nullable $nullable) { $collection = new Collection($request); $static = StaticClass::foo(); + + $self_static = self::foo(); } } EOF;