SingleLineCommentStyleFixer.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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\Comment;
  13. use PhpCsFixer\AbstractFixer;
  14. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  15. use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
  16. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  17. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
  18. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  19. use PhpCsFixer\FixerDefinition\CodeSample;
  20. use PhpCsFixer\FixerDefinition\FixerDefinition;
  21. use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
  22. use PhpCsFixer\Preg;
  23. use PhpCsFixer\Tokenizer\Token;
  24. use PhpCsFixer\Tokenizer\Tokens;
  25. /**
  26. * @author Filippo Tessarotto <zoeslam@gmail.com>
  27. */
  28. final class SingleLineCommentStyleFixer extends AbstractFixer implements ConfigurableFixerInterface
  29. {
  30. /**
  31. * @var bool
  32. */
  33. private $asteriskEnabled;
  34. /**
  35. * @var bool
  36. */
  37. private $hashEnabled;
  38. public function configure(array $configuration): void
  39. {
  40. parent::configure($configuration);
  41. $this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true);
  42. $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true);
  43. }
  44. public function getDefinition(): FixerDefinitionInterface
  45. {
  46. return new FixerDefinition(
  47. 'Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax.',
  48. [
  49. new CodeSample(
  50. '<?php
  51. /* asterisk comment */
  52. $a = 1;
  53. # hash comment
  54. $b = 2;
  55. /*
  56. * multi-line
  57. * comment
  58. */
  59. $c = 3;
  60. '
  61. ),
  62. new CodeSample(
  63. '<?php
  64. /* first comment */
  65. $a = 1;
  66. /*
  67. * second comment
  68. */
  69. $b = 2;
  70. /*
  71. * third
  72. * comment
  73. */
  74. $c = 3;
  75. ',
  76. ['comment_types' => ['asterisk']]
  77. ),
  78. new CodeSample(
  79. "<?php # comment\n",
  80. ['comment_types' => ['hash']]
  81. ),
  82. ]
  83. );
  84. }
  85. /**
  86. * {@inheritdoc}
  87. *
  88. * Must run after HeaderCommentFixer, NoUselessReturnFixer, PhpdocToCommentFixer.
  89. */
  90. public function getPriority(): int
  91. {
  92. return -31;
  93. }
  94. public function isCandidate(Tokens $tokens): bool
  95. {
  96. return $tokens->isTokenKindFound(T_COMMENT);
  97. }
  98. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
  99. {
  100. foreach ($tokens as $index => $token) {
  101. if (!$token->isGivenKind(T_COMMENT)) {
  102. continue;
  103. }
  104. $content = $token->getContent();
  105. /** @TODO PHP 8.0 - no more need for `?: ''` */
  106. $commentContent = substr($content, 2, -2) ?: ''; // @phpstan-ignore-line
  107. if ($this->hashEnabled && str_starts_with($content, '#')) {
  108. if (isset($content[1]) && '[' === $content[1]) {
  109. continue; // This might be an attribute on PHP8, do not change
  110. }
  111. $tokens[$index] = new Token([$token->getId(), '//'.substr($content, 1)]);
  112. continue;
  113. }
  114. if (
  115. !$this->asteriskEnabled
  116. || str_contains($commentContent, '?>')
  117. || !str_starts_with($content, '/*')
  118. || Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent)
  119. ) {
  120. continue;
  121. }
  122. $nextTokenIndex = $index + 1;
  123. if (isset($tokens[$nextTokenIndex])) {
  124. $nextToken = $tokens[$nextTokenIndex];
  125. if (!$nextToken->isWhitespace() || !Preg::match('/\R/', $nextToken->getContent())) {
  126. continue;
  127. }
  128. $tokens[$nextTokenIndex] = new Token([$nextToken->getId(), ltrim($nextToken->getContent(), " \t")]);
  129. }
  130. $content = '//';
  131. if (Preg::match('/[^\s\*]/', $commentContent)) {
  132. $content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent);
  133. }
  134. $tokens[$index] = new Token([$token->getId(), $content]);
  135. }
  136. }
  137. protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
  138. {
  139. return new FixerConfigurationResolver([
  140. (new FixerOptionBuilder('comment_types', 'List of comment types to fix.'))
  141. ->setAllowedTypes(['string[]'])
  142. ->setAllowedValues([new AllowedValueSubset(['asterisk', 'hash'])])
  143. ->setDefault(['asterisk', 'hash'])
  144. ->getOption(),
  145. ]);
  146. }
  147. }