From ef58367eee9ebc8f35c173bac4c1782be664b37b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 8 Nov 2026 14:06:26 +0700 Subject: [PATCH 1/3] refactor: Read class-like docblock once for @api and @internal tags --- src/Analyser/AnalysisNodeCollector.php | 42 +++++++++++++++++--------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Analyser/AnalysisNodeCollector.php b/src/Analyser/AnalysisNodeCollector.php index 24daeefb..3b5c2994 100644 --- a/src/Analyser/AnalysisNodeCollector.php +++ b/src/Analyser/AnalysisNodeCollector.php @@ -97,6 +97,7 @@ use function is_int; use function is_string; use function preg_match; +use function sprintf; use function str_contains; use function str_starts_with; use function strcasecmp; @@ -146,6 +147,11 @@ final class AnalysisNodeCollector extends NodeVisitorAbstract */ private const METHOD_NAME_PATTERN = '/^[A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*+\z/'; + /** + * A docblock tag at the start of a line, e.g. `@api`; %s is the tag name. + */ + private const DOC_BLOCK_TAG_PATTERN = '#^[/*\s]*@%s(?![\w-])#m'; + /** * Marks the name string passed to function_exists(): the check probes for * a function, it does not use it, so the string is not a reference. @@ -1831,11 +1837,12 @@ private function addLanguageConstruct(string $languageConstruct): void private function collectClassLike(ClassLike $classLike, ClassLikeAnalysis $classLikeAnalysis): void { - $analysis = $this->collectClassLikeAnalysis($classLikeAnalysis); - $className = $this->resolveClassName($classLike); - [$layer, $layers] = $this->resolveLayerData($className); - $implements = $this->collectImplements($classLike); - $interfaceExtends = $this->collectInterfaceExtends($classLike); + $analysis = $this->collectClassLikeAnalysis($classLikeAnalysis); + $className = $this->resolveClassName($classLike); + [$layer, $layers] = $this->resolveLayerData($className); + $implements = $this->collectImplements($classLike); + $interfaceExtends = $this->collectInterfaceExtends($classLike); + [$isApi, $isInternal] = $this->collectApiAndInternalTags($classLike); $this->classNodes[] = new ClassNode( className: $className, @@ -1868,8 +1875,8 @@ enumBackingType: $classLike instanceof Enum_ && $classLike->scalarType instan : null, nonClassDependencies: $analysis['nonClassDependencies'], constantFetches: $analysis['constantFetches'], - isApi: $this->hasDocBlockTag($classLike, 'api'), - isInternal: $this->hasDocBlockTag($classLike, 'internal'), + isApi: $isApi, + isInternal: $isInternal, signatures: $analysis['signatures'], ); } @@ -2012,17 +2019,24 @@ private function resolveClassName(ClassLike $classLike): string } /** - * Whether the docblock carries the $tag tag, e.g. `@api`. A tag starts a - * docblock line, so the tag name mentioned in the description, a longer - * tag such as `@api-version`, or an inline `{@internal ...}` comment does - * not count. + * Whether the docblock carries the `@api` and `@internal` tags. + * + * @return array{bool, bool} [isApi, isInternal] */ - private function hasDocBlockTag(ClassLike $classLike, string $tag): bool + private function collectApiAndInternalTags(ClassLike $classLike): array { $docComment = $classLike->getDocComment(); - return $docComment instanceof Doc - && preg_match('#^[/*\s]*@' . $tag . '(?![\w-])#m', $docComment->getText()) === 1; + if (! $docComment instanceof Doc) { + return [false, false]; + } + + $text = $docComment->getText(); + + return [ + preg_match(sprintf(self::DOC_BLOCK_TAG_PATTERN, 'api'), $text) === 1, + preg_match(sprintf(self::DOC_BLOCK_TAG_PATTERN, 'internal'), $text) === 1, + ]; } /** From d34c58d898a19f41b5d399838bd61b723af65393 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 8 Nov 2026 14:22:24 +0700 Subject: [PATCH 2/3] rename method --- src/Analyser/AnalysisNodeCollector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyser/AnalysisNodeCollector.php b/src/Analyser/AnalysisNodeCollector.php index 3b5c2994..833b1456 100644 --- a/src/Analyser/AnalysisNodeCollector.php +++ b/src/Analyser/AnalysisNodeCollector.php @@ -1842,7 +1842,7 @@ private function collectClassLike(ClassLike $classLike, ClassLikeAnalysis $class [$layer, $layers] = $this->resolveLayerData($className); $implements = $this->collectImplements($classLike); $interfaceExtends = $this->collectInterfaceExtends($classLike); - [$isApi, $isInternal] = $this->collectApiAndInternalTags($classLike); + [$isApi, $isInternal] = $this->detectApiAndInternalTags($classLike); $this->classNodes[] = new ClassNode( className: $className, @@ -2023,7 +2023,7 @@ private function resolveClassName(ClassLike $classLike): string * * @return array{bool, bool} [isApi, isInternal] */ - private function collectApiAndInternalTags(ClassLike $classLike): array + private function detectApiAndInternalTags(ClassLike $classLike): array { $docComment = $classLike->getDocComment(); From 1683676ca7e67910883bbf9a3db980d54e9d423d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 8 Nov 2026 14:23:57 +0700 Subject: [PATCH 3/3] use more generic name --- src/Analyser/AnalysisNodeCollector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyser/AnalysisNodeCollector.php b/src/Analyser/AnalysisNodeCollector.php index 833b1456..bdf1a832 100644 --- a/src/Analyser/AnalysisNodeCollector.php +++ b/src/Analyser/AnalysisNodeCollector.php @@ -1842,7 +1842,7 @@ private function collectClassLike(ClassLike $classLike, ClassLikeAnalysis $class [$layer, $layers] = $this->resolveLayerData($className); $implements = $this->collectImplements($classLike); $interfaceExtends = $this->collectInterfaceExtends($classLike); - [$isApi, $isInternal] = $this->detectApiAndInternalTags($classLike); + [$isApi, $isInternal] = $this->detectDocBlockTags($classLike); $this->classNodes[] = new ClassNode( className: $className, @@ -2023,7 +2023,7 @@ private function resolveClassName(ClassLike $classLike): string * * @return array{bool, bool} [isApi, isInternal] */ - private function detectApiAndInternalTags(ClassLike $classLike): array + private function detectDocBlockTags(ClassLike $classLike): array { $docComment = $classLike->getDocComment();