Repository: qossmic/deptrac (php)

Issue:
support `@internal` class annotations
to formulate all restrictions we have in our current architecture we need roughly 120 lines within the depfile.
I try to find some ways so the depfile could get easier/simpler without loosing its explicitness(or rules).

we divided our source code in modules, and each of consist of several layers.
our file strucutre (simplified) looks like:

```

├── lib
│   ├── account
│   │   ├── business
│   │   │   └── UserAccount.php
│   │   ├── exception
│   │   │   └── DuplicateUserMailException.php
│   │   ├── persistence
│   │   │   ├── BlockedDeliveryAddressRecord.php
│   │   │   ├── DeliveryAddressAccountRepository.php
│   │   │   └── UserAccountRepository.php
│   ├── auth
│   │   ├── business
│   │   │   ├── EmailVO.php
│   │   │   ├── EncryptedPassword.php
│   │   ├── exception
│   │   │   ├── BlockedUserException.php
│   │   │   ├── InvalidEmailException.php
│   │   ├── persistence
│   │   │   ├── UserAuthRepository.php
│   │   │   ├── UserRecord.php
│   │   │   └── UserRepository.php

```

the file structure and class-names adhere to PSR4

in our `persistence`-layer we define repository  classes, which internally use record classes for the mapping to the database.

```
  - name: Persistence
    collectors:
      - type: className
        regex: .*\\persistence\\.*
```

we have rules which layer is allowed to depend on persistence. but since - because of the namespace - record classes also are part of the persistence layer a service class is actually allowed to reference/use record classes.

```
ruleset:
  Service:
    - Persistence
```

our goal is to hide the record classes within the persistence layer and that other layers which depend on the persistence layer need to call into it thru the repository classes.

atm we need to blacklist every class within the persistence layer which should not be leaked out of the layer with a separate selector and/or rule.

wouldn't it be easier if deptrac would support a class-level annotation, e.g. `@internal` which defines that the given class is not allowed to be referenced outside the layer it resides...?

Pull request description:
Internal call implementation
Fixes #538 

Tests are implemented by making `make deptrac` passing. I am unable to write an e2e test from scratch.

Patch:
diff --git a/docs/concepts.md b/docs/concepts.md
index f0421f08..3bc84b10 100644
--- a/docs/concepts.md
+++ b/docs/concepts.md
@@ -231,6 +231,10 @@ uncovered dependencies are encountered.
 You can use the `--report-uncovered` option to let you know about any uncovered
 dependencies in the report that is generated by Deptrac.
 
+### @internal and @deptrac-internal annotation
+
+You can mark your classes with `@internal` or `@deptrac-internal` annotation. If you do so, the marked class cannot be referenced by any class outside its layer. This will supersede any ruleset configuration you might have defined. This way, you don't have to define 2 separate layers to distinguish between layers external API and its private implementation.
+
 ## Violations
 
 If we have 2 layers (*Models*, *Controller*) and one layer is using the other,
diff --git a/src/Analyser/AstMapExtractor.php b/src/Analyser/AstMapExtractor.php
index cb58da64..b2d71ba3 100644
--- a/src/Analyser/AstMapExtractor.php
+++ b/src/Analyser/AstMapExtractor.php
@@ -8,6 +8,9 @@ use Qossmic\Deptrac\Ast\AstLoader;
 use Qossmic\Deptrac\Ast\AstMap\AstMap;
 use Qossmic\Deptrac\InputCollector\InputCollectorInterface;
 
