From eb7cdaedc729e86945fdf50957f66c18dcf1d763 Mon Sep 17 00:00:00 2001 From: Houssem Eddine BENABDALLAH Date: Fri, 30 Oct 2026 09:57:56 +0100 Subject: [PATCH] Apply the AGENTS.md rules to src/: no nested ternaries, exact PHPDoc shapes, recursive structures as readonly objects --- src/Builder/ActionStats.php | 6 +- src/Builder/ApiSchemaStats.php | 6 +- src/Builder/BuildState.php | 2 +- src/Builder/BusStats.php | 6 +- src/Builder/CallStats.php | 2 +- src/Builder/ConfigurationEvaluator.php | 192 ++++++++++++------------ src/Builder/ContainerServices.php | 25 ++- src/Builder/Generics.php | 4 +- src/Builder/GraphBuilder.php | 20 ++- src/Builder/HttpStats.php | 6 +- src/Builder/InjectionStats.php | 2 +- src/Builder/ServiceInjections.php | 5 +- src/Builder/TypeResolver.php | 43 +++--- src/Builder/YamlRoutes.php | 6 +- src/Command/CheckCommand.php | 5 +- src/Command/McpConfigCommand.php | 18 ++- src/Extractor/ApiPlatformResources.php | 4 +- src/Extractor/CachingExtractor.php | 3 + src/Extractor/ConditionalArgument.php | 16 ++ src/Extractor/ConditionalClass.php | 18 +++ src/Extractor/ConditionalReturn.php | 41 +++++ src/Extractor/ConditionalTest.php | 14 ++ src/Extractor/ConfigurationHelper.php | 21 +++ src/Extractor/ConfigurationHelpers.php | 127 +++++++++------- src/Extractor/DefinitionKind.php | 15 ++ src/Extractor/DocTypeResolver.php | 36 ++--- src/Extractor/ExtractionVisitor.php | 78 +++++++--- src/Extractor/FileExtraction.php | 6 +- src/Extractor/HelperArgument.php | 21 +++ src/Extractor/PendingHelperCall.php | 43 ++++++ src/Extractor/ServiceArgumentFact.php | 25 +++ src/Extractor/ServiceDefinitionFact.php | 20 +++ src/Extractor/ServiceTagFact.php | 21 +++ src/Extractor/SourceFacts.php | 17 ++- src/Extractor/Template.php | 91 +++++++++++ src/Extractor/TemplateKind.php | 19 +++ src/Extractor/TypeExpr.php | 22 ++- src/Graph/Edge.php | 14 +- src/Graph/Graph.php | 5 +- src/Graph/Node.php | 18 ++- src/Mcp/McpServer.php | 15 +- src/Presentation/ImpactReport.php | 111 ++++++++++---- src/Presentation/OutlineReport.php | 151 ++++++++++++++----- src/Presentation/TextPresenter.php | 78 ++++++---- src/Project/ProjectSummary.php | 34 ++++- src/Project/Rules/Pattern.php | 4 +- src/Query/FeatureOutline.php | 10 +- src/Query/GraphQuery.php | 4 +- src/Vendor/ComposerClassLocator.php | 5 +- src/Vendor/VendorSignatures.php | 7 +- tests/CacheTest.php | 26 ++++ tests/ConfigurationHelpersTest.php | 14 ++ 52 files changed, 1125 insertions(+), 377 deletions(-) create mode 100644 src/Extractor/ConditionalArgument.php create mode 100644 src/Extractor/ConditionalClass.php create mode 100644 src/Extractor/ConditionalReturn.php create mode 100644 src/Extractor/ConditionalTest.php create mode 100644 src/Extractor/ConfigurationHelper.php create mode 100644 src/Extractor/DefinitionKind.php create mode 100644 src/Extractor/HelperArgument.php create mode 100644 src/Extractor/PendingHelperCall.php create mode 100644 src/Extractor/ServiceArgumentFact.php create mode 100644 src/Extractor/ServiceDefinitionFact.php create mode 100644 src/Extractor/ServiceTagFact.php create mode 100644 src/Extractor/Template.php create mode 100644 src/Extractor/TemplateKind.php diff --git a/src/Builder/ActionStats.php b/src/Builder/ActionStats.php index 92e6662..ffda2be 100644 --- a/src/Builder/ActionStats.php +++ b/src/Builder/ActionStats.php @@ -73,7 +73,11 @@ public static function fromArray(array $data): self } /** - * @return array> + * @return array{ + * actions: int, linked: int, toClass: int, unlinkedCount: int, unlinked: list, forwards: int, + * forwardsLinked: int, templates: int, unresolvedTargetCount: int, unresolvedTargets: list, + * citations: int, citingFiles: int, unreadable: list, + * } */ public function toArray(): array { diff --git a/src/Builder/ApiSchemaStats.php b/src/Builder/ApiSchemaStats.php index f4ea831..a0da63a 100644 --- a/src/Builder/ApiSchemaStats.php +++ b/src/Builder/ApiSchemaStats.php @@ -70,7 +70,11 @@ public static function fromArray(array $data): self } /** - * @return array> + * @return array{ + * documents: int, operations: int, implemented: int, notImplemented: list, routesCovered: int, + * undocumentedCount: int, undocumented: list, rpcs: int, rpcsImplemented: int, + * rpcsNotImplemented: list, otherApis: int, requests: int, + * } */ public function toArray(): array { diff --git a/src/Builder/BuildState.php b/src/Builder/BuildState.php index 41316a9..ea4e2ed 100644 --- a/src/Builder/BuildState.php +++ b/src/Builder/BuildState.php @@ -24,7 +24,7 @@ final class BuildState * @param string $names ids every name resolved to * @param list $overrides * @param array> $calls file => [edge added, outcome, method] per call site - * @param array}> $configuration parsed configuration files: [mtime, size, data] + * @param array, parameters: array, tags: list}>, arguments: list}}> $configuration parsed configuration files: [mtime, size, what each declares] */ public function __construct( public readonly string $options, diff --git a/src/Builder/BusStats.php b/src/Builder/BusStats.php index f2737dc..bc90ac8 100644 --- a/src/Builder/BusStats.php +++ b/src/Builder/BusStats.php @@ -56,7 +56,11 @@ public static function fromArray(array $data): self } /** - * @return array + * @return array{ + * handlers: array, sends: array, untypedSends: int, + * messagesWithoutHandlerCount: int, messagesWithoutHandler: list, + * messagesNeverSentCount: int, messagesNeverSent: list, contracts: int, + * } */ public function toArray(): array { diff --git a/src/Builder/CallStats.php b/src/Builder/CallStats.php index 02981c9..32ff560 100644 --- a/src/Builder/CallStats.php +++ b/src/Builder/CallStats.php @@ -49,7 +49,7 @@ public function total(): int } /** - * @return array + * @return array{total: int, inferred: int, ambiguous: int, outsideProject: int, unknownReceiver: int, chainOutsideProject: int} */ public function toArray(): array { diff --git a/src/Builder/ConfigurationEvaluator.php b/src/Builder/ConfigurationEvaluator.php index 9395d0e..e4ce068 100644 --- a/src/Builder/ConfigurationEvaluator.php +++ b/src/Builder/ConfigurationEvaluator.php @@ -4,7 +4,14 @@ namespace PhpGraph\Builder; +use PhpGraph\Extractor\ConfigurationHelper; use PhpGraph\Extractor\ConfigurationHelpers; +use PhpGraph\Extractor\DefinitionKind; +use PhpGraph\Extractor\PendingHelperCall; +use PhpGraph\Extractor\ServiceDefinitionFact; +use PhpGraph\Extractor\ServiceTagFact; +use PhpGraph\Extractor\Template; +use PhpGraph\Extractor\TemplateKind; /** * Evaluates the service configuration written in helpers (see ConfigurationHelpers): each call whose arguments are @@ -18,10 +25,10 @@ final class ConfigurationEvaluator { private const MAX_DEPTH = 5; - /** @var array, facts: list>, file: string}> lowercase helper id => helper */ + /** @var array lowercase helper id => helper */ private array $helpers = []; - /** @var list}>, file: string, service: string}> */ + /** @var list */ private array $calls = []; /** @var array{definitions: array>, tags: array}>>, arguments: array>} */ @@ -34,20 +41,14 @@ public function __construct(private readonly \Closure $constant) { } - /** - * @param array{params: list, facts: list>} $helper - */ - public function addHelper(string $id, array $helper, string $file): void + public function addHelper(string $id, ConfigurationHelper $helper, string $file): void { - $this->helpers[strtolower($id)] = $helper + ['file' => $file]; + $this->helpers[strtolower($id)] = ['helper' => $helper, 'file' => $file]; } - /** - * @param array{caller: ?string, callee: string, args: list}>, line: int} $call - */ - public function addCall(array $call, string $file, string $service): void + public function addCall(PendingHelperCall $call, string $file, string $service): void { - $this->calls[] = ['caller' => $call['caller'], 'callee' => $call['callee'], 'args' => $call['args'], 'file' => $file, 'service' => $service]; + $this->calls[] = ['call' => $call, 'file' => $file, 'service' => $service]; } /** @@ -55,12 +56,12 @@ public function addCall(array $call, string $file, string $service): void */ public function evaluate(): array { - foreach ($this->calls as $call) { + foreach ($this->calls as ['call' => $call, 'file' => $file, 'service' => $service]) { // The calls whose arguments do not depend on the parameters of a caller: the others run with their caller. - if (!isset($this->helpers[strtolower($call['callee'])]) || $this->usesParameters($call['args'])) { + if (!isset($this->helpers[strtolower($call->callee)]) || $call->usesParameters()) { continue; } - $this->run($call['callee'], $this->bind($call['callee'], $call['args'], [], $call['file']), $call['service'], 0); + $this->run($call->callee, $this->bind($call, [], $file), $service, 0); } return $this->result; @@ -76,39 +77,38 @@ private function run(string $callee, array $bindings, string $service, int $dept return; } - $value = fn (?array $template): ?string => $template === null ? null : $this->value($template, $bindings, $helper['file']); - foreach ($helper['facts'] as $fact) { - if ($fact['kind'] === 'set') { - $id = $value(\is_array($fact['id']) ? $fact['id'] : null); - $class = $value(\is_array($fact['class'] ?? null) ? $fact['class'] : null); + $value = fn (?Template $template): ?string => $template === null ? null : $this->value($template, $bindings, $helper['file']); + foreach ($helper['helper']->facts as $fact) { + if ($fact instanceof ServiceDefinitionFact) { + $id = $value($fact->id); + $class = $value($fact->class); if ($id !== null) { $this->result['definitions'][$service][$id] ??= $class ?? $id; } - } elseif ($fact['kind'] === 'tag' && \is_array($fact['definition'] ?? null)) { - [$kind, $template] = $fact['definition']; - $target = $value(\is_array($template) ? $template : null); - $name = $value(\is_array($fact['name']) ? $fact['name'] : null); + } elseif ($fact instanceof ServiceTagFact) { + $target = $value($fact->target); + $name = $value($fact->name); if ($target === null || $name === null) { continue; } - $this->result['tags'][$service][] = match ($kind) { - 'instanceof' => ['id' => null, 'instanceof' => ltrim($target, '\\'), 'name' => $name, 'attributes' => []], - 'load' => ['id' => rtrim(ltrim($target, '\\'), '\\') . '\\', 'instanceof' => null, 'name' => $name, 'attributes' => []], - default => ['id' => $target, 'instanceof' => null, 'name' => $name, 'attributes' => []], + $this->result['tags'][$service][] = match ($fact->kind) { + DefinitionKind::Instanceof => ['id' => null, 'instanceof' => ltrim($target, '\\'), 'name' => $name, 'attributes' => []], + DefinitionKind::Load => ['id' => rtrim(ltrim($target, '\\'), '\\') . '\\', 'instanceof' => null, 'name' => $name, 'attributes' => []], + DefinitionKind::Set => ['id' => $target, 'instanceof' => null, 'name' => $name, 'attributes' => []], }; - } elseif ($fact['kind'] === 'argument') { - $id = $value(\is_array($fact['id']) ? $fact['id'] : null); - $tag = $value(\is_array($fact['tag'] ?? null) ? $fact['tag'] : null); - $target = $value(\is_array($fact['service'] ?? null) ? $fact['service'] : null); + } else { + $id = $value($fact->id); + $tag = $value($fact->tag); + $target = $value($fact->service); if ($id !== null && ($tag !== null || $target !== null)) { - $this->result['arguments'][$service][] = ['id' => $id, 'tag' => $tag, 'service' => $target, 'locator' => ($fact['locator'] ?? false) === true]; + $this->result['arguments'][$service][] = ['id' => $id, 'tag' => $tag, 'service' => $target, 'locator' => $fact->locator]; } } } - foreach ($this->calls as $call) { - if ($call['caller'] !== null && strtolower($call['caller']) === strtolower($callee) && $this->usesParameters($call['args'])) { - $this->run($call['callee'], $this->bind($call['callee'], $call['args'], $bindings, $helper['file']), $service, $depth + 1); + foreach ($this->calls as ['call' => $call]) { + if ($call->caller !== null && strtolower($call->caller) === strtolower($callee) && $call->usesParameters()) { + $this->run($call->callee, $this->bind($call, $bindings, $helper['file']), $service, $depth + 1); } } } @@ -116,19 +116,18 @@ private function run(string $callee, array $bindings, string $service, int $dept /** * The values of a call's arguments, by the callee's parameter names: positional, then named. * - * @param list}> $args - * @param array $bindings the caller's + * @param array $bindings the caller's * * @return array */ - private function bind(string $callee, array $args, array $bindings, string $file): array + private function bind(PendingHelperCall $call, array $bindings, string $file): array { - $params = $this->helpers[strtolower($callee)]['params'] ?? []; + $params = $this->helpers[strtolower($call->callee)]['helper']->params ?? []; $bound = []; - foreach ($args as $position => [$name, $template]) { - $param = $name ?? ($params[$position] ?? null); + foreach ($call->arguments as $position => $argument) { + $param = $argument->name ?? ($params[$position] ?? null); if ($param !== null) { - $bound[$param] = $template === null ? null : $this->value($template, $bindings, $file); + $bound[$param] = $argument->value === null ? null : $this->value($argument->value, $bindings, $file); } } @@ -136,47 +135,31 @@ private function bind(string $callee, array $args, array $bindings, string $file } /** - * @param list}> $args - */ - private function usesParameters(array $args): bool - { - foreach ($args as [, $template]) { - if ($template !== null && str_contains(serialize($template), 's:5:"param"')) { - return true; - } - } - - return false; - } - - /** - * @param array $template * @param array $bindings */ - private function value(array $template, array $bindings, string $file): ?string + private function value(Template $template, array $bindings, string $file): ?string { - $parts = $template[1] ?? null; - - return match ($template[0] ?? null) { - 'lit' => \is_string($parts) ? $parts : null, - 'param' => \is_string($parts) ? $bindings[$parts] ?? null : null, - 'dir' => \dirname($file), - 'const' => \is_string($parts) ? ($this->constant)($parts, $file) : null, - 'concat' => \is_array($parts) ? $this->concat($parts, $bindings, $file) : null, - 'call' => \is_string($parts) && \is_array($template[2] ?? null) ? $this->call($parts, $template[2], $bindings, $file) : null, - default => null, + return match ($template->kind) { + TemplateKind::Literal => $template->value, + TemplateKind::Parameter => $bindings[$template->value] ?? null, + TemplateKind::Directory => \dirname($file), + TemplateKind::Constant => ($this->constant)($template->value, $file), + TemplateKind::Concat => $this->concat($template->parts, $bindings, $file), + TemplateKind::Call => $this->call($template->value, $template->parts, $bindings, $file), + // An array is the argument of a function only. + TemplateKind::List => null, }; } /** - * @param array $parts + * @param list