PhpdocTrimFixer.php 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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\Fixer\Phpdoc;
  13. use PhpCsFixer\AbstractFixer;
  14. use PhpCsFixer\FixerDefinition\CodeSample;
  15. use PhpCsFixer\FixerDefinition\FixerDefinition;
  16. use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
  17. use PhpCsFixer\Preg;
  18. use PhpCsFixer\Tokenizer\Token;
  19. use PhpCsFixer\Tokenizer\Tokens;
  20. /**
  21. * @author Graham Campbell <hello@gjcampbell.co.uk>
  22. */
  23. final class PhpdocTrimFixer extends AbstractFixer
  24. {
  25. public function getDefinition(): FixerDefinitionInterface
  26. {
  27. return new FixerDefinition(
  28. 'PHPDoc should start and end with content, excluding the very first and last line of the docblocks.',
  29. [new CodeSample('<?php
  30. /**
  31. *
  32. * Foo must be final class.
  33. *
  34. *
  35. */
  36. final class Foo {}
  37. ')]
  38. );
  39. }
  40. /**
  41. * {@inheritdoc}
  42. *
  43. * Must run before PhpdocAlignFixer.
  44. * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpUnitAttributesFixer, PhpUnitTestAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
  45. */
  46. public function getPriority(): int
  47. {
  48. return -5;
  49. }
  50. public function isCandidate(Tokens $tokens): bool
  51. {
  52. return $tokens->isTokenKindFound(T_DOC_COMMENT);
  53. }
  54. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
  55. {
  56. foreach ($tokens as $index => $token) {
  57. if (!$token->isGivenKind(T_DOC_COMMENT)) {
  58. continue;
  59. }
  60. $content = $token->getContent();
  61. $content = $this->fixStart($content);
  62. // we need re-parse the docblock after fixing the start before
  63. // fixing the end in order for the lines to be correctly indexed
  64. $content = $this->fixEnd($content);
  65. $tokens[$index] = new Token([T_DOC_COMMENT, $content]);
  66. }
  67. }
  68. /**
  69. * Make sure the first useful line starts immediately after the first line.
  70. */
  71. private function fixStart(string $content): string
  72. {
  73. return Preg::replace(
  74. '~
  75. (^/\*\*) # DocComment begin
  76. (?:
  77. \R\h*(?:\*\h*)? # lines without useful content
  78. (?!\R\h*\*/) # not followed by a DocComment end
  79. )+
  80. (\R\h*(?:\*\h*)?\S) # first line with useful content
  81. ~x',
  82. '$1$2',
  83. $content
  84. );
  85. }
  86. /**
  87. * Make sure the last useful line is immediately before the final line.
  88. */
  89. private function fixEnd(string $content): string
  90. {
  91. return Preg::replace(
  92. '~
  93. (\R\h*(?:\*\h*)?\S.*?) # last line with useful content
  94. (?:
  95. (?<!/\*\*) # not preceded by a DocComment start
  96. \R\h*(?:\*\h*)? # lines without useful content
  97. )+
  98. (\R\h*\*/$) # DocComment end
  99. ~xu',
  100. '$1$2',
  101. $content
  102. );
  103. }
  104. }