diff --git a/docs/format.md b/docs/format.md index dccf4ef..54f85f6 100644 --- a/docs/format.md +++ b/docs/format.md @@ -10,10 +10,16 @@ currentMenu: format * Static methods and functions are shown underlined * Abstract classes, abstract methods and interfaces names are shown in *italics* -## Traits and Attributes +## Traits, Attributes, and Enums -[Traits](https://www.php.net/manual/en/language.oop5.traits.php) and class [attributes](https://www.php.net/manual/en/language.attributes.overview.php)(annotations) will be shown with a [UML stereotype](https://www.uml-diagrams.org/stereotype.html). -Traits will be shown with the `<>` stereotype above its name, and attributes (annotations) will be shown with the `<>` stereotype above its name. +[Traits](https://www.php.net/manual/en/language.oop5.traits.php), class [attributes](https://www.php.net/manual/en/language.attributes.overview.php)(annotations), and [enumerations](https://www.php.net/manual/en/language.enumerations.php) will be shown with a [UML stereotype](https://www.uml-diagrams.org/stereotype.html) above its name. +Traits will be shown with the `<>` stereotype, attributes (annotations) will be shown with the `<>` stereotype, and enumerations will be shown with the `<>` stereotype. + +## Enumerations + +Case definitions in enumerations will be shown without a visibility modifier (`+`, `-`, `#`) and to differentiate them from constants, cases won't be in italics. + +If an enum defines constants, they will be shown above its cases. ## Relationships diff --git a/src/Code/EnumDefinition.php b/src/Code/EnumDefinition.php new file mode 100644 index 0000000..917c6be --- /dev/null +++ b/src/Code/EnumDefinition.php @@ -0,0 +1,51 @@ +constants = $constants; + $this->traits = $traits; + $this->interfaces = $interfaces; + } + + public function hasProperties(): bool + { + return $this->cases !== [] || $this->constants !== []; + } + + /** @return EnumCase[] */ + public function cases(): array + { + return $this->cases; + } +} diff --git a/src/Code/Properties/EnumCase.php b/src/Code/Properties/EnumCase.php new file mode 100644 index 0000000..176ef93 --- /dev/null +++ b/src/Code/Properties/EnumCase.php @@ -0,0 +1,20 @@ +name; + } +} diff --git a/src/Code/WithInterfaces.php b/src/Code/WithInterfaces.php index 2c1854f..2ddcb24 100644 --- a/src/Code/WithInterfaces.php +++ b/src/Code/WithInterfaces.php @@ -8,7 +8,7 @@ trait WithInterfaces { /** @var Name[] */ - private array $interfaces; + private readonly array $interfaces; /** @return Name[] */ public function interfaces(): array diff --git a/src/Graphviz/Builders/DirectedEdgesBuilder.php b/src/Graphviz/Builders/DirectedEdgesBuilder.php index cf5e7c9..8cc7a3f 100644 --- a/src/Graphviz/Builders/DirectedEdgesBuilder.php +++ b/src/Graphviz/Builders/DirectedEdgesBuilder.php @@ -78,7 +78,7 @@ private function addAssociations(ClassDefinition $class, HasType $property, Code private function isAssociationResolved(EdgeKey $key): bool { - return array_key_exists((string) $key, $this->edges) && $this->edges[(string) $key] === true; + return array_key_exists((string) $key, $this->edges) && $this->edges[(string) $key]; } private function markAssociationResolvedFor(EdgeKey $key): void diff --git a/src/Graphviz/Builders/EdgeKey.php b/src/Graphviz/Builders/EdgeKey.php index 38829ec..ca80514 100644 --- a/src/Graphviz/Builders/EdgeKey.php +++ b/src/Graphviz/Builders/EdgeKey.php @@ -11,7 +11,7 @@ final class EdgeKey implements Stringable { - private string $key; + private readonly string $key; public static function from(Name $name, TypeDeclaration $type): EdgeKey { @@ -23,11 +23,6 @@ private function __construct(string $key) $this->key = $key; } - public function equals(EdgeKey $anotherKey): bool - { - return $this->key === $anotherKey->key; - } - public function __toString(): string { return $this->key; diff --git a/src/Graphviz/Builders/EnumGraphBuilder.php b/src/Graphviz/Builders/EnumGraphBuilder.php new file mode 100644 index 0000000..4c9e4c7 --- /dev/null +++ b/src/Graphviz/Builders/EnumGraphBuilder.php @@ -0,0 +1,31 @@ +interfaces() as $interface) { + $dotElements[] = Edge::implementation($codebase->get($interface), $enum); + } + + foreach ($enum->traits() as $usedTrait) { + $dotElements[] = Edge::use($codebase->get($usedTrait), $enum); + } + + return $dotElements; + } +} diff --git a/src/Graphviz/Node.php b/src/Graphviz/Node.php index 137c203..8ee3502 100644 --- a/src/Graphviz/Node.php +++ b/src/Graphviz/Node.php @@ -8,6 +8,7 @@ use PhUml\Code\ClassDefinition; use PhUml\Code\Definition; use PhUml\Code\InterfaceDefinition; +use PhUml\Code\TraitDefinition; /** * `ClassDefinition`, `InterfaceDefinition` and `TraitDefinition` objects can be nodes @@ -32,12 +33,11 @@ public function dotTemplate(): string public function labelTemplate(): string { - if ($this->definition instanceof ClassDefinition) { - return 'class'; - } - if ($this->definition instanceof InterfaceDefinition) { - return 'interface'; - } - return 'trait'; + return match ($this->definition::class) { + ClassDefinition::class => 'class', + InterfaceDefinition::class => 'interface', + TraitDefinition::class => 'trait', + default => 'enum', + }; } } diff --git a/src/Parser/Code/Builders/EnumDefinitionBuilder.php b/src/Parser/Code/Builders/EnumDefinitionBuilder.php new file mode 100644 index 0000000..755fd8d --- /dev/null +++ b/src/Parser/Code/Builders/EnumDefinitionBuilder.php @@ -0,0 +1,38 @@ +useStatementsBuilder->build($enum); + + return new EnumDefinition( + new Name((string) $enum->namespacedName), + $this->membersBuilder->enumCases($enum->stmts), + $this->membersBuilder->methods($enum->getMethods(), $useStatements), + $this->membersBuilder->constants($enum->stmts), + $this->buildInterfaces($enum->implements), + $this->buildTraits($enum->stmts), + ); + } +} diff --git a/src/Parser/Code/Builders/Members/TypeBuilder.php b/src/Parser/Code/Builders/Members/TypeBuilder.php index a3b18f7..a0c62e6 100644 --- a/src/Parser/Code/Builders/Members/TypeBuilder.php +++ b/src/Parser/Code/Builders/Members/TypeBuilder.php @@ -54,23 +54,14 @@ private function fromParsedType(Identifier|Name|ComplexType|null $type): TypeDec $type instanceof Name, $type instanceof Identifier => TypeDeclaration::from((string) $type), $type === null => TypeDeclaration::absent(), $type instanceof UnionType => TypeDeclaration::fromCompositeType( - $this->fromCompositeType($type), + array_map(strval(...), $type->types), CompositeType::UNION ), $type instanceof IntersectionType => TypeDeclaration::fromCompositeType( - $this->fromCompositeType($type), + array_map(strval(...), $type->types), CompositeType::INTERSECTION ), default => throw new RuntimeException(sprintf('%s is not supported', $type::class)), }; } - - /** @return string[] */ - private function fromCompositeType(UnionType|IntersectionType $type): array - { - return array_map( - static fn (Identifier|Name $name): string => (string) $name, - $type->types - ); - } } diff --git a/src/Parser/Code/Builders/MembersBuilder.php b/src/Parser/Code/Builders/MembersBuilder.php index d04833d..8a76fc8 100644 --- a/src/Parser/Code/Builders/MembersBuilder.php +++ b/src/Parser/Code/Builders/MembersBuilder.php @@ -8,9 +8,11 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\EnumCase as ParsedEnumCase; use PhpParser\Node\Stmt\Property as ParsedProperty; use PhUml\Code\Methods\Method; use PhUml\Code\Properties\Constant; +use PhUml\Code\Properties\EnumCase; use PhUml\Code\Properties\Property; use PhUml\Code\UseStatements; use PhUml\Parser\Code\Builders\Members\ConstantsBuilder; @@ -70,6 +72,20 @@ public function properties(array $members, ?ClassMethod $constructor, UseStateme return array_merge($this->propertiesBuilder->build($filteredProperties, $useStatements), $properties); } + /** @return Property[] */ + private function fromPromotedProperties(ClassMethod $constructor, UseStatements $useStatements): array + { + $promotedProperties = array_filter( + $constructor->getParams(), + static fn (Node\Param $param) => $param->flags !== 0 + ); + + /** @var Node\Param[] $filteredPromotedProperties */ + $filteredPromotedProperties = $this->filters->apply($promotedProperties); + + return $this->propertiesBuilder->fromPromotedProperties($filteredPromotedProperties, $useStatements); + } + /** * @param ClassMethod[] $methods * @return Method[] @@ -82,17 +98,15 @@ public function methods(array $methods, UseStatements $useStatements): array return $this->methodsBuilder->build($filteredMethods, $useStatements); } - /** @return Property[] */ - private function fromPromotedProperties(ClassMethod $constructor, UseStatements $useStatements): array + /** + * @param Node[] $members + * @return EnumCase[] + */ + public function enumCases(array $members): array { - $promotedProperties = array_filter( - $constructor->getParams(), - static fn (Node\Param $param) => $param->flags !== 0 - ); - - /** @var Node\Param[] $filteredPromotedProperties */ - $filteredPromotedProperties = $this->filters->apply($promotedProperties); + /** @var ParsedEnumCase[] $cases */ + $cases = array_filter($members, static fn ($member): bool => $member instanceof ParsedEnumCase); - return $this->propertiesBuilder->fromPromotedProperties($filteredPromotedProperties, $useStatements); + return array_map(static fn (ParsedEnumCase $case) => new EnumCase((string) $case->name), $cases); } } diff --git a/src/Parser/Code/Builders/TagTypeFactory.php b/src/Parser/Code/Builders/TagTypeFactory.php index 107e47f..e5f7cfb 100644 --- a/src/Parser/Code/Builders/TagTypeFactory.php +++ b/src/Parser/Code/Builders/TagTypeFactory.php @@ -47,11 +47,14 @@ public function typeFromTag(string $comment, TagName $tagName, callable $filter private function resolveType(?Type $type): ?TagType { - return match (true) { - $type === null => null, - $type instanceof Nullable => TagType::nullable((string) $type->getActualType()), - $type instanceof Compound => TagType::union(array_map(strval(...), $type->getIterator()->getArrayCopy())), - $type instanceof Intersection => TagType::intersection(array_map(strval(...), $type->getIterator()->getArrayCopy())), + if ($type === null) { + return null; + } + + return match ($type::class) { + Nullable::class => TagType::nullable((string) $type->getActualType()), + Compound::class => TagType::union(array_map(strval(...), $type->getIterator()->getArrayCopy())), + Intersection::class => TagType::intersection(array_map(strval(...), $type->getIterator()->getArrayCopy())), default => $this->fromType($type) }; } diff --git a/src/Parser/Code/Builders/UseStatementsBuilder.php b/src/Parser/Code/Builders/UseStatementsBuilder.php index 46037e5..cc9d1f8 100644 --- a/src/Parser/Code/Builders/UseStatementsBuilder.php +++ b/src/Parser/Code/Builders/UseStatementsBuilder.php @@ -7,6 +7,7 @@ use PhpParser\Node\Name as ParsedName; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; @@ -18,7 +19,7 @@ final class UseStatementsBuilder { - public function build(Class_|Interface_|Trait_ $definition): UseStatements + public function build(Class_|Interface_|Trait_|Enum_ $definition): UseStatements { $uses = []; diff --git a/src/Parser/Code/ExternalDefinitionsResolver.php b/src/Parser/Code/ExternalDefinitionsResolver.php index db62549..7e0e8f9 100644 --- a/src/Parser/Code/ExternalDefinitionsResolver.php +++ b/src/Parser/Code/ExternalDefinitionsResolver.php @@ -7,13 +7,14 @@ use PhUml\Code\ClassDefinition; use PhUml\Code\Codebase; +use PhUml\Code\EnumDefinition; use PhUml\Code\InterfaceDefinition; use PhUml\Code\Name; use PhUml\Code\TraitDefinition; /** - * It checks the parent of a definition, the interfaces it implements, and the traits it uses - * looking for external definitions + * It looks for external definitions from the parent of a definition, the interfaces it implements, and the traits it + * uses * * An external definition is a class, trait or interface from a third party library, or a built-in class or interface */ @@ -21,11 +22,12 @@ final class ExternalDefinitionsResolver implements RelationshipsResolver { public function resolve(Codebase $codebase): void { - /** @var ClassDefinition|InterfaceDefinition|TraitDefinition $definition */ + /** @var ClassDefinition|InterfaceDefinition|TraitDefinition|EnumDefinition $definition */ foreach ($codebase->definitions() as $definition) { - match (true) { - $definition instanceof ClassDefinition => $this->resolveForClass($definition, $codebase), - $definition instanceof InterfaceDefinition => $this->resolveInterfaces($definition->parents(), $codebase), + match ($definition::class) { + ClassDefinition::class => $this->resolveForClass($definition, $codebase), + EnumDefinition::class => $this->resolveForEnum($definition, $codebase), + InterfaceDefinition::class => $this->resolveInterfaces($definition->parents(), $codebase), default => $this->resolveTraits($definition->traits(), $codebase), }; } @@ -41,6 +43,12 @@ private function resolveForClass(ClassDefinition $definition, Codebase $codebase $this->resolveExternalParentClass($definition, $codebase); } + private function resolveForEnum(EnumDefinition $definition, Codebase $codebase): void + { + $this->resolveInterfaces($definition->interfaces(), $codebase); + $this->resolveTraits($definition->traits(), $codebase); + } + /** @param Name[] $interfaces */ private function resolveInterfaces(array $interfaces, Codebase $codebase): void { diff --git a/src/Parser/Code/PhpCodeParser.php b/src/Parser/Code/PhpCodeParser.php index a78ddaa..3f45416 100644 --- a/src/Parser/Code/PhpCodeParser.php +++ b/src/Parser/Code/PhpCodeParser.php @@ -15,6 +15,7 @@ use PhUml\Code\Codebase; use PhUml\Parser\Code\Builders\AttributeAnalyzer; use PhUml\Parser\Code\Builders\ClassDefinitionBuilder; +use PhUml\Parser\Code\Builders\EnumDefinitionBuilder; use PhUml\Parser\Code\Builders\Filters\PrivateVisibilityFilter; use PhUml\Parser\Code\Builders\Filters\ProtectedVisibilityFilter; use PhUml\Parser\Code\Builders\InterfaceDefinitionBuilder; @@ -34,6 +35,7 @@ use PhUml\Parser\Code\Builders\TraitDefinitionBuilder; use PhUml\Parser\Code\Builders\UseStatementsBuilder; use PhUml\Parser\Code\Visitors\ClassVisitor; +use PhUml\Parser\Code\Visitors\EnumVisitor; use PhUml\Parser\Code\Visitors\InterfaceVisitor; use PhUml\Parser\Code\Visitors\TraitVisitor; use PhUml\Parser\CodeParserConfiguration; @@ -83,6 +85,7 @@ public static function fromConfiguration(CodeParserConfiguration $configuration) $classBuilder = new ClassDefinitionBuilder($membersBuilder, $useStatementsBuilder, new AttributeAnalyzer()); $interfaceBuilder = new InterfaceDefinitionBuilder($membersBuilder, $useStatementsBuilder); $traitBuilder = new TraitDefinitionBuilder($membersBuilder, $useStatementsBuilder); + $enumBuilder = new EnumDefinitionBuilder($membersBuilder, $useStatementsBuilder); $codebase = new Codebase(); @@ -93,6 +96,7 @@ public static function fromConfiguration(CodeParserConfiguration $configuration) $traverser->addVisitor(new ClassVisitor($classBuilder, $codebase)); $traverser->addVisitor(new InterfaceVisitor($interfaceBuilder, $codebase)); $traverser->addVisitor(new TraitVisitor($traitBuilder, $codebase)); + $traverser->addVisitor(new EnumVisitor($enumBuilder, $codebase)); $traverser = new PhpTraverser($traverser, $codebase); return new self($parser, $traverser); @@ -107,7 +111,7 @@ private function __construct( public function parse(SourceCode $sourceCode): Codebase { foreach ($sourceCode->fileContents() as $code) { - /** @var Stmt[] $nodes Since the parser is run in throw errors mode */ + /** @var Stmt[] $nodes Since the parser is configured to throw in case of errors */ $nodes = $this->parser->parse($code); $this->traverser->traverse($nodes); } diff --git a/src/Parser/Code/Visitors/ClassVisitor.php b/src/Parser/Code/Visitors/ClassVisitor.php index aa90652..ab84dd9 100644 --- a/src/Parser/Code/Visitors/ClassVisitor.php +++ b/src/Parser/Code/Visitors/ClassVisitor.php @@ -12,7 +12,7 @@ use PhUml\Parser\Code\Builders\ClassDefinitionBuilder; /** - * It extracts a `ClassDefinition` and adds it to the `Codebase` + * It builds a `ClassDefinition` and adds it to the `Codebase` */ final class ClassVisitor extends NodeVisitorAbstract { @@ -20,7 +20,8 @@ public function __construct(private readonly ClassDefinitionBuilder $builder, pr { } - public function leaveNode(Node $node) + /** @return null|int|Node|Node[] */ + public function leaveNode(Node $node): null|int|Node|array { if (! $node instanceof Class_) { return null; @@ -28,7 +29,9 @@ public function leaveNode(Node $node) if ($node->isAnonymous()) { return null; } + $this->codebase->add($this->builder->build($node)); + return null; } } diff --git a/src/Parser/Code/Visitors/EnumVisitor.php b/src/Parser/Code/Visitors/EnumVisitor.php new file mode 100644 index 0000000..dab3e9d --- /dev/null +++ b/src/Parser/Code/Visitors/EnumVisitor.php @@ -0,0 +1,31 @@ +codebase->add($this->builder->build($node)); + } + return null; + } +} diff --git a/src/Parser/Code/Visitors/InterfaceVisitor.php b/src/Parser/Code/Visitors/InterfaceVisitor.php index e0aa5b6..8a8c774 100644 --- a/src/Parser/Code/Visitors/InterfaceVisitor.php +++ b/src/Parser/Code/Visitors/InterfaceVisitor.php @@ -12,7 +12,7 @@ use PhUml\Parser\Code\Builders\InterfaceDefinitionBuilder; /** - * It extracts an `InterfaceDefinition` and adds it to the `Codebase` + * It builds an `InterfaceDefinition` and adds it to the `Codebase` */ final class InterfaceVisitor extends NodeVisitorAbstract { @@ -20,7 +20,8 @@ public function __construct(private readonly InterfaceDefinitionBuilder $builder { } - public function leaveNode(Node $node) + /** @return null|int|Node|Node[] */ + public function leaveNode(Node $node): null|int|Node|array { if ($node instanceof Interface_) { $this->codebase->add($this->builder->build($node)); diff --git a/src/Parser/Code/Visitors/TraitVisitor.php b/src/Parser/Code/Visitors/TraitVisitor.php index daa3e4d..e194b1c 100644 --- a/src/Parser/Code/Visitors/TraitVisitor.php +++ b/src/Parser/Code/Visitors/TraitVisitor.php @@ -12,7 +12,7 @@ use PhUml\Parser\Code\Builders\TraitDefinitionBuilder; /** - * It extracts an `TraitDefinition` and adds it to the `Codebase` + * It builds a `TraitDefinition` and adds it to the `Codebase` */ final class TraitVisitor extends NodeVisitorAbstract { @@ -20,7 +20,8 @@ public function __construct(private readonly TraitDefinitionBuilder $builder, pr { } - public function leaveNode(Node $node) + /** @return null|int|Node|Node[] */ + public function leaveNode(Node $node): null|int|Node|array { if ($node instanceof Trait_) { $this->codebase->add($this->builder->build($node)); diff --git a/src/Parser/CodebaseDirectory.php b/src/Parser/CodebaseDirectory.php index ac537a0..34be0f8 100644 --- a/src/Parser/CodebaseDirectory.php +++ b/src/Parser/CodebaseDirectory.php @@ -10,19 +10,19 @@ final class CodebaseDirectory { - private SplFileInfo $directory; + private readonly string $directory; public function __construct(string $path) { - $this->setDirectory($path); + $this->directory = $this->getAbsolutePath($path); } public function absolutePath(): string { - return (string) $this->directory->getRealPath(); + return $this->directory; } - private function setDirectory(string $path): void + private function getAbsolutePath(string $path): string { Assert::stringNotEmpty( $path, @@ -32,6 +32,6 @@ private function setDirectory(string $path): void if (! $directory->isDir()) { throw InvalidDirectory::notFoundAt($directory); } - $this->directory = $directory; + return $directory->getRealPath(); } } diff --git a/src/Processors/GraphvizProcessor.php b/src/Processors/GraphvizProcessor.php index 35b3d35..4333824 100644 --- a/src/Processors/GraphvizProcessor.php +++ b/src/Processors/GraphvizProcessor.php @@ -7,10 +7,11 @@ use PhUml\Code\ClassDefinition; use PhUml\Code\Codebase; -use PhUml\Code\Definition; +use PhUml\Code\EnumDefinition; use PhUml\Code\InterfaceDefinition; use PhUml\Code\TraitDefinition; use PhUml\Graphviz\Builders\ClassGraphBuilder; +use PhUml\Graphviz\Builders\EnumGraphBuilder; use PhUml\Graphviz\Builders\InterfaceGraphBuilder; use PhUml\Graphviz\Builders\TraitGraphBuilder; use PhUml\Graphviz\Digraph; @@ -18,7 +19,7 @@ use PhUml\Templates\TemplateEngine; /** - * It creates a digraph from a `Structure` and returns it as a string in DOT format + * It creates a digraph from a `Codebase` and returns it as a string in DOT format */ final class GraphvizProcessor implements Processor { @@ -31,6 +32,7 @@ public static function fromConfiguration(GraphvizConfiguration $configuration): new ClassGraphBuilder($associationsBuilder), new InterfaceGraphBuilder(), new TraitGraphBuilder(), + new EnumGraphBuilder(), new DigraphPrinter(new TemplateEngine(), $style) ); } @@ -39,6 +41,7 @@ private function __construct( private readonly ClassGraphBuilder $classBuilder, private readonly InterfaceGraphBuilder $interfaceBuilder, private readonly TraitGraphBuilder $traitBuilder, + private readonly EnumGraphBuilder $enumBuilder, private readonly DigraphPrinter $printer ) { } @@ -51,6 +54,7 @@ public function name(): string public function process(Codebase $codebase): OutputContent { $digraph = new Digraph(); + /** @var ClassDefinition|InterfaceDefinition|TraitDefinition|EnumDefinition $definition */ foreach ($codebase->definitions() as $definition) { $this->extractElements($definition, $codebase, $digraph); } @@ -58,16 +62,15 @@ public function process(Codebase $codebase): OutputContent } private function extractElements( - Definition $definition, + ClassDefinition|InterfaceDefinition|TraitDefinition|EnumDefinition $definition, Codebase $codebase, Digraph $digraph ): void { - if ($definition instanceof ClassDefinition) { - $digraph->add($this->classBuilder->extractFrom($definition, $codebase)); - } elseif ($definition instanceof InterfaceDefinition) { - $digraph->add($this->interfaceBuilder->extractFrom($definition, $codebase)); - } elseif ($definition instanceof TraitDefinition) { - $digraph->add($this->traitBuilder->extractFrom($definition, $codebase)); - } + match ($definition::class) { + ClassDefinition::class => $digraph->add($this->classBuilder->extractFrom($definition, $codebase)), + InterfaceDefinition::class => $digraph->add($this->interfaceBuilder->extractFrom($definition, $codebase)), + TraitDefinition::class => $digraph->add($this->traitBuilder->extractFrom($definition, $codebase)), + default => $digraph->add($this->enumBuilder->extractFrom($definition, $codebase)), + }; } } diff --git a/src/resources/templates/partials/_case.html.twig b/src/resources/templates/partials/_case.html.twig new file mode 100644 index 0000000..b87f888 --- /dev/null +++ b/src/resources/templates/partials/_case.html.twig @@ -0,0 +1,4 @@ + + {{ case }} + +
diff --git a/src/resources/templates/partials/_empty-properties.html.twig b/src/resources/templates/partials/_empty-properties.html.twig index 610280e..616c35f 100644 --- a/src/resources/templates/partials/_empty-properties.html.twig +++ b/src/resources/templates/partials/_empty-properties.html.twig @@ -7,6 +7,9 @@ {% for property in definition.properties %} {% include 'partials/_property.html.twig' %} {% endfor %} + {% for case in definition.cases|default([]) %} + {% include 'partials/_case.html.twig' %} + {% endfor %} {% endif %} diff --git a/src/resources/templates/partials/_enum-name.html.twig b/src/resources/templates/partials/_enum-name.html.twig new file mode 100644 index 0000000..2c0f900 --- /dev/null +++ b/src/resources/templates/partials/_enum-name.html.twig @@ -0,0 +1,13 @@ + + + + <<enum>> + +
+ + + {{ definition.name }} + + + + diff --git a/src/resources/templates/partials/_properties.html.twig b/src/resources/templates/partials/_properties.html.twig index 0a2297d..d365f5d 100644 --- a/src/resources/templates/partials/_properties.html.twig +++ b/src/resources/templates/partials/_properties.html.twig @@ -7,6 +7,9 @@ {% for property in definition.properties %} {% include 'partials/_property.html.twig' %} {% endfor %} + {% for case in definition.cases|default([]) %} + {% include 'partials/_case.html.twig' %} + {% endfor %} {% else %}   {% endif %} diff --git a/src/resources/templates/uml/enum.html.twig b/src/resources/templates/uml/enum.html.twig new file mode 100644 index 0000000..96a8dc0 --- /dev/null +++ b/src/resources/templates/uml/enum.html.twig @@ -0,0 +1,8 @@ +{% set tag %} + + {% include 'partials/_enum-name.html.twig' with {'definition': definition, 'theme': theme} %} + {% include style.properties with {'definition': definition, 'theme' : theme} %} + {% include style.methods with {'definition': definition, 'theme' : theme} %} +
+{% endset %} +{{ tag|replace({ '\n': '', '\r': '' })|whitespace|raw }} diff --git a/tests/resources/.code/classes/hasvalue.php b/tests/resources/.code/classes/hasvalue.php new file mode 100644 index 0000000..776caf0 --- /dev/null +++ b/tests/resources/.code/classes/hasvalue.php @@ -0,0 +1,8 @@ +name; + } +} diff --git a/tests/resources/.code/classes/withvalue.php b/tests/resources/.code/classes/withvalue.php new file mode 100644 index 0000000..ebf0053 --- /dev/null +++ b/tests/resources/.code/classes/withvalue.php @@ -0,0 +1,10 @@ +withConstants(A::constant('CONSTANT')->build()) + ->build(); + $enumWithCases = A::enum('EnumWithCases') + ->withCases('PRIVATE', 'PROTECTED') + ->build(); + $enumWithCasesAndConstants = A::enum('EnumWithConstants') + ->withConstants(A::constant('CONSTANT')->build()) + ->withCases('PRIVATE', 'PROTECTED') + ->build(); + $enumWithoutProperties = A::enum('EnumWithoutProperties')->build(); + + $this->assertTrue($enumWithConstants->hasProperties()); + $this->assertTrue($enumWithCases->hasProperties()); + $this->assertTrue($enumWithCasesAndConstants->hasProperties()); + $this->assertFalse($enumWithoutProperties->hasProperties()); + } +} diff --git a/tests/src/Fakes/WithDotLanguageAssertions.php b/tests/src/Fakes/WithDotLanguageAssertions.php index 76c5aeb..03f3c10 100644 --- a/tests/src/Fakes/WithDotLanguageAssertions.php +++ b/tests/src/Fakes/WithDotLanguageAssertions.php @@ -7,6 +7,7 @@ use PhUml\Code\ClassDefinition; use PhUml\Code\Definition; +use PhUml\Code\EnumDefinition; use PhUml\Code\InterfaceDefinition; use PhUml\Code\TraitDefinition; @@ -37,16 +38,16 @@ public function assertInheritance( } public function assertImplementation( - ClassDefinition $class, + ClassDefinition|EnumDefinition $classOrEnum, InterfaceDefinition $interface, string $dotLanguage ): void { $interfaceIdentifier = str_replace('\\', '\\\\', $interface->identifier()); - $identifier = str_replace('\\', '\\\\', $class->identifier()); + $identifier = str_replace('\\', '\\\\', $classOrEnum->identifier()); $this->assertMatchesRegularExpression( "/\"{$interfaceIdentifier}\" -> \"{$identifier}\" \\[dir=back arrowtail=empty style=dashed color=\"#[0-9a-f]{6}\"\\]/", $dotLanguage, - "{$class->name()} does not implements {$interface->name()}" + "{$classOrEnum->name()} does not implements {$interface->name()}" ); } @@ -63,14 +64,14 @@ public function assertAssociation( } public function assertUseTrait( - ClassDefinition $class, + ClassDefinition|EnumDefinition $classOrEnum, TraitDefinition $trait, string $dotLanguage ): void { $this->assertMatchesRegularExpression( - "/\"{$trait->identifier()}\" -> \"{$class->identifier()}\" \\[dir=back arrowtail=normal style=solid color=\"#[0-9a-f]{6}\"\\]/", + "/\"{$trait->identifier()}\" -> \"{$classOrEnum->identifier()}\" \\[dir=back arrowtail=normal style=solid color=\"#[0-9a-f]{6}\"\\]/", $dotLanguage, - "Class {$class->name()} does not use trait {$trait->name()}" + "{$classOrEnum->name()} does not use trait {$trait->name()}" ); } } diff --git a/tests/src/Parser/Code/Builders/EnumDefinitionBuilderTest.php b/tests/src/Parser/Code/Builders/EnumDefinitionBuilderTest.php new file mode 100644 index 0000000..a401b28 --- /dev/null +++ b/tests/src/Parser/Code/Builders/EnumDefinitionBuilderTest.php @@ -0,0 +1,144 @@ + [ + new TraitUse([ + new Name('ATrait'), + new Name('AnotherTrait'), + ]), + ], + ]); + $parsedEnum->namespacedName = new Name('AnEnumWithTraits'); + + $enum = $this->builder->build($parsedEnum); + + $enumDefinition = A::enum('AnEnumWithTraits') + ->using(new DefinitionName('ATrait'), new DefinitionName('AnotherTrait')) + ->build(); + $this->assertEquals($enumDefinition, $enum); + } + + /** @test */ + function it_builds_an_enum_implementing_interfaces() + { + $parsedEnum = new Enum_(new Identifier('AnEnumImplementingInterfaces'), [ + 'implements' => [ + new Name('AnInterface'), + new Name('AnotherInterface'), + ], + ]); + $parsedEnum->namespacedName = new Name('AnEnumImplementingInterfaces'); + + $enum = $this->builder->build($parsedEnum); + + $enumDefinition = A::enum('AnEnumImplementingInterfaces') + ->implementing(new DefinitionName('AnInterface'), new DefinitionName('AnotherInterface')) + ->build(); + $this->assertEquals($enumDefinition, $enum); + } + + /** @test */ + function it_builds_an_enum_with_methods() + { + $parsedEnum = new Enum_('AnEnum', [ + 'stmts' => [ + new ClassMethod('privateMethod', ['type' => Class_::MODIFIER_PRIVATE]), + new ClassMethod('protectedMethod', ['type' => Class_::MODIFIER_PROTECTED]), + new ClassMethod('staticMethod', ['type' => Class_::MODIFIER_STATIC]), + new ClassMethod('publicMethod', ['type' => Class_::MODIFIER_PUBLIC]), + ], + ]); + $parsedEnum->namespacedName = 'AnEnum'; + + $enum = $this->builder->build($parsedEnum); + + $enumWithMultipleTypesOfMethods = A::enum('AnEnum') + ->withAPrivateMethod('privateMethod') + ->withAProtectedMethod('protectedMethod') + ->withAMethod(A::method('staticMethod')->public()->static()->build()) + ->withAMethod(A::method('publicMethod')->public()->build()) + ->build(); + $this->assertEquals($enumWithMultipleTypesOfMethods, $enum); + } + + /** @test */ + function it_builds_an_enum_with_constants() + { + $parsedEnum = new Enum_('AnEnum', [ + 'stmts' => [ + new ClassConst([new Const_('INTEGER', new LNumber(1))]), + new ClassConst([new Const_('FLOAT', new DNumber(1.5))], Class_::MODIFIER_PRIVATE), + new ClassConst([new Const_('STRING', new String_('test'))], Class_::MODIFIER_PROTECTED), + new ClassConst([new Const_('IS_TRUE', new ConstFetch(new Name(['false'])))]), + ], + ]); + $parsedEnum->namespacedName = 'AnEnum'; + + $enum = $this->builder->build($parsedEnum); + + $enumWithMultipleTypesOfMethods = A::enum('AnEnum') + ->withConstants( + A::constant('INTEGER')->public()->withType('int')->build(), + A::constant('FLOAT')->private()->withType('float')->build(), + A::constant('STRING')->protected()->withType('string')->build(), + A::constant('IS_TRUE')->public()->withType('bool')->build(), + ) + ->build(); + $this->assertEquals($enumWithMultipleTypesOfMethods, $enum); + } + + /** @test */ + function it_builds_an_enum_with_cases() + { + $parsedEnum = new Enum_('Visibility', [ + 'stmts' => [ + new EnumCase('PRIVATE'), + new EnumCase('PROTECTED'), + new EnumCase('PUBLIC'), + ], + ]); + $parsedEnum->namespacedName = 'AnEnum'; + + $enum = $this->builder->build($parsedEnum); + + $enumWithMultipleTypesOfMethods = A::enum('AnEnum') + ->withCases('PRIVATE', 'PROTECTED', 'PUBLIC') + ->build(); + $this->assertEquals($enumWithMultipleTypesOfMethods, $enum); + } + + /** @before */ + function let() + { + $this->builder = new EnumDefinitionBuilder(A::membersBuilder()->build(), new UseStatementsBuilder()); + } + + private EnumDefinitionBuilder $builder; +} diff --git a/tests/src/TestBuilders/A.php b/tests/src/TestBuilders/A.php index 0ad3341..3ea2322 100644 --- a/tests/src/TestBuilders/A.php +++ b/tests/src/TestBuilders/A.php @@ -42,6 +42,11 @@ public static function trait(string $name): TraitBuilder return new TraitBuilder($name); } + public static function enum(string $name): EnumBuilder + { + return new EnumBuilder($name); + } + public static function parameter(string $name): ParameterBuilder { return new ParameterBuilder($name); @@ -106,4 +111,9 @@ public static function codeFinderConfiguration(): CodeFinderConfigurationBuilder { return new CodeFinderConfigurationBuilder(); } + + public static function constant(string $name): ConstantBuilder + { + return new ConstantBuilder($name); + } } diff --git a/tests/src/TestBuilders/ConstantBuilder.php b/tests/src/TestBuilders/ConstantBuilder.php new file mode 100644 index 0000000..39e8570 --- /dev/null +++ b/tests/src/TestBuilders/ConstantBuilder.php @@ -0,0 +1,52 @@ +type = TypeDeclaration::absent(); + $this->visibility = Visibility::PUBLIC; + } + + public function public(): ConstantBuilder + { + $this->visibility = Visibility::PUBLIC; + return $this; + } + + public function withType(string $type): ConstantBuilder + { + $this->type = TypeDeclaration::from($type); + return $this; + } + + public function private(): ConstantBuilder + { + $this->visibility = Visibility::PRIVATE; + return $this; + } + + public function protected(): ConstantBuilder + { + $this->visibility = Visibility::PROTECTED; + return $this; + } + + public function build(): Constant + { + return new Constant($this->name, $this->type, $this->visibility); + } +} diff --git a/tests/src/TestBuilders/EnumBuilder.php b/tests/src/TestBuilders/EnumBuilder.php new file mode 100644 index 0000000..507ccf2 --- /dev/null +++ b/tests/src/TestBuilders/EnumBuilder.php @@ -0,0 +1,70 @@ +traits = $traits; + + return $this; + } + + public function implementing(Name ...$interfaces): EnumBuilder + { + $this->interfaces = array_merge($this->interfaces, $interfaces); + + return $this; + } + + public function withConstants(Constant ...$constants): EnumBuilder + { + $this->constants = $constants; + return $this; + } + + public function withCases(string ...$cases): EnumBuilder + { + $this->cases = array_map(static fn ($case) => new EnumCase($case), $cases); + return $this; + } + + public function build(): EnumDefinition + { + return new EnumDefinition( + new Name($this->name), + $this->cases, + $this->methods, + $this->constants, + $this->interfaces, + $this->traits + ); + } +} diff --git a/tests/unit/Generators/StatisticsGeneratorTest.php b/tests/unit/Generators/StatisticsGeneratorTest.php index 0743efc..01d4243 100644 --- a/tests/unit/Generators/StatisticsGeneratorTest.php +++ b/tests/unit/Generators/StatisticsGeneratorTest.php @@ -22,23 +22,23 @@ function it_shows_the_statistics_of_a_directory() ------------------ Classes: 2 -Interfaces: 0 +Interfaces: 1 Attributes: 5 (3 are typed) * private: 4 * protected: 1 * public: 0 -Functions: 11 +Functions: 15 * private: 1 * protected: 0 - * public: 10 + * public: 14 Average statistics ------------------ Attributes per class: 2.5 -Functions per class: 5.5 +Functions per class: 7.5 STATS; $generator = StatisticsGenerator::fromConfiguration(A::statisticsGeneratorConfiguration()->build()); @@ -59,23 +59,23 @@ function it_shows_the_statistics_of_a_directory_using_a_recursive_finder() ------------------ Classes: 21 -Interfaces: 0 +Interfaces: 1 -Attributes: 24 (6 are typed) - * private: 18 +Attributes: 25 (7 are typed) + * private: 19 * protected: 2 * public: 4 -Functions: 87 +Functions: 91 * private: 36 * protected: 0 - * public: 51 + * public: 55 Average statistics ------------------ -Attributes per class: 1.14 -Functions per class: 4.14 +Attributes per class: 1.19 +Functions per class: 4.33 STATS; $configuration = A::statisticsGeneratorConfiguration()->recursive()->build(); diff --git a/tests/src/Graphviz/Builders/EdgeKeyTest.php b/tests/unit/Graphviz/Builders/EdgeKeyTest.php similarity index 68% rename from tests/src/Graphviz/Builders/EdgeKeyTest.php rename to tests/unit/Graphviz/Builders/EdgeKeyTest.php index 11f7314..467c8d5 100644 --- a/tests/src/Graphviz/Builders/EdgeKeyTest.php +++ b/tests/unit/Graphviz/Builders/EdgeKeyTest.php @@ -17,17 +17,17 @@ function it_can_be_converted_to_string() $keyA = EdgeKey::from(new Name('AClass'), TypeDeclaration::from('AnotherClass')); $keyB = EdgeKey::from(new Name('ThirdClass'), TypeDeclaration::from('FourthClass')); - $this->assertEquals('AClassAnotherClass', $keyA); - $this->assertEquals('ThirdClassFourthClass', $keyB); - $this->assertEquals((string) $keyA, (string) $keyA); - $this->assertEquals( + $this->assertSame('AClassAnotherClass', (string) $keyA); + $this->assertSame('ThirdClassFourthClass', (string) $keyB); + $this->assertSame((string) $keyA, (string) $keyA); + $this->assertSame( (string) $keyB, (string) EdgeKey::from( new Name('ThirdClass'), TypeDeclaration::from('FourthClass') ) ); - $this->assertNotEquals((string) $keyA, (string) $keyB); - $this->assertNotEquals((string) $keyB, (string) $keyA); + $this->assertNotSame((string) $keyA, (string) $keyB); + $this->assertNotSame((string) $keyB, (string) $keyA); } } diff --git a/tests/unit/Graphviz/Builders/EnumGraphBuilderTest.php b/tests/unit/Graphviz/Builders/EnumGraphBuilderTest.php new file mode 100644 index 0000000..8d98f58 --- /dev/null +++ b/tests/unit/Graphviz/Builders/EnumGraphBuilderTest.php @@ -0,0 +1,66 @@ +using($anotherTrait->name(), $thirdTrait->name()) + ->build(); + $codebase = new Codebase(); + $codebase->add($anotherTrait); + $codebase->add($thirdTrait); + + + $nodes = $this->builder->extractFrom($enum, $codebase); + + $this->assertEquals([ + new Node($enum), + Edge::use($anotherTrait, $enum), + Edge::use($thirdTrait, $enum), + ], $nodes); + } + + /** @test */ + function it_extracts_edges_from_the_interfaces_it_implements() + { + $firstInterface = A::interfaceNamed('FirstInterface'); + $secondInterface = A::interfaceNamed('FirstInterface'); + $enum = A::enum('AnEnum') + ->implementing($firstInterface->name(), $secondInterface->name()) + ->build(); + $codebase = new Codebase(); + $codebase->add($firstInterface); + $codebase->add($secondInterface); + + $dotElements = $this->builder->extractFrom($enum, $codebase); + + $this->assertEquals([ + new Node($enum), + Edge::implementation($firstInterface, $enum), + Edge::implementation($secondInterface, $enum), + ], $dotElements); + } + + /** @before */ + function let() + { + $this->builder = new EnumGraphBuilder(); + } + + private EnumGraphBuilder $builder; +} diff --git a/tests/unit/Graphviz/Builders/TraitGraphBuilderTest.php b/tests/unit/Graphviz/Builders/TraitGraphBuilderTest.php index 98bef9e..ea76f31 100644 --- a/tests/unit/Graphviz/Builders/TraitGraphBuilderTest.php +++ b/tests/unit/Graphviz/Builders/TraitGraphBuilderTest.php @@ -17,9 +17,8 @@ final class TraitGraphBuilderTest extends TestCase function it_extracts_the_node_for_a_trait() { $trait = A::traitNamed('ATrait'); - $builders = new TraitGraphBuilder(); - $nodes = $builders->extractFrom($trait, new Codebase()); + $nodes = $this->builder->extractFrom($trait, new Codebase()); $this->assertCount(1, $nodes); $this->assertEquals([new Node($trait)], $nodes); @@ -32,14 +31,12 @@ function it_extract_the_edges_from_the_traits_it_uses() $thirdTrait = A::traitNamed('ThirdTrait'); $trait = A::trait('ATrait') ->using($anotherTrait->name(), $thirdTrait->name()) - ->build() - ; + ->build(); $codebase = new Codebase(); $codebase->add($anotherTrait); $codebase->add($thirdTrait); - $builders = new TraitGraphBuilder(); - $nodes = $builders->extractFrom($trait, $codebase); + $nodes = $this->builder->extractFrom($trait, $codebase); $this->assertEquals([ new Node($trait), @@ -47,4 +44,12 @@ function it_extract_the_edges_from_the_traits_it_uses() Edge::use($thirdTrait, $trait), ], $nodes); } + + /** @before */ + function let() + { + $this->builder = new TraitGraphBuilder(); + } + + private TraitGraphBuilder $builder; } diff --git a/tests/unit/Parser/Code/ExternalDefinitionsResolverTest.php b/tests/unit/Parser/Code/ExternalDefinitionsResolverTest.php index b567a53..f64257e 100644 --- a/tests/unit/Parser/Code/ExternalDefinitionsResolverTest.php +++ b/tests/unit/Parser/Code/ExternalDefinitionsResolverTest.php @@ -31,56 +31,62 @@ function it_does_not_change_the_definitions_if_no_relations_are_declared() /** @test */ function it_adds_external_interfaces() { - $codebase = new Codebase(); - $resolver = new ExternalDefinitionsResolver(); - - $codebase->add(A::class('AClass') + $this->codebase->add(A::class('AClass') ->implementing(new Name('AnExternalInterface'), new Name('AnExistingInterface')) ->build()); - $codebase->add(A::interface('AnInterface') + $this->codebase->add(A::interface('AnInterface') ->extending(new Name('AnotherExternalInterface'))->build()); - $codebase->add(A::interface('AnExistingInterface')->build()); + $this->codebase->add(A::interface('AnExistingInterface')->build()); + $this->codebase->add(A::enum('AnEnum')->implementing(new Name('ThirdExternalInterface'))->build()); - $resolver->resolve($codebase); + $this->resolver->resolve($this->codebase); - $this->assertCount(5, $codebase->definitions()); - $this->assertArrayHasKey('AnExternalInterface', $codebase->definitions()); - $this->assertArrayHasKey('AnotherExternalInterface', $codebase->definitions()); + $this->assertCount(7, $this->codebase->definitions()); + $this->assertArrayHasKey('AnExternalInterface', $this->codebase->definitions()); + $this->assertArrayHasKey('AnotherExternalInterface', $this->codebase->definitions()); + $this->assertArrayHasKey('ThirdExternalInterface', $this->codebase->definitions()); } /** @test */ function it_adds_external_classes() { - $codebase = new Codebase(); - $resolver = new ExternalDefinitionsResolver(); - - $codebase->add(A::class('AClass')->extending(new Name('AnExternalClass'))->build()); - $codebase->add(A::class('AnotherClass')->extending(new Name('AnotherExternalClass'))->build()); + $this->codebase->add(A::class('AClass')->extending(new Name('AnExternalClass'))->build()); + $this->codebase->add(A::class('AnotherClass')->extending(new Name('AnotherExternalClass'))->build()); - $resolver->resolve($codebase); + $this->resolver->resolve($this->codebase); - $this->assertCount(4, $codebase->definitions()); - $this->assertArrayHasKey('AnExternalClass', $codebase->definitions()); - $this->assertArrayHasKey('AnotherExternalClass', $codebase->definitions()); + $this->assertCount(4, $this->codebase->definitions()); + $this->assertArrayHasKey('AnExternalClass', $this->codebase->definitions()); + $this->assertArrayHasKey('AnotherExternalClass', $this->codebase->definitions()); } /** @test */ function it_adds_external_traits() { - $codebase = new Codebase(); - $resolver = new ExternalDefinitionsResolver(); - - $codebase->add(A::class('AClass') + $this->codebase->add(A::class('AClass') ->using(new Name('AnExternalTrait'), new Name('AnExistingTrait')) ->build()); - $codebase->add(A::trait('ATrait') + $this->codebase->add(A::trait('ATrait') ->using(new Name('AnotherExternalTrait'))->build()); - $codebase->add(A::trait('AnExistingTrait')->build()); + $this->codebase->add(A::trait('AnExistingTrait')->build()); + $this->codebase->add(A::enum('AnEnum')->using(new Name('ThirdExternalTrait'))->build()); - $resolver->resolve($codebase); + $this->resolver->resolve($this->codebase); - $this->assertCount(5, $codebase->definitions()); - $this->assertArrayHasKey('AnExternalTrait', $codebase->definitions()); - $this->assertArrayHasKey('AnotherExternalTrait', $codebase->definitions()); + $this->assertCount(7, $this->codebase->definitions()); + $this->assertArrayHasKey('AnExternalTrait', $this->codebase->definitions()); + $this->assertArrayHasKey('AnotherExternalTrait', $this->codebase->definitions()); + $this->assertArrayHasKey('ThirdExternalTrait', $this->codebase->definitions()); } + + /** @before */ + function let() + { + $this->codebase = new Codebase(); + $this->resolver = new ExternalDefinitionsResolver(); + } + + private Codebase $codebase; + + private ExternalDefinitionsResolver $resolver; } diff --git a/tests/unit/Parser/Code/PhpCodeParserTest.php b/tests/unit/Parser/Code/PhpCodeParserTest.php index 74b76fd..207ec8d 100644 --- a/tests/unit/Parser/Code/PhpCodeParserTest.php +++ b/tests/unit/Parser/Code/PhpCodeParserTest.php @@ -6,6 +6,7 @@ namespace PhUml\Parser\Code; use PHPUnit\Framework\TestCase; +use PhUml\Code\EnumDefinition; use PhUml\Fakes\WithVisibilityAssertions; use PhUml\Parser\CodebaseDirectory; use PhUml\Parser\CodeFinder; @@ -26,7 +27,7 @@ function it_excludes_methods() $definitions = $parser->parse($sourceCode)->definitions(); - $this->assertCount(2, $definitions); + $this->assertCount(5, $definitions); $this->assertEmpty($definitions['phuml\\plBase']->methods()); $this->assertNotEmpty($definitions['phuml\\plBase']->properties()); $this->assertEmpty($definitions['phuml\\plPhuml']->methods()); @@ -42,7 +43,7 @@ function it_excludes_properties() $definitions = $parser->parse($sourceCode)->definitions(); - $this->assertCount(2, $definitions); + $this->assertCount(5, $definitions); $this->assertEmpty($definitions['phuml\\plBase']->properties()); $this->assertNotEmpty($definitions['phuml\\plBase']->methods()); $this->assertEmpty($definitions['phuml\\plPhuml']->properties()); @@ -58,7 +59,7 @@ function it_excludes_both_methods_and_properties() $definitions = $parser->parse($sourceCode)->definitions(); - $this->assertCount(2, $definitions); + $this->assertCount(5, $definitions); $this->assertEmpty($definitions['phuml\\plBase']->properties()); $this->assertEmpty($definitions['phuml\\plBase']->methods()); $this->assertEmpty($definitions['phuml\\plPhuml']->properties()); @@ -74,7 +75,7 @@ function it_excludes_private_members() $definitions = $parser->parse($sourceCode)->definitions(); - $this->assertCount(2, $definitions); + $this->assertCount(5, $definitions); $this->assertCount(2, $definitions['phuml\\plBase']->constants()); $this->assertProtected($definitions['phuml\\plBase']->constants()[1]); $this->assertPublic($definitions['phuml\\plBase']->constants()[2]); @@ -104,7 +105,7 @@ function it_excludes_protected_members() $definitions = $parser->parse($sourceCode)->definitions(); - $this->assertCount(2, $definitions); + $this->assertCount(5, $definitions); $this->assertCount(2, $definitions['phuml\\plBase']->constants()); $this->assertPrivate($definitions['phuml\\plBase']->constants()[0]); $this->assertPublic($definitions['phuml\\plBase']->constants()[2]); @@ -138,7 +139,7 @@ function it_excludes_private_and_protected_members() $definitions = $parser->parse($sourceCode)->definitions(); - $this->assertCount(2, $definitions); + $this->assertCount(5, $definitions); $this->assertCount(1, $definitions['phuml\\plBase']->constants()); $this->assertPublic($definitions['phuml\\plBase']->constants()[2]); $this->assertEmpty($definitions['phuml\\plBase']->properties()); @@ -157,6 +158,24 @@ function it_excludes_private_and_protected_members() $this->assertPublic($definitions['phuml\\plPhuml']->methods()[7]); } + /** @test */ + function it_includes_enum_definitions() + { + $configuration = A::codeParserConfiguration()->build(); + $parser = PhpCodeParser::fromConfiguration($configuration); + $sourceCode = $this->finder->find($this->directory); + + $definitions = $parser->parse($sourceCode)->definitions(); + + $this->assertCount(5, $definitions); + /** @var EnumDefinition $visibility */ + $visibility = $definitions['phuml\\plVisibility']; + $this->assertCount(1, $visibility->constants()); + $this->assertCount(2, $visibility->methods()); + $this->assertCount(1, $visibility->interfaces()); + $this->assertCount(1, $visibility->traits()); + } + /** @before */ function let() { diff --git a/tests/unit/Parser/CodeFinderTest.php b/tests/unit/Parser/CodeFinderTest.php index 5a46e1a..bbee878 100644 --- a/tests/unit/Parser/CodeFinderTest.php +++ b/tests/unit/Parser/CodeFinderTest.php @@ -16,9 +16,12 @@ function it_finds_files_only_in_the_given_directory() $sourceCode = $finder->find(new CodebaseDirectory("{$this->pathToCode}/classes")); - $this->assertCount(2, $sourceCode->fileContents()); + $this->assertCount(5, $sourceCode->fileContents()); $this->assertMatchesRegularExpression('/class plBase/', $sourceCode->fileContents()[0]); - $this->assertMatchesRegularExpression('/class plPhuml/', $sourceCode->fileContents()[1]); + $this->assertMatchesRegularExpression('/interface plHasValue/', $sourceCode->fileContents()[1]); + $this->assertMatchesRegularExpression('/class plPhuml/', $sourceCode->fileContents()[2]); + $this->assertMatchesRegularExpression('/enum plVisibility/', $sourceCode->fileContents()[3]); + $this->assertMatchesRegularExpression('/trait plWithValue/', $sourceCode->fileContents()[4]); } /** @test */ diff --git a/tests/unit/Processors/GraphvizProcessorTest.php b/tests/unit/Processors/GraphvizProcessorTest.php index 20cb58a..103a69a 100644 --- a/tests/unit/Processors/GraphvizProcessorTest.php +++ b/tests/unit/Processors/GraphvizProcessorTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use PhUml\Code\Codebase; +use PhUml\Code\Name; use PhUml\Fakes\WithDotLanguageAssertions; use PhUml\TestBuilders\A; @@ -25,7 +26,7 @@ function it_has_a_name() } /** @test */ - function it_turns_a_code_structure_into_dot_language() + function it_turns_a_codebase_into_dot_language() { $processor = A::graphvizProcessor()->withAssociations()->build(); $parentInterface = A::interfaceNamed('ParentInterface'); @@ -47,6 +48,10 @@ function it_turns_a_code_structure_into_dot_language() ->extending($parentClass->name()) ->using($trait->name()) ->build(); + $enum = A::enum('AnEnum') + ->implementing(new Name('ImplementedInterface')) + ->using(new Name('ATrait')) + ->build(); $codebase = new Codebase(); $codebase->add($parentClass); $codebase->add($reference); @@ -54,19 +59,23 @@ function it_turns_a_code_structure_into_dot_language() $codebase->add($interface); $codebase->add($class); $codebase->add($trait); + $codebase->add($enum); $dotLanguage = $processor->process($codebase); $this->assertNode($parentClass, $dotLanguage->value()); $this->assertNode($reference, $dotLanguage->value()); $this->assertNode($class, $dotLanguage->value()); + $this->assertNode($enum, $dotLanguage->value()); $this->assertInheritance($class, $parentClass, $dotLanguage->value()); $this->assertAssociation($reference, $class, $dotLanguage->value()); $this->assertImplementation($class, $interface, $dotLanguage->value()); + $this->assertImplementation($enum, $interface, $dotLanguage->value()); $this->assertNode($parentInterface, $dotLanguage->value()); $this->assertNode($interface, $dotLanguage->value()); $this->assertInheritance($interface, $parentInterface, $dotLanguage->value()); $this->assertNode($trait, $dotLanguage->value()); $this->assertUseTrait($class, $trait, $dotLanguage->value()); + $this->assertUseTrait($enum, $trait, $dotLanguage->value()); } }