diff --git a/doc/list.rst b/doc/list.rst index 86bad617b3d..eceead31b49 100644 --- a/doc/list.rst +++ b/doc/list.rst @@ -2625,6 +2625,13 @@ List of Available Rules Part of rule sets `@PSR12 <./ruleSets/PSR12.rst>`_ `@PSR2 <./ruleSets/PSR2.rst>`_ `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_ `Source PhpCsFixer\\Fixer\\Import\\SingleLineAfterImportsFixer <./../src/Fixer/Import/SingleLineAfterImportsFixer.php>`_ +- `single_line_comment_spacing <./rules/comment/single_line_comment_spacing.rst>`_ + + Single-line comments must have proper spacing. + + Part of rule sets `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_ + + `Source PhpCsFixer\\Fixer\\Comment\\SingleLineCommentSpacingFixer <./../src/Fixer/Comment/SingleLineCommentSpacingFixer.php>`_ - `single_line_comment_style <./rules/comment/single_line_comment_style.rst>`_ Single-line comments and multi-line comments with only one line of actual content should use the ``//`` syntax. diff --git a/doc/ruleSets/Symfony.rst b/doc/ruleSets/Symfony.rst index 95972acde2d..e762b31ad94 100644 --- a/doc/ruleSets/Symfony.rst +++ b/doc/ruleSets/Symfony.rst @@ -112,6 +112,7 @@ Rules - `protected_to_private <./../rules/class_notation/protected_to_private.rst>`_ - `semicolon_after_instruction <./../rules/semicolon/semicolon_after_instruction.rst>`_ - `single_class_element_per_statement <./../rules/class_notation/single_class_element_per_statement.rst>`_ +- `single_line_comment_spacing <./../rules/comment/single_line_comment_spacing.rst>`_ - `single_line_comment_style <./../rules/comment/single_line_comment_style.rst>`_ config: ``['comment_types' => ['hash']]`` diff --git a/doc/rules/comment/single_line_comment_spacing.rst b/doc/rules/comment/single_line_comment_spacing.rst new file mode 100644 index 00000000000..bace3420eb4 --- /dev/null +++ b/doc/rules/comment/single_line_comment_spacing.rst @@ -0,0 +1,34 @@ +==================================== +Rule ``single_line_comment_spacing`` +==================================== + +Single-line comments must have proper spacing. + +Examples +-------- + +Example #1 +~~~~~~~~~~ + +.. code-block:: diff + + --- Original + +++ New + `_ rule set will enable the ``single_line_comment_spacing`` rule. + +@Symfony + Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``single_line_comment_spacing`` rule. diff --git a/doc/rules/index.rst b/doc/rules/index.rst index dc5d99839e5..df4cc8407fc 100644 --- a/doc/rules/index.rst +++ b/doc/rules/index.rst @@ -219,6 +219,9 @@ Comment - `no_trailing_whitespace_in_comment <./comment/no_trailing_whitespace_in_comment.rst>`_ There MUST be no trailing spaces inside comment or PHPDoc. +- `single_line_comment_spacing <./comment/single_line_comment_spacing.rst>`_ + + Single-line comments must have proper spacing. - `single_line_comment_style <./comment/single_line_comment_style.rst>`_ Single-line comments and multi-line comments with only one line of actual content should use the ``//`` syntax. diff --git a/src/Fixer/Comment/SingleLineCommentSpacingFixer.php b/src/Fixer/Comment/SingleLineCommentSpacingFixer.php new file mode 100644 index 00000000000..490ee88f922 --- /dev/null +++ b/src/Fixer/Comment/SingleLineCommentSpacingFixer.php @@ -0,0 +1,119 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SingleLineCommentSpacingFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Single-line comments must have proper spacing.', + [ + new CodeSample( + 'isTokenKindFound(T_COMMENT); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $content = $token->getContent(); + $contentLength = \strlen($content); + + if ('/' === $content[0]) { + if ($contentLength < 3) { + continue; // cheap check for "//" + } + + if ('*' === $content[1]) { // slash asterisk comment + if ($contentLength < 5 || '*' === $content[2] || str_contains($content, "\n")) { + continue; // cheap check for "/**/", comment that looks like a PHPDoc, or multi line comment + } + + $newContent = rtrim(substr($content, 0, -2)).' '.substr($content, -2); + $newContent = $this->fixCommentLeadingSpace($newContent, '/*'); + } else { // double slash comment + $newContent = $this->fixCommentLeadingSpace($content, '//'); + } + } else { // hash comment + if ($contentLength < 2 || '[' === $content[1]) { // cheap check for "#" or annotation (like) comment + continue; + } + + $newContent = $this->fixCommentLeadingSpace($content, '#'); + } + + if ($newContent !== $content) { + $tokens[$index] = new Token([T_COMMENT, $newContent]); + } + } + } + + // fix space between comment open and leading text + private function fixCommentLeadingSpace(string $content, string $prefix): string + { + if (0 !== Preg::match(sprintf('@^%s\h+.*$@', preg_quote($prefix, '@')), $content)) { + return $content; + } + + $position = \strlen($prefix); + + return substr($content, 0, $position).' '.substr($content, $position); + } +} diff --git a/src/Fixer/Phpdoc/PhpdocToCommentFixer.php b/src/Fixer/Phpdoc/PhpdocToCommentFixer.php index ecc9ed65629..4475443e798 100644 --- a/src/Fixer/Phpdoc/PhpdocToCommentFixer.php +++ b/src/Fixer/Phpdoc/PhpdocToCommentFixer.php @@ -49,7 +49,7 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} * - * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer, SingleLineCommentStyleFixer. + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer, SingleLineCommentSpacingFixer, SingleLineCommentStyleFixer. * Must run after CommentToPhpdocFixer. */ public function getPriority(): int diff --git a/src/RuleSet/Sets/SymfonySet.php b/src/RuleSet/Sets/SymfonySet.php index 051ad957689..2d357c37bcf 100644 --- a/src/RuleSet/Sets/SymfonySet.php +++ b/src/RuleSet/Sets/SymfonySet.php @@ -158,6 +158,7 @@ public function getRules(): array 'protected_to_private' => true, 'semicolon_after_instruction' => true, 'single_class_element_per_statement' => true, + 'single_line_comment_spacing' => true, 'single_line_comment_style' => [ 'comment_types' => [ 'hash', diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php index 601c7324346..0fa28369148 100644 --- a/tests/AutoReview/FixerFactoryTest.php +++ b/tests/AutoReview/FixerFactoryTest.php @@ -692,6 +692,7 @@ private static function getFixersPriorityGraph(): array 'phpdoc_to_comment' => [ 'no_empty_comment', 'phpdoc_no_useless_inheritdoc', + 'single_line_comment_spacing', 'single_line_comment_style', ], 'phpdoc_to_param_type' => [ diff --git a/tests/Fixer/Comment/SingleLineCommentSpacingFixerTest.php b/tests/Fixer/Comment/SingleLineCommentSpacingFixerTest.php new file mode 100644 index 00000000000..9be56c167bf --- /dev/null +++ b/tests/Fixer/Comment/SingleLineCommentSpacingFixerTest.php @@ -0,0 +1,144 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Fixer\Comment; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @internal + * + * @covers \PhpCsFixer\Fixer\Comment\SingleLineCommentSpacingFixer + */ +final class SingleLineCommentSpacingFixerTest extends AbstractFixerTestCase +{ + /** + * @dataProvider provideTestCases + */ + public function testFix(string $expected, ?string $input = null): void + { + $this->doTest($expected, $input); + } + + public function provideTestCases(): iterable + { + yield 'comment list' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + " [ + ' [ + 'getMessage(), $file->getRelativePathname()), + sprintf('%s Test file: "%s".', $e->getMessage(), $file->getPathname()), $e->getCode(), $e );