Skip to content

Commit

Permalink
ClassLikeExtractor.php implementation and better scoping for test res…
Browse files Browse the repository at this point in the history
…olving issues with discovering symbols.
  • Loading branch information
patrickkusebauch committed Mar 28, 2024
1 parent aecac14 commit f7d1339
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 179 deletions.
9 changes: 0 additions & 9 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
parameters:
ignoreErrors:
-
message: "#^Property Qossmic\\\\Deptrac\\\\Contract\\\\Analyser\\\\EventHelper\\:\\:\\$unmatchedSkippedViolation \\(array\\<string, list\\<string\\>\\>\\) does not accept array\\<string, array\\<int\\<0, max\\>, string\\>\\>\\.$#"
count: 1
path: src/Contract/Analyser/EventHelper.php

-
message: "#^Method Qossmic\\\\Deptrac\\\\Contract\\\\Result\\\\OutputResult\\:\\:allOf\\(\\) should return list\\<T of Qossmic\\\\Deptrac\\\\Contract\\\\Result\\\\RuleInterface\\> but returns list\\<Qossmic\\\\Deptrac\\\\Contract\\\\Result\\\\RuleInterface\\>\\.$#"
Expand All @@ -20,11 +16,6 @@ parameters:
count: 1
path: src/Core/Ast/AstMap/ReferenceBuilder.php

-
message: "#^Property Qossmic\\\\Deptrac\\\\Core\\\\Ast\\\\AstMap\\\\ReferenceBuilder\\:\\:\\$tokenTemplates \\(list\\<string\\>\\) does not accept array\\<int\\<0, max\\>, string\\>\\.$#"
count: 1
path: src/Core/Ast/AstMap/ReferenceBuilder.php

-
message: "#^Calling PHPStan\\\\Analyser\\\\ScopeContext\\:\\:enterClass\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
count: 1
Expand Down
51 changes: 50 additions & 1 deletion src/Core/Ast/Parser/Extractors/ClassLikeExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Qossmic\Deptrac\Core\Ast\AstMap\ReferenceBuilder;
use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicTypeResolver;
use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\TypeScope;
use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanContainerDecorator;
use Qossmic\Deptrac\Core\Ast\Parser\PhpStanParser\PhpStanTypeResolver;

/**
* @implements ReferenceExtractorInterface<ClassLike>
Expand All @@ -28,6 +30,8 @@ class ClassLikeExtractor implements ReferenceExtractorInterface
private readonly PhpDocParser $docParser;

public function __construct(
private readonly PhpStanContainerDecorator $phpStanContainer,
private readonly PhpStanTypeResolver $phpStanTypeResolver,
private readonly NikicTypeResolver $typeResolver
) {
$this->lexer = new Lexer();
Expand Down Expand Up @@ -92,7 +96,52 @@ public function processNodeWithClassicScope(Node $node, ReferenceBuilder $refere

public function processNodeWithPhpStanScope(Node $node, ReferenceBuilder $referenceBuilder, Scope $scope): void
{
// TODO: @Incomplete (Patrick Kusebauch @ 04.03.24)
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attribute) {
foreach ($this->phpStanTypeResolver->resolveType($attribute->name, $scope) as $classLikeName) {
$referenceBuilder->attribute($classLikeName, $attribute->getLine());
}
}
}

$docComment = $node->getDocComment();
if (!$docComment instanceof Doc) {
return;
}

$fileTypeMapper = $this->phpStanContainer->createFileTypeMapper();
$classReflection = $scope->getClassReflection();
assert(null !== $classReflection);

$resolvedPhpDoc = $fileTypeMapper->getResolvedPhpDoc(
$scope->getFile(),
$classReflection->getName(),
$scope->getTraitReflection()?->getName(),
$scope->getFunction()?->getName(),
$docComment->getText(),
);

foreach ($resolvedPhpDoc->getMethodTags() as $methodTag) {
foreach ($methodTag->getParameters() as $methodTagParameter) {
foreach ($methodTagParameter->getType()->getReferencedClasses() as $referencedClass) {
$referenceBuilder->parameter($referencedClass, $node->getStartLine());
}
}
foreach ($methodTag->getReturnType()->getReferencedClasses() as $referencedClass) {
$referenceBuilder->returnType($referencedClass, $node->getStartLine());
}
}

foreach ($resolvedPhpDoc->getPropertyTags() as $propertyTag) {

$referencedClasses = array_merge(
$propertyTag->getReadableType()?->getReferencedClasses() ?? [],
$propertyTag->getWritableType()?->getReferencedClasses() ?? [],
);
foreach (array_unique($referencedClasses) as $referencedClass) {
$referenceBuilder->variable($referencedClass, $node->getStartLine());
}
}
}

public function getNodeType(): string
Expand Down
43 changes: 38 additions & 5 deletions src/Core/Ast/Parser/PhpStanParser/FileReferenceVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\ScopeContext;
use PHPStan\Analyser\ScopeFactory;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\Reflection\ReflectionProvider;
use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReferenceBuilder;
use Qossmic\Deptrac\Core\Ast\AstMap\ReferenceBuilder;
Expand All @@ -28,6 +33,10 @@ class FileReferenceVisitor extends NodeVisitorAbstract

private Scope $scope;

private Lexer $lexer;

private PhpDocParser $docParser;

/**
* @param ReferenceExtractorInterface<\PhpParser\Node> ...$dependencyResolvers
*/
Expand All @@ -41,6 +50,8 @@ public function __construct(
$this->dependencyResolvers = $dependencyResolvers;
$this->currentReference = $fileReferenceBuilder;
$this->scope = $this->scopeFactory->create(ScopeContext::create($this->file));
$this->lexer = new Lexer();
$this->docParser = new PhpDocParser(new TypeParser(), new ConstExprParser());
}