+/**
+ * @internal
+ */
 class AstMapExtractor
 {
     private InputCollectorInterface $inputCollector;
diff --git a/src/Analyser/EventHandler/AllowDependencyHandler.php b/src/Analyser/EventHandler/AllowDependencyHandler.php
index 7261cb51..08f069bd 100644
--- a/src/Analyser/EventHandler/AllowDependencyHandler.php
+++ b/src/Analyser/EventHandler/AllowDependencyHandler.php
@@ -5,12 +5,16 @@ declare(strict_types=1);
 namespace Qossmic\Deptrac\Analyser\EventHandler;
 
 use Qossmic\Deptrac\Analyser\Event\ProcessEvent;
+use Qossmic\Deptrac\Ast\AstMap\ClassLike\ClassLikeReference;
 use Qossmic\Deptrac\Layer\Exception\CircularReferenceException;
 use Qossmic\Deptrac\Layer\LayerProvider;
 use Qossmic\Deptrac\Result\Allowed;
 use Qossmic\Deptrac\Result\Error;
 use function in_array;
 
+/**
+ * @internal
+ */
 class AllowDependencyHandler
 {
     private LayerProvider $layerProvider;
@@ -40,6 +44,15 @@ class AllowDependencyHandler
                 return;
             }
 
+            if ($dependerLayer !== $dependentLayer) {
+                $dependentReference = $event->getDependentReference();
+                if (($dependentReference instanceof ClassLikeReference)
+                    && $dependentReference->isInternal()
+                ) {
+                    return;
+                }
+            }
+
             $ruleset->add(new Allowed($dependency, $dependerLayer, $dependentLayer));
 
             $event->stopPropagation();
diff --git a/src/Analyser/EventHandler/MatchingLayersHandler.php b/src/Analyser/EventHandler/MatchingLayersHandler.php
index 91998dbe..09fcebbc 100644
--- a/src/Analyser/EventHandler/MatchingLayersHandler.php
+++ b/src/Analyser/EventHandler/MatchingLayersHandler.php
@@ -6,6 +6,9 @@ namespace Qossmic\Deptrac\Analyser\EventHandler;
 
 use Qossmic\Deptrac\Analyser\Event\ProcessEvent;
 
+/**
+ * @internal
+ */
 class MatchingLayersHandler
 {
     public function __invoke(ProcessEvent $event): void
diff --git a/src/Analyser/EventHandler/SkippedViolationHelper.php b/src/Analyser/EventHandler/SkippedViolationHelper.php
index 820f822e..08f13003 100644
--- a/src/Analyser/EventHandler/SkippedViolationHelper.php
+++ b/src/Analyser/EventHandler/SkippedViolationHelper.php
@@ -8,6 +8,8 @@ use function in_array;
 
 /**
  * @psalm-immutable
+ *
+ * @internal
  */
 final class SkippedViolationHelper
 {
diff --git a/src/Analyser/EventHandler/UncoveredDependentHandler.php b/src/Analyser/EventHandler/UncoveredDependentHandler.php
index e13dba1c..e999b71c 100644
--- a/src/Analyser/EventHandler/UncoveredDependentHandler.php
+++ b/src/Analyser/EventHandler/UncoveredDependentHandler.php
@@ -9,6 +9,9 @@ use Qossmic\Deptrac\Analyser\Event\ProcessEvent;
 use Qossmic\Deptrac\Ast\AstMap\ClassLike\ClassLikeToken;
 use Qossmic\Deptrac\Result\Uncovered;
 
+/**
+ * @internal
+ */
 class UncoveredDependentHandler
 {
     private bool $ignoreUncoveredInternalClasses;
diff --git a/src/Analyser/EventHandler/ViolationHandler.php b/src/Analyser/EventHandler/ViolationHandler.php
index b9a57607..c45089b3 100644
--- a/src/Analyser/EventHandler/ViolationHandler.php
+++ b/src/Analyser/EventHandler/ViolationHandler.php
@@ -11,6 +11,9 @@ use Qossmic\Deptrac\Result\SkippedViolation;
 use Qossmic\Deptrac\Result\Violation;
 use function sprintf;
 
+/**
+ * @internal
+ */
 class ViolationHandler
 {
     private SkippedViolationHelper $skippedViolationHelper;
diff --git a/src/Ast/AstMap/ClassLike/ClassLikeReference.php b/src/Ast/AstMap/ClassLike/ClassLikeReference.php
index 507f01d6..fe8cabbd 100644
--- a/src/Ast/AstMap/ClassLike/ClassLikeReference.php
+++ b/src/Ast/AstMap/ClassLike/ClassLikeReference.php
@@ -25,16 +25,19 @@ class ClassLikeReference implements TokenReferenceInterface
     /** @var AstInherit[] */
     private array $inherits;
 
+    private bool $isInternal;
+
     /**
      * @param AstInherit[]      $inherits
      * @param DependencyToken[] $dependencies
      */
-    public function __construct(ClassLikeToken $classLikeName, ClassLikeType $classLikeType = null, array $inherits = [], array $dependencies = [])
+    public function __construct(ClassLikeToken $classLikeName, ClassLikeType $classLikeType = null, array $inherits = [], array $dependencies = [], bool $isInternal = false)
     {
         $this->classLikeName = $classLikeName;
         $this->classLikeType = $classLikeType ?? ClassLikeType::classLike();
         $this->dependencies = $dependencies;
         $this->inherits = $inherits;
+        $this->isInternal = $isInternal;
     }
 
     public function withFileReference(FileReference $astFileReference): self
@@ -75,4 +78,9 @@ class ClassLikeReference implements TokenReferenceInterface
     {
         return $this->inherits;
     }
+
+    public function isInternal(): bool
+    {
+        return $this->isInternal;
+    }
 }
diff --git a/src/Ast/AstMap/ClassLike/ClassLikeReferenceBuilder.php b/src/Ast/AstMap/ClassLike/ClassLikeReferenceBuilder.php
index 5fd7938f..65a353d2 100644
--- a/src/Ast/AstMap/ClassLike/ClassLikeReferenceBuilder.php
+++ b/src/Ast/AstMap/ClassLike/ClassLikeReferenceBuilder.php
@@ -16,47 +16,50 @@ final class ClassLikeReferenceBuilder extends ReferenceBuilder
     /** @var AstInherit[] */
     private array $inherits = [];
 
+    private bool $isInternal;
+
     /**
      * @param string[] $tokenTemplates
      */
-    protected function __construct(array $tokenTemplates, string $filepath, ClassLikeToken $classLikeToken, ClassLikeType $classLikeType)
+    protected function __construct(array $tokenTemplates, string $filepath, ClassLikeToken $classLikeToken, ClassLikeType $classLikeType, bool $isInternal)
     {
         parent::__construct($tokenTemplates, $filepath);
 
         $this->classLikeToken = $classLikeToken;
         $this->classLikeType = $classLikeType;
+        $this->isInternal = $isInternal;
     }
 
     /**
      * @param string[] $classTemplates
      */
-    public static function createClassLike(string $filepath, string $classLikeName, array $classTemplates): self
+    public static function createClassLike(string $filepath, string $classLikeName, array $classTemplates, bool $isInternal): self
     {
-        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::classLike());
+        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::classLike(), $isInternal);
     }
 
     /**
      * @param string[] $classTemplates
      */
-    public static function createClass(string $filepath, string $classLikeName, array $classTemplates): self
+    public static function createClass(string $filepath, string $classLikeName, array $classTemplates, bool $isInternal): self
     {
-        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::class());
+        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::class(), $isInternal);
     }
 
     /**
      * @param string[] $classTemplates
      */
