AbstractDoctrineAnnotationFixer.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer;
  13. use PhpCsFixer\Doctrine\Annotation\Tokens as DoctrineAnnotationTokens;
  14. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  15. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  16. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
  17. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  18. use PhpCsFixer\Tokenizer\CT;
  19. use PhpCsFixer\Tokenizer\Token;
  20. use PhpCsFixer\Tokenizer\Tokens;
  21. use PhpCsFixer\Tokenizer\TokensAnalyzer;
  22. /**
  23. * @internal
  24. */
  25. abstract class AbstractDoctrineAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface
  26. {
  27. /**
  28. * @var array<int, array{classIndex: int, token: Token, type: string}>
  29. */
  30. private array $classyElements;
  31. public function isCandidate(Tokens $tokens): bool
  32. {
  33. return $tokens->isTokenKindFound(T_DOC_COMMENT);
  34. }
  35. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
  36. {
  37. // fetch indices one time, this is safe as we never add or remove a token during fixing
  38. $analyzer = new TokensAnalyzer($tokens);
  39. $this->classyElements = $analyzer->getClassyElements();
  40. /** @var Token $docCommentToken */
  41. foreach ($tokens->findGivenKind(T_DOC_COMMENT) as $index => $docCommentToken) {
  42. if (!$this->nextElementAcceptsDoctrineAnnotations($tokens, $index)) {
  43. continue;
  44. }
  45. $doctrineAnnotationTokens = DoctrineAnnotationTokens::createFromDocComment(
  46. $docCommentToken,
  47. $this->configuration['ignored_tags']
  48. );
  49. $this->fixAnnotations($doctrineAnnotationTokens);
  50. $tokens[$index] = new Token([T_DOC_COMMENT, $doctrineAnnotationTokens->getCode()]);
  51. }
  52. }
  53. /**
  54. * Fixes Doctrine annotations from the given PHPDoc style comment.
  55. */
  56. abstract protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens): void;
  57. protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
  58. {
  59. return new FixerConfigurationResolver([
  60. (new FixerOptionBuilder('ignored_tags', 'List of tags that must not be treated as Doctrine Annotations.'))
  61. ->setAllowedTypes(['string[]'])
  62. ->setAllowedValues([static function (array $values): bool {
  63. foreach ($values as $value) {
  64. if (!\is_string($value)) {
  65. return false;
  66. }
  67. }
  68. return true;
  69. }])
  70. ->setDefault([
  71. // PHPDocumentor 1
  72. 'abstract',
  73. 'access',
  74. 'code',
  75. 'deprec',
  76. 'encode',
  77. 'exception',
  78. 'final',
  79. 'ingroup',
  80. 'inheritdoc',
  81. 'inheritDoc',
  82. 'magic',
  83. 'name',
  84. 'toc',
  85. 'tutorial',
  86. 'private',
  87. 'static',
  88. 'staticvar',
  89. 'staticVar',
  90. 'throw',
  91. // PHPDocumentor 2
  92. 'api',
  93. 'author',
  94. 'category',
  95. 'copyright',
  96. 'deprecated',
  97. 'example',
  98. 'filesource',
  99. 'global',
  100. 'ignore',
  101. 'internal',
  102. 'license',
  103. 'link',
  104. 'method',
  105. 'package',
  106. 'param',
  107. 'property',
  108. 'property-read',
  109. 'property-write',
  110. 'return',
  111. 'see',
  112. 'since',
  113. 'source',
  114. 'subpackage',
  115. 'throws',
  116. 'todo',
  117. 'TODO',
  118. 'usedBy',
  119. 'uses',
  120. 'var',
  121. 'version',
  122. // PHPUnit
  123. 'after',
  124. 'afterClass',
  125. 'backupGlobals',
  126. 'backupStaticAttributes',
  127. 'before',
  128. 'beforeClass',
  129. 'codeCoverageIgnore',
  130. 'codeCoverageIgnoreStart',
  131. 'codeCoverageIgnoreEnd',
  132. 'covers',
  133. 'coversDefaultClass',
  134. 'coversNothing',
  135. 'dataProvider',
  136. 'depends',
  137. 'expectedException',
  138. 'expectedExceptionCode',
  139. 'expectedExceptionMessage',
  140. 'expectedExceptionMessageRegExp',
  141. 'group',
  142. 'large',
  143. 'medium',
  144. 'preserveGlobalState',
  145. 'requires',
  146. 'runTestsInSeparateProcesses',
  147. 'runInSeparateProcess',
  148. 'small',
  149. 'test',
  150. 'testdox',
  151. 'ticket',
  152. 'uses',
  153. // PHPCheckStyle
  154. 'SuppressWarnings',
  155. // PHPStorm
  156. 'noinspection',
  157. // PEAR
  158. 'package_version',
  159. // PlantUML
  160. 'enduml',
  161. 'startuml',
  162. // Psalm
  163. 'psalm',
  164. // PHPStan
  165. 'phpstan',
  166. 'template',
  167. // other
  168. 'fix',
  169. 'FIXME',
  170. 'fixme',
  171. 'override',
  172. ])
  173. ->getOption(),
  174. ]);
  175. }
  176. private function nextElementAcceptsDoctrineAnnotations(Tokens $tokens, int $index): bool
  177. {
  178. $classModifiers = [T_ABSTRACT, T_FINAL];
  179. if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required
  180. $classModifiers[] = T_READONLY;
  181. }
  182. do {
  183. $index = $tokens->getNextMeaningfulToken($index);
  184. if (null === $index) {
  185. return false;
  186. }
  187. } while ($tokens[$index]->isGivenKind($classModifiers));
  188. if ($tokens[$index]->isGivenKind(T_CLASS)) {
  189. return true;
  190. }
  191. $modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_NS_SEPARATOR, T_STRING, CT::T_NULLABLE_TYPE];
  192. if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
  193. $modifierKinds[] = T_READONLY;
  194. }
  195. while ($tokens[$index]->isGivenKind($modifierKinds)) {
  196. $index = $tokens->getNextMeaningfulToken($index);
  197. }
  198. if (!isset($this->classyElements[$index])) {
  199. return false;
  200. }
  201. return $tokens[$this->classyElements[$index]['classIndex']]->isGivenKind(T_CLASS); // interface, enums and traits cannot have doctrine annotations
  202. }
  203. }