|null */ public ?array $annotations = null; /** @var array|null */ private ?array $normalizedAnnotations = null; /** * @return array */ public function register(): array { return [ T_DOC_COMMENT_OPEN_TAG, ]; } /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @param int $docCommentOpenPointer */ public function process(File $phpcsFile, $docCommentOpenPointer): void { $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); $correctAnnotationNames = $this->getNormalizedAnnotationNames(); foreach ($annotations as $annotation) { $lowerCasedAnnotationName = strtolower($annotation->getName()); if (!array_key_exists($lowerCasedAnnotationName, $correctAnnotationNames)) { continue; } $correctAnnotationName = $correctAnnotationNames[$lowerCasedAnnotationName]; if ($correctAnnotationName === $annotation->getName()) { continue; } $annotationNameWithoutAtSign = ltrim($annotation->getName(), '@'); $fullyQualifiedAnnotationName = NamespaceHelper::resolveClassName( $phpcsFile, $annotationNameWithoutAtSign, $annotation->getStartPointer(), ); if (NamespaceHelper::normalizeToCanonicalName($fullyQualifiedAnnotationName) !== $annotationNameWithoutAtSign) { continue; } $fix = $phpcsFile->addFixableError( sprintf('Annotation name is incorrect. Expected %s, found %s.', $correctAnnotationName, $annotation->getName()), $annotation->getStartPointer(), self::CODE_ANNOTATION_NAME_INCORRECT, ); if (!$fix) { continue; } $phpcsFile->fixer->beginChangeset(); FixerHelper::replace($phpcsFile, $annotation->getStartPointer(), $correctAnnotationName); $phpcsFile->fixer->endChangeset(); } $tokens = $phpcsFile->getTokens(); $docCommentContent = TokenHelper::getContent($phpcsFile, $docCommentOpenPointer, $tokens[$docCommentOpenPointer]['comment_closer']); if (preg_match_all( '~\{(' . implode('|', $correctAnnotationNames) . ')\}~i', $docCommentContent, $matches, PREG_OFFSET_CAPTURE, ) === 0) { return; } foreach ($matches[1] as $match) { $correctAnnotationName = $correctAnnotationNames[strtolower($match[0])]; if ($correctAnnotationName === $match[0]) { continue; } $fix = $phpcsFile->addFixableError( sprintf('Annotation name is incorrect. Expected %s, found %s.', $correctAnnotationName, $match[0]), $docCommentOpenPointer, self::CODE_ANNOTATION_NAME_INCORRECT, ); if (!$fix) { continue; } $phpcsFile->fixer->beginChangeset(); $fixedDocCommentContent = substr($docCommentContent, 0, $match[1]) . $correctAnnotationName . substr( $docCommentContent, $match[1] + strlen($match[0]), ); FixerHelper::change( $phpcsFile, $docCommentOpenPointer, $tokens[$docCommentOpenPointer]['comment_closer'], $fixedDocCommentContent, ); $phpcsFile->fixer->endChangeset(); } } /** * @return array */ private function getNormalizedAnnotationNames(): array { if ($this->normalizedAnnotations !== null) { return $this->normalizedAnnotations; } if ($this->annotations !== null) { $annotationNames = array_map( static fn (string $annotationName): string => ltrim($annotationName, '@'), SniffSettingsHelper::normalizeArray($this->annotations), ); } else { $annotationNames = [...self::STANDARD_ANNOTATIONS, ...self::PHPUNIT_ANNOTATIONS, ...self::STATIC_ANALYSIS_ANNOTATIONS]; foreach (self::STATIC_ANALYSIS_ANNOTATIONS as $annotationName) { if (strpos($annotationName, 'psalm') === 0) { continue; } foreach (AnnotationHelper::STATIC_ANALYSIS_PREFIXES as $prefix) { $annotationNames[] = sprintf('%s-%s', $prefix, $annotationName); } } } $annotationNames = array_map(static fn (string $annotationName): string => '@' . $annotationName, array_unique($annotationNames)); $this->normalizedAnnotations = array_combine( array_map(static fn (string $annotationName): string => strtolower($annotationName), $annotationNames), $annotationNames, ); return $this->normalizedAnnotations; } }