public function enterNode(Node $node)
Expand Down Expand Up @@ -77,12 +88,13 @@ private function enterClassLike(ClassLike $node): void
assert(null !== $name);
$context = ScopeContext::create($this->file)->enterClass($this->reflectionProvider->getClass($name));
$this->scope = $this->scopeFactory->create($context);
$tags = $this->getTags($node);

$this->currentReference = match (true) {
$node instanceof Interface_ => $this->fileReferenceBuilder->newInterface($name, [], []),
$node instanceof Class_ => $this->fileReferenceBuilder->newClass($name, [], []),
$node instanceof Trait_ => $this->fileReferenceBuilder->newTrait($name, [], []),
default => $this->fileReferenceBuilder->newClassLike($name, [], [])
$node instanceof Interface_ => $this->fileReferenceBuilder->newInterface($name, [], $tags),
$node instanceof Class_ => $this->fileReferenceBuilder->newClass($name, [], $tags),
$node instanceof Trait_ => $this->fileReferenceBuilder->newTrait($name, [], $tags),
default => $this->fileReferenceBuilder->newClassLike($name, [], $tags)
};
}

Expand All @@ -91,7 +103,7 @@ private function enterFunction(Node\Stmt\Function_ $node): void
$name = $this->getReferenceName($node);
assert(null !== $name);

$this->currentReference = $this->fileReferenceBuilder->newFunction($name);
$this->currentReference = $this->fileReferenceBuilder->newFunction($name, [], $this->getTags($node));
}

private function getReferenceName(Node\Stmt\Function_|ClassLike $node): ?string
Expand All @@ -106,4 +118,25 @@ private function getReferenceName(Node\Stmt\Function_|ClassLike $node): ?string

return null;
}

/**
* @return array<string,list<string>>
*/
private function getTags(ClassLike|Node\Stmt\Function_ $node): array
{
$docComment = $node->getDocComment();
if (null === $docComment) {
return [];
}

$tokens = new TokenIterator($this->lexer->tokenize($docComment->getText()));
$docNodeCrate = $this->docParser->parse($tokens);

$tags = [];
foreach ($docNodeCrate->getTags() as $tag) {
$tags[$tag->name][] = (string) $tag->value;
}

return $tags;
}
}
Loading

0 comments on commit f7d1339

Please sign in to comment.