-    public static function createTrait(string $filepath, string $classLikeName, array $classTemplates): self
+    public static function createTrait(string $filepath, string $classLikeName, array $classTemplates, bool $isInternal): self
     {
-        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::trait());
+        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::trait(), $isInternal);
     }
 
     /**
      * @param string[] $classTemplates
      */
-    public static function createInterface(string $filepath, string $classLikeName, array $classTemplates): self
+    public static function createInterface(string $filepath, string $classLikeName, array $classTemplates, bool $isInternal): self
     {
-        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::interface());
+        return new self($classTemplates, $filepath, ClassLikeToken::fromFQCN($classLikeName), ClassLikeType::interface(), $isInternal);
     }
 
     /** @internal */
@@ -66,7 +69,8 @@ final class ClassLikeReferenceBuilder extends ReferenceBuilder
             $this->classLikeToken,
             $this->classLikeType,
             $this->inherits,
-            $this->dependencies
+            $this->dependencies,
+            $this->isInternal
         );
     }
 
@@ -99,4 +103,9 @@ final class ClassLikeReferenceBuilder extends ReferenceBuilder
 
         return $this;
     }
+
+    public function isInternal(): bool
+    {
+        return $this->isInternal;
+    }
 }
diff --git a/src/Ast/AstMap/File/FileReferenceBuilder.php b/src/Ast/AstMap/File/FileReferenceBuilder.php
index 7a19750c..f6195a49 100644
--- a/src/Ast/AstMap/File/FileReferenceBuilder.php
+++ b/src/Ast/AstMap/File/FileReferenceBuilder.php
@@ -38,9 +38,9 @@ final class FileReferenceBuilder extends ReferenceBuilder
     /**
      * @param string[] $templateTypes
      */
-    public function newClass(string $classLikeName, array $templateTypes = []): ClassLikeReferenceBuilder
+    public function newClass(string $classLikeName, array $templateTypes, bool $isInternal): ClassLikeReferenceBuilder
     {
-        $classReference = ClassLikeReferenceBuilder::createClass($this->filepath, $classLikeName, $templateTypes);
+        $classReference = ClassLikeReferenceBuilder::createClass($this->filepath, $classLikeName, $templateTypes, $isInternal);
         $this->classReferences[] = $classReference;
 
         return $classReference;
@@ -49,9 +49,9 @@ final class FileReferenceBuilder extends ReferenceBuilder
     /**
      * @param string[] $templateTypes
      */
-    public function newTrait(string $classLikeName, array $templateTypes = []): ClassLikeReferenceBuilder
+    public function newTrait(string $classLikeName, array $templateTypes, bool $isInternal): ClassLikeReferenceBuilder
     {
-        $classReference = ClassLikeReferenceBuilder::createTrait($this->filepath, $classLikeName, $templateTypes);
+        $classReference = ClassLikeReferenceBuilder::createTrait($this->filepath, $classLikeName, $templateTypes, $isInternal);
         $this->classReferences[] = $classReference;
 
         return $classReference;
@@ -60,9 +60,9 @@ final class FileReferenceBuilder extends ReferenceBuilder
     /**
      * @param string[] $templateTypes
      */
-    public function newClassLike(string $classLikeName, array $templateTypes = []): ClassLikeReferenceBuilder
+    public function newClassLike(string $classLikeName, array $templateTypes, bool $isInternal): ClassLikeReferenceBuilder
     {
-        $classReference = ClassLikeReferenceBuilder::createClassLike($this->filepath, $classLikeName, $templateTypes);
+        $classReference = ClassLikeReferenceBuilder::createClassLike($this->filepath, $classLikeName, $templateTypes, $isInternal);
         $this->classReferences[] = $classReference;
 
         return $classReference;
@@ -71,9 +71,9 @@ final class FileReferenceBuilder extends ReferenceBuilder
     /**
      * @param string[] $templateTypes
      */
-    public function newInterface(string $classLikeName, array $templateTypes = []): ClassLikeReferenceBuilder
+    public function newInterface(string $classLikeName, array $templateTypes, bool $isInternal): ClassLikeReferenceBuilder
     {
-        $classReference = ClassLikeReferenceBuilder::createInterface($this->filepath, $classLikeName, $templateTypes);
+        $classReference = ClassLikeReferenceBuilder::createInterface($this->filepath, $classLikeName, $templateTypes, $isInternal);
         $this->classReferences[] = $classReference;
 
         return $classReference;
diff --git a/src/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php b/src/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php
index c4b0f84d..04d357c4 100644
--- a/src/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php
+++ b/src/Ast/Parser/NikicPhpParser/FileReferenceVisitor.php
@@ -246,8 +246,16 @@ class FileReferenceVisitor extends NodeVisitorAbstract
 
     private function enterClassLike(string $name, ClassLike $node): void
     {
+        $isInternal = false;
+        $docComment = $node->getDocComment();
+        if (null !== $docComment) {
+            $tokens = new TokenIterator($this->lexer->tokenize($docComment->getText()));
+            $docNode = $this->docParser->parse($tokens);
+            $isInternal = [] !== array_merge($docNode->getTagsByName('@internal'), $docNode->getTagsByName('@deptrac-internal'));
+        }
+
         if ($node instanceof Interface_) {
-            $this->currentReference = $this->fileReferenceBuilder->newInterface($name, $this->templatesFromDocs($node));
+            $this->currentReference = $this->fileReferenceBuilder->newInterface($name, $this->templatesFromDocs($node), $isInternal);
 
             foreach ($node->extends as $extend) {
                 $this->currentReference->implements($extend->toCodeString(), $extend->getLine());
@@ -257,7 +265,7 @@ class FileReferenceVisitor extends NodeVisitorAbstract
         }
 
         if ($node instanceof Class_) {
-            $this->currentReference = $this->fileReferenceBuilder->newClass($name, $this->templatesFromDocs($node));
+            $this->currentReference = $this->fileReferenceBuilder->newClass($name, $this->templatesFromDocs($node), $isInternal);
             if ($node->extends instanceof Name) {
                 $this->currentReference->extends($node->extends->toCodeString(), $node->extends->getLine());
             }
@@ -270,12 +278,12 @@ class FileReferenceVisitor extends NodeVisitorAbstract
         }
 
         if ($node instanceof Trait_) {
-            $this->currentReference = $this->fileReferenceBuilder->newTrait($name, $this->templatesFromDocs($node));
+            $this->currentReference = $this->fileReferenceBuilder->newTrait($name, $this->templatesFromDocs($node), $isInternal);
 
             return;
         }
 
-        $this->currentReference = $this->fileReferenceBuilder->newClassLike($name, $this->templatesFromDocs($node));
+        $this->currentReference = $this->fileReferenceBuilder->newClassLike($name, $this->templatesFromDocs($node), $isInternal);
     }
 
     private function enterFunction(string $name, Node\Stmt\Function_ $node): void
diff --git a/src/File/Exception/CouldNotReadFileException.php b/src/File/Exception/CouldNotReadFileException.php
index 15c45fd2..8c129d84 100644
--- a/src/File/Exception/CouldNotReadFileException.php
+++ b/src/File/Exception/CouldNotReadFileException.php
@@ -7,6 +7,9 @@ namespace Qossmic\Deptrac\File\Exception;
 use Qossmic\Deptrac\Exception\ExceptionInterface;
 use RuntimeException;
 
+/**
+ * @internal
+ */
 final class CouldNotReadFileException extends RuntimeException implements ExceptionInterface
 {
     public static function fromFilename(string $filename): self
diff --git a/src/File/Exception/FileCannotBeParsedAsYamlException.php b/src/File/Exception/FileCannotBeParsedAsYamlException.php
index 61c44420..b9b5b758 100644
--- a/src/File/Exception/FileCannotBeParsedAsYamlException.php
+++ b/src/File/Exception/FileCannotBeParsedAsYamlException.php
@@ -8,6 +8,9 @@ use Qossmic\Deptrac\Exception\ExceptionInterface;
 use RuntimeException;
 use Symfony\Component\Yaml\Exception\ParseException;
 
+/**
+ * @internal
+ */
 final class FileCannotBeParsedAsYamlException extends RuntimeException implements ExceptionInterface
 {
     public static function fromFilenameAndException(string $filename, ParseException $exception): self
diff --git a/src/File/Exception/ParsedYamlIsNotAnArrayException.php b/src/File/Exception/ParsedYamlIsNotAnArrayException.php
index 385c77fc..e9408323 100644
--- a/src/File/Exception/ParsedYamlIsNotAnArrayException.php
+++ b/src/File/Exception/ParsedYamlIsNotAnArrayException.php
@@ -7,6 +7,9 @@ namespace Qossmic\Deptrac\File\Exception;
 use Qossmic\Deptrac\Exception\ExceptionInterface;
 use RuntimeException;
 
+/**
+ * @internal
+ */
 final class ParsedYamlIsNotAnArrayException extends RuntimeException implements ExceptionInterface
 {
     public static function fromFilename(string $filename): self
diff --git a/src/File/YmlFileLoader.php b/src/File/YmlFileLoader.php
index f6dbb77e..edde3cf7 100644
--- a/src/File/YmlFileLoader.php
+++ b/src/File/YmlFileLoader.php
@@ -7,6 +7,9 @@ use Qossmic\Deptrac\File\Exception\ParsedYamlIsNotAnArrayException;
 use Symfony\Component\Yaml\Exception\ParseException;
 use Symfony\Component\Yaml\Yaml;
 
+/**
+ * @internal
+ */
 class YmlFileLoader
 {
     /**
diff --git a/src/InputCollector/FileInputCollector.php b/src/InputCollector/FileInputCollector.php
index b2048cb3..473c4cc5 100644
--- a/src/InputCollector/FileInputCollector.php
+++ b/src/InputCollector/FileInputCollector.php
@@ -10,6 +10,9 @@ use SplFileInfo;
 use Symfony\Component\Filesystem\Path;
 use Symfony\Component\Finder\Finder;
 
+/**
+ * @internal
+ */
 final class FileInputCollector implements InputCollectorInterface
 {
     /**
diff --git a/src/OutputFormatter/BaselineOutputFormatter.php b/src/OutputFormatter/BaselineOutputFormatter.php
index c79ea221..ab5a2ee4 100644
--- a/src/OutputFormatter/BaselineOutputFormatter.php
+++ b/src/OutputFormatter/BaselineOutputFormatter.php
@@ -14,6 +14,9 @@ use function array_values;
 use function ksort;
 use function sort;
 
+/**
+ * @internal
+ */
 final class BaselineOutputFormatter implements OutputFormatterInterface
 {
     private const DEFAULT_PATH = './deptrac.baseline.yaml';
diff --git a/src/OutputFormatter/CodeclimateOutputFormatter.php b/src/OutputFormatter/CodeclimateOutputFormatter.php
index 1eab821b..183f393d 100644
--- a/src/OutputFormatter/CodeclimateOutputFormatter.php
+++ b/src/OutputFormatter/CodeclimateOutputFormatter.php
@@ -19,6 +19,9 @@ use function json_last_error;
 use function sprintf;
 use const JSON_PRETTY_PRINT;
 
+/**
+ * @internal
+ */
 final class CodeclimateOutputFormatter implements OutputFormatterInterface
 {
     /**
diff --git a/src/OutputFormatter/ConsoleOutputFormatter.php b/src/OutputFormatter/ConsoleOutputFormatter.php
index 9490ff08..efea51dd 100644
--- a/src/OutputFormatter/ConsoleOutputFormatter.php
+++ b/src/OutputFormatter/ConsoleOutputFormatter.php
@@ -14,6 +14,9 @@ use Qossmic\Deptrac\Result\SkippedViolation;
 use Qossmic\Deptrac\Result\Violation;
 use function count;
 
+/**
+ * @internal
+ */
 final class ConsoleOutputFormatter implements OutputFormatterInterface
 {
     public static function getName(): string
diff --git a/src/OutputFormatter/GraphVizOutputDisplayFormatter.php b/src/OutputFormatter/GraphVizOutputDisplayFormatter.php
index 605f87e0..98676ca2 100644
--- a/src/OutputFormatter/GraphVizOutputDisplayFormatter.php
+++ b/src/OutputFormatter/GraphVizOutputDisplayFormatter.php
@@ -10,6 +10,9 @@ use phpDocumentor\GraphViz\Graph;
 use Qossmic\Deptrac\Configuration\OutputFormatterInput;
 use Qossmic\Deptrac\Console\Output;
 
+/**
+ * @internal
+ */
 final class GraphVizOutputDisplayFormatter extends GraphVizOutputFormatter
 {
     /** @var positive-int */
diff --git a/src/OutputFormatter/GraphVizOutputDotFormatter.php b/src/OutputFormatter/GraphVizOutputDotFormatter.php
index 5a1a7d7d..ad5c0545 100644
--- a/src/OutputFormatter/GraphVizOutputDotFormatter.php
+++ b/src/OutputFormatter/GraphVizOutputDotFormatter.php
@@ -9,6 +9,9 @@ use phpDocumentor\GraphViz\Graph;
 use Qossmic\Deptrac\Configuration\OutputFormatterInput;
 use Qossmic\Deptrac\Console\Output;
 
+/**
+ * @internal
+ */
 final class GraphVizOutputDotFormatter extends GraphVizOutputFormatter
 {
     public static function getName(): string
diff --git a/src/OutputFormatter/GraphVizOutputHtmlFormatter.php b/src/OutputFormatter/GraphVizOutputHtmlFormatter.php
index ce8190b5..d97be373 100644
--- a/src/OutputFormatter/GraphVizOutputHtmlFormatter.php
+++ b/src/OutputFormatter/GraphVizOutputHtmlFormatter.php
@@ -13,6 +13,9 @@ use RuntimeException;
 use function base64_encode;
 use function file_get_contents;
 
+/**
+ * @internal
+ */
 final class GraphVizOutputHtmlFormatter extends GraphVizOutputFormatter
 {
     public static function getName(): string
diff --git a/src/OutputFormatter/GraphVizOutputImageFormatter.php b/src/OutputFormatter/GraphVizOutputImageFormatter.php
index 2f64236f..a72c1767 100644
--- a/src/OutputFormatter/GraphVizOutputImageFormatter.php
+++ b/src/OutputFormatter/GraphVizOutputImageFormatter.php
@@ -12,6 +12,9 @@ use Qossmic\Deptrac\Console\Output;
 use SplFileInfo;
 use Symfony\Component\Filesystem\Path;
 
+/**
+ * @internal
+ */
 final class GraphVizOutputImageFormatter extends GraphVizOutputFormatter
 {
     public static function getName(): string
diff --git a/src/OutputFormatter/JUnitOutputFormatter.php b/src/OutputFormatter/JUnitOutputFormatter.php
index 11965bff..d22722bc 100644
--- a/src/OutputFormatter/JUnitOutputFormatter.php
+++ b/src/OutputFormatter/JUnitOutputFormatter.php
@@ -17,6 +17,9 @@ use Qossmic\Deptrac\Result\SkippedViolation;
 use Qossmic\Deptrac\Result\Uncovered;
 use Qossmic\Deptrac\Result\Violation;
 
+/**
+ * @internal
+ */
 final class JUnitOutputFormatter implements OutputFormatterInterface
 {
     private const DEFAULT_PATH = './junit-report.xml';
diff --git a/src/OutputFormatter/JsonOutputFormatter.php b/src/OutputFormatter/JsonOutputFormatter.php
index 23abed17..0d51f8b4 100644
--- a/src/OutputFormatter/JsonOutputFormatter.php
+++ b/src/OutputFormatter/JsonOutputFormatter.php
@@ -16,6 +16,9 @@ use function json_last_error;
 use function sprintf;
 use const JSON_PRETTY_PRINT;
 
+/**
+ * @internal
+ */
 final class JsonOutputFormatter implements OutputFormatterInterface
 {
     public static function getName(): string
diff --git a/src/OutputFormatter/XMLOutputFormatter.php b/src/OutputFormatter/XMLOutputFormatter.php
index 6c21a987..56c90d09 100644
--- a/src/OutputFormatter/XMLOutputFormatter.php
+++ b/src/OutputFormatter/XMLOutputFormatter.php
@@ -14,6 +14,9 @@ use Qossmic\Deptrac\Result\LegacyResult;
 use Qossmic\Deptrac\Result\SkippedViolation;
 use Qossmic\Deptrac\Result\Violation;
 
+/**
+ * @internal
+ */
 final class XMLOutputFormatter implements OutputFormatterInterface
 {
     private const DEFAULT_PATH = './deptrac-report.xml';


Test patch:
diff --git a/tests/Layer/Collector/AttributeCollectorTest.php b/tests/Layer/Collector/AttributeCollectorTest.php
index 1f393a0b..28a3dfd9 100644
--- a/tests/Layer/Collector/AttributeCollectorTest.php
+++ b/tests/Layer/Collector/AttributeCollectorTest.php
@@ -42,7 +42,7 @@ final class AttributeCollectorTest extends TestCase
     public function testSatisfy(array $config, bool $expected): void
     {
         $classLikeReference = FileReferenceBuilder::create('Foo.php')
-            ->newClass('App\Foo')
+            ->newClass('App\Foo', [], false)
             ->attribute('App\MyAttribute', 2)
             ->attribute('MyAttribute', 3)
             ->build();
diff --git a/tests/Layer/Collector/DirectoryCollectorTest.php b/tests/Layer/Collector/DirectoryCollectorTest.php
index 8dd92dd4..1316df93 100644
--- a/tests/Layer/Collector/DirectoryCollectorTest.php
+++ b/tests/Layer/Collector/DirectoryCollectorTest.php
@@ -40,7 +40,7 @@ final class DirectoryCollectorTest extends TestCase
     public function testSatisfy(array $configuration, string $filePath, bool $expected): void
     {
         $fileReferenceBuilder = FileReferenceBuilder::create($filePath);
-        $fileReferenceBuilder->newClassLike('Test');
+        $fileReferenceBuilder->newClassLike('Test', [], false);
         $fileReference = $fileReferenceBuilder->build();
 
         $actual = $this->collector->satisfy(
@@ -55,7 +55,7 @@ final class DirectoryCollectorTest extends TestCase
     public function testMissingRegexThrowsException(): void
     {
         $fileReferenceBuilder = FileReferenceBuilder::create('/some/path/to/file.php');
-        $fileReferenceBuilder->newClassLike('Test');
+        $fileReferenceBuilder->newClassLike('Test', [], false);
         $fileReference = $fileReferenceBuilder->build();
 
         $this->expectException(LogicException::class);
@@ -71,7 +71,7 @@ final class DirectoryCollectorTest extends TestCase
     public function testInvalidRegexParam(): void
     {
         $fileReferenceBuilder = FileReferenceBuilder::create('/some/path/to/file.php');
-        $fileReferenceBuilder->newClassLike('Test');
+        $fileReferenceBuilder->newClassLike('Test', [], false);
         $fileReference = $fileReferenceBuilder->build();
 
         $this->expectException(LogicException::class);
diff --git a/tests/Layer/Collector/ExtendsCollectorTest.php b/tests/Layer/Collector/ExtendsCollectorTest.php
index c90ad2e4..0ede8c5d 100644
--- a/tests/Layer/Collector/ExtendsCollectorTest.php
+++ b/tests/Layer/Collector/ExtendsCollectorTest.php
@@ -42,28 +42,28 @@ final class ExtendsCollectorTest extends TestCase
     {
         $fooFileReferenceBuilder = FileReferenceBuilder::create('foo.php');
         $fooFileReferenceBuilder
-            ->newClassLike('App\Foo')
+            ->newClassLike('App\Foo', [], false)
             ->implements('App\Bar', 2);
         $fooFileReference = $fooFileReferenceBuilder->build();
 
         $barFileReferenceBuilder = FileReferenceBuilder::create('bar.php');
         $barFileReferenceBuilder
-            ->newClassLike('App\Bar')
+            ->newClassLike('App\Bar', [], false)
             ->implements('App\Baz', 2);
         $barFileReference = $barFileReferenceBuilder->build();
 
         $bazFileReferenceBuilder = FileReferenceBuilder::create('baz.php');
-        $bazFileReferenceBuilder->newClassLike('App\Baz');
+        $bazFileReferenceBuilder->newClassLike('App\Baz', [], false);
         $bazFileReference = $bazFileReferenceBuilder->build();
 
         $fizTraitFileReferenceBuilder = FileReferenceBuilder::create('fiztrait.php');
         $fizTraitFileReferenceBuilder
-            ->newClassLike('App\FizTrait');
+            ->newClassLike('App\FizTrait', [], false);
         $fizTraitFileReference = $fizTraitFileReferenceBuilder->build();
 
         $fooBarFileReferenceBuilder = FileReferenceBuilder::create('foobar.php');
         $fooBarFileReferenceBuilder
-            ->newClassLike('App\FooBar')
+            ->newClassLike('App\FooBar', [], false)
             ->extends('App\Foo', 2)
             ->trait('App\FizTrait', 4);
         $fooBarFileReference = $fooBarFileReferenceBuilder->build();
diff --git a/tests/Layer/Collector/GlobCollectorTest.php b/tests/Layer/Collector/GlobCollectorTest.php
index 9bfcdebd..4e58a760 100644
--- a/tests/Layer/Collector/GlobCollectorTest.php
+++ b/tests/Layer/Collector/GlobCollectorTest.php
@@ -35,7 +35,7 @@ final class GlobCollectorTest extends TestCase
     public function testSatisfy(array $configuration, string $filePath, bool $expected): void
     {
         $fileReferenceBuilder = FileReferenceBuilder::create($filePath);
-        $fileReferenceBuilder->newClassLike('Test');
+        $fileReferenceBuilder->newClassLike('Test', [], false);
         $fileReference = $fileReferenceBuilder->build();
 
         $actual = $this->collector->satisfy(
diff --git a/tests/Layer/Collector/ImplementsCollectorTest.php b/tests/Layer/Collector/ImplementsCollectorTest.php
index be7a08c4..bcc10f8c 100644
--- a/tests/Layer/Collector/ImplementsCollectorTest.php
+++ b/tests/Layer/Collector/ImplementsCollectorTest.php
@@ -42,28 +42,28 @@ final class ImplementsCollectorTest extends TestCase
     {
         $fooFileReferenceBuilder = FileReferenceBuilder::create('foo.php');
         $fooFileReferenceBuilder
-            ->newClassLike('App\Foo')
+            ->newClassLike('App\Foo', [], false)
             ->implements('App\Bar', 2);
         $fooFileReference = $fooFileReferenceBuilder->build();
 
         $barFileReferenceBuilder = FileReferenceBuilder::create('bar.php');
         $barFileReferenceBuilder
-            ->newClassLike('App\Bar')
+            ->newClassLike('App\Bar', [], false)
             ->implements('App\Baz', 2);
         $barFileReference = $barFileReferenceBuilder->build();
 
         $bazFileReferenceBuilder = FileReferenceBuilder::create('baz.php');
-        $bazFileReferenceBuilder->newClassLike('App\Baz');
+        $bazFileReferenceBuilder->newClassLike('App\Baz', [], false);
         $bazFileReference = $bazFileReferenceBuilder->build();
 
         $fizTraitFileReferenceBuilder = FileReferenceBuilder::create('fiztrait.php');
         $fizTraitFileReferenceBuilder
-            ->newClassLike('App\FizTrait');
+            ->newClassLike('App\FizTrait', [], false);
         $fizTraitFileReference = $fizTraitFileReferenceBuilder->build();
 
         $fooBarFileReferenceBuilder = FileReferenceBuilder::create('foobar.php');
         $fooBarFileReferenceBuilder
-            ->newClassLike('App\FooBar')
+            ->newClassLike('App\FooBar', [], false)
             ->extends('App\Foo', 2)
             ->trait('App\FizTrait', 4);
         $fooBarFileReference = $fooBarFileReferenceBuilder->build();
diff --git a/tests/Layer/Collector/InheritsCollectorTest.php b/tests/Layer/Collector/InheritsCollectorTest.php
index 35372503..800edb94 100644
--- a/tests/Layer/Collector/InheritsCollectorTest.php
+++ b/tests/Layer/Collector/InheritsCollectorTest.php
@@ -42,28 +42,28 @@ final class InheritsCollectorTest extends TestCase
     {
         $fooFileReferenceBuilder = FileReferenceBuilder::create('foo.php');
         $fooFileReferenceBuilder
-            ->newClassLike('App\Foo')
+            ->newClassLike('App\Foo', [], false)
             ->implements('App\Bar', 2);
         $fooFileReference = $fooFileReferenceBuilder->build();
 
         $barFileReferenceBuilder = FileReferenceBuilder::create('bar.php');
         $barFileReferenceBuilder
-            ->newClassLike('App\Bar')
+            ->newClassLike('App\Bar', [], false)
             ->implements('App\Baz', 2);
         $barFileReference = $barFileReferenceBuilder->build();
 
         $bazFileReferenceBuilder = FileReferenceBuilder::create('baz.php');
-        $bazFileReferenceBuilder->newClassLike('App\Baz');
+        $bazFileReferenceBuilder->newClassLike('App\Baz', [], false);
         $bazFileReference = $bazFileReferenceBuilder->build();
 
         $fizTraitFileReferenceBuilder = FileReferenceBuilder::create('fiztrait.php');
         $fizTraitFileReferenceBuilder
-            ->newClassLike('App\FizTrait');
+            ->newClassLike('App\FizTrait', [], false);
         $fizTraitFileReference = $fizTraitFileReferenceBuilder->build();
 
         $fooBarFileReferenceBuilder = FileReferenceBuilder::create('foobar.php');
         $fooBarFileReferenceBuilder
-            ->newClassLike('App\FooBar')
+            ->newClassLike('App\FooBar', [], false)
             ->extends('App\Foo', 2)
             ->trait('App\FizTrait', 4);
         $fooBarFileReference = $fooBarFileReferenceBuilder->build();
diff --git a/tests/Layer/Collector/UsesCollectorTest.php b/tests/Layer/Collector/UsesCollectorTest.php
index 58ed3742..a6269121 100644
--- a/tests/Layer/Collector/UsesCollectorTest.php
+++ b/tests/Layer/Collector/UsesCollectorTest.php
@@ -36,28 +36,28 @@ final class UsesCollectorTest extends TestCase
     {
         $fooFileReferenceBuilder = FileReferenceBuilder::create('foo.php');
         $fooFileReferenceBuilder
-            ->newClassLike('App\Foo')
+            ->newClassLike('App\Foo', [], false)
             ->implements('App\Bar', 2);
         $fooFileReference = $fooFileReferenceBuilder->build();
 
         $barFileReferenceBuilder = FileReferenceBuilder::create('bar.php');
         $barFileReferenceBuilder
-            ->newClassLike('App\Bar')
+            ->newClassLike('App\Bar', [], false)
             ->implements('App\Baz', 2);
         $barFileReference = $barFileReferenceBuilder->build();
 
         $bazFileReferenceBuilder = FileReferenceBuilder::create('baz.php');
-        $bazFileReferenceBuilder->newClassLike('App\Baz');
+        $bazFileReferenceBuilder->newClassLike('App\Baz', [], false);
         $bazFileReference = $bazFileReferenceBuilder->build();
 
         $fizTraitFileReferenceBuilder = FileReferenceBuilder::create('fiztrait.php');
         $fizTraitFileReferenceBuilder
-            ->newClassLike('App\FizTrait');
+            ->newClassLike('App\FizTrait', [], false);
         $fizTraitFileReference = $fizTraitFileReferenceBuilder->build();
 
         $fooBarFileReferenceBuilder = FileReferenceBuilder::create('foobar.php');
         $fooBarFileReferenceBuilder
-            ->newClassLike('App\FooBar')
+            ->newClassLike('App\FooBar', [], false)
             ->extends('App\Foo', 2)
             ->trait('App\FizTrait', 4);
         $fooBarFileReference = $fooBarFileReferenceBuilder->build();