Missing dependancies

This commit is contained in:
tokslaw7
2023-06-21 12:19:22 +00:00
parent fbf3f180c2
commit 421f25c80d
2356 changed files with 342670 additions and 4743 deletions
@@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Analyzer\CommentsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Utils;
/**
* @author Kuba Werłos <werlos@gmail.com>
*/
final class CommentToPhpdocFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
{
/**
* @var string[]
*/
private array $ignoredTags = [];
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_COMMENT);
}
/**
* {@inheritdoc}
*/
public function isRisky(): bool
{
return true;
}
/**
* {@inheritdoc}
*
* Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
* Must run after AlignMultilineCommentFixer.
*/
public function getPriority(): int
{
// Should be run before all other PHPDoc fixers
return 26;
}
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Comments with annotation should be docblock when used on structural elements.',
[
new CodeSample("<?php /* header */ \$x = true; /* @var bool \$isFoo */ \$isFoo = true;\n"),
new CodeSample("<?php\n// @todo do something later\n\$foo = 1;\n\n// @var int \$a\n\$a = foo();\n", ['ignored_tags' => ['todo']]),
],
null,
'Risky as new docblocks might mean more, e.g. a Doctrine entity might have a new column in database.'
);
}
/**
* {@inheritdoc}
*/
public function configure(array $configuration): void
{
parent::configure($configuration);
$this->ignoredTags = array_map(
static function (string $tag): string {
return strtolower($tag);
},
$this->configuration['ignored_tags']
);
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('ignored_tags', 'List of ignored tags'))
->setAllowedTypes(['array'])
->setDefault([])
->getOption(),
]);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$commentsAnalyzer = new CommentsAnalyzer();
for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) {
$token = $tokens[$index];
if (!$token->isGivenKind(T_COMMENT)) {
continue;
}
if ($commentsAnalyzer->isHeaderComment($tokens, $index)) {
continue;
}
if (!$commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) {
continue;
}
$commentIndices = $commentsAnalyzer->getCommentBlockIndices($tokens, $index);
if ($this->isCommentCandidate($tokens, $commentIndices)) {
$this->fixComment($tokens, $commentIndices);
}
$index = max($commentIndices);
}
}
/**
* @param int[] $indices
*/
private function isCommentCandidate(Tokens $tokens, array $indices): bool
{
return array_reduce(
$indices,
function (bool $carry, int $index) use ($tokens): bool {
if ($carry) {
return true;
}
if (1 !== Preg::match('~(?:#|//|/\*+|\R(?:\s*\*)?)\s*\@([a-zA-Z0-9_\\\\-]+)(?=\s|\(|$)~', $tokens[$index]->getContent(), $matches)) {
return false;
}
return !\in_array(strtolower($matches[1]), $this->ignoredTags, true);
},
false
);
}
/**
* @param int[] $indices
*/
private function fixComment(Tokens $tokens, array $indices): void
{
if (1 === \count($indices)) {
$this->fixCommentSingleLine($tokens, reset($indices));
} else {
$this->fixCommentMultiLine($tokens, $indices);
}
}
private function fixCommentSingleLine(Tokens $tokens, int $index): void
{
$message = $this->getMessage($tokens[$index]->getContent());
if ('' !== trim(substr($message, 0, 1))) {
$message = ' '.$message;
}
if ('' !== trim(substr($message, -1))) {
$message .= ' ';
}
$tokens[$index] = new Token([T_DOC_COMMENT, '/**'.$message.'*/']);
}
/**
* @param int[] $indices
*/
private function fixCommentMultiLine(Tokens $tokens, array $indices): void
{
$startIndex = reset($indices);
$indent = Utils::calculateTrailingWhitespaceIndent($tokens[$startIndex - 1]);
$newContent = '/**'.$this->whitespacesConfig->getLineEnding();
$count = max($indices);
for ($index = $startIndex; $index <= $count; ++$index) {
if (!$tokens[$index]->isComment()) {
continue;
}
if (str_contains($tokens[$index]->getContent(), '*/')) {
return;
}
$message = $this->getMessage($tokens[$index]->getContent());
if ('' !== trim(substr($message, 0, 1))) {
$message = ' '.$message;
}
$newContent .= $indent.' *'.$message.$this->whitespacesConfig->getLineEnding();
}
for ($index = $startIndex; $index <= $count; ++$index) {
$tokens->clearAt($index);
}
$newContent .= $indent.' */';
$tokens->insertAt($startIndex, new Token([T_DOC_COMMENT, $newContent]));
}
private function getMessage(string $content): string
{
if (str_starts_with($content, '#')) {
return substr($content, 1);
}
if (str_starts_with($content, '//')) {
return substr($content, 2);
}
return rtrim(ltrim($content, '/*'), '*/');
}
}
@@ -0,0 +1,454 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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\ConfigurationException\InvalidFixerConfigurationException;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use Symfony\Component\OptionsResolver\Options;
/**
* @author Antonio J. García Lagar <aj@garcialagar.es>
*/
final class HeaderCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
{
/**
* @internal
*/
public const HEADER_PHPDOC = 'PHPDoc';
/**
* @internal
*/
public const HEADER_COMMENT = 'comment';
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Add, replace or remove header comment.',
[
new CodeSample(
'<?php
declare(strict_types=1);
namespace A\B;
echo 1;
',
[
'header' => 'Made with love.',
]
),
new CodeSample(
'<?php
declare(strict_types=1);
namespace A\B;
echo 1;
',
[
'header' => 'Made with love.',
'comment_type' => 'PHPDoc',
'location' => 'after_open',
'separate' => 'bottom',
]
),
new CodeSample(
'<?php
declare(strict_types=1);
namespace A\B;
echo 1;
',
[
'header' => 'Made with love.',
'comment_type' => 'comment',
'location' => 'after_declare_strict',
]
),
new CodeSample(
'<?php
declare(strict_types=1);
/*
* Comment is not wanted here.
*/
namespace A\B;
echo 1;
',
[
'header' => '',
]
),
]
);
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isMonolithicPhp();
}
/**
* {@inheritdoc}
*
* Must run before SingleLineCommentStyleFixer.
* Must run after DeclareStrictTypesFixer, NoBlankLinesAfterPhpdocFixer.
*/
public function getPriority(): int
{
// When this fixer is configured with ["separate" => "bottom", "comment_type" => "PHPDoc"]
// and the target file has no namespace or declare() construct,
// the fixed header comment gets trimmed by NoBlankLinesAfterPhpdocFixer if we run before it.
return -30;
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$location = $this->configuration['location'];
$locationIndices = [];
foreach (['after_open', 'after_declare_strict'] as $possibleLocation) {
$locationIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation);
if (!isset($locationIndices[$locationIndex]) || $possibleLocation === $location) {
$locationIndices[$locationIndex] = $possibleLocation;
}
}
foreach ($locationIndices as $possibleLocation) {
// figure out where the comment should be placed
$headerNewIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation);
// check if there is already a comment
$headerCurrentIndex = $this->findHeaderCommentCurrentIndex($tokens, $headerNewIndex - 1);
if (null === $headerCurrentIndex) {
if ('' === $this->configuration['header'] || $possibleLocation !== $location) {
continue;
}
$this->insertHeader($tokens, $headerNewIndex);
continue;
}
$sameComment = $this->getHeaderAsComment() === $tokens[$headerCurrentIndex]->getContent();
$expectedLocation = $possibleLocation === $location;
if (!$sameComment || !$expectedLocation) {
if ($expectedLocation ^ $sameComment) {
$this->removeHeader($tokens, $headerCurrentIndex);
}
if ('' === $this->configuration['header']) {
continue;
}
if ($possibleLocation === $location) {
$this->insertHeader($tokens, $headerNewIndex);
}
continue;
}
$this->fixWhiteSpaceAroundHeader($tokens, $headerCurrentIndex);
}
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
$fixerName = $this->getName();
return new FixerConfigurationResolver([
(new FixerOptionBuilder('header', 'Proper header content.'))
->setAllowedTypes(['string'])
->setNormalizer(static function (Options $options, string $value) use ($fixerName): string {
if ('' === trim($value)) {
return '';
}
if (str_contains($value, '*/')) {
throw new InvalidFixerConfigurationException($fixerName, 'Cannot use \'*/\' in header.');
}
return $value;
})
->getOption(),
(new FixerOptionBuilder('comment_type', 'Comment syntax type.'))
->setAllowedValues([self::HEADER_PHPDOC, self::HEADER_COMMENT])
->setDefault(self::HEADER_COMMENT)
->getOption(),
(new FixerOptionBuilder('location', 'The location of the inserted header.'))
->setAllowedValues(['after_open', 'after_declare_strict'])
->setDefault('after_declare_strict')
->getOption(),
(new FixerOptionBuilder('separate', 'Whether the header should be separated from the file content with a new line.'))
->setAllowedValues(['both', 'top', 'bottom', 'none'])
->setDefault('both')
->getOption(),
]);
}
/**
* Enclose the given text in a comment block.
*/
private function getHeaderAsComment(): string
{
$lineEnding = $this->whitespacesConfig->getLineEnding();
$comment = (self::HEADER_COMMENT === $this->configuration['comment_type'] ? '/*' : '/**').$lineEnding;
$lines = explode("\n", str_replace("\r", '', $this->configuration['header']));
foreach ($lines as $line) {
$comment .= rtrim(' * '.$line).$lineEnding;
}
return $comment.' */';
}
private function findHeaderCommentCurrentIndex(Tokens $tokens, int $headerNewIndex): ?int
{
$index = $tokens->getNextNonWhitespace($headerNewIndex);
if (null === $index || !$tokens[$index]->isComment()) {
return null;
}
$next = $index + 1;
if (!isset($tokens[$next]) || \in_array($this->configuration['separate'], ['top', 'none'], true) || !$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
return $index;
}
if ($tokens[$next]->isWhitespace()) {
if (!Preg::match('/^\h*\R\h*$/D', $tokens[$next]->getContent())) {
return $index;
}
++$next;
}
if (!isset($tokens[$next]) || !$tokens[$next]->isClassy() && !$tokens[$next]->isGivenKind(T_FUNCTION)) {
return $index;
}
return $this->getHeaderAsComment() === $tokens[$index]->getContent() ? $index : null;
}
/**
* Find the index where the header comment must be inserted.
*/
private function findHeaderCommentInsertionIndex(Tokens $tokens, string $location): int
{
$openTagIndex = $tokens[0]->isGivenKind(T_OPEN_TAG) ? 0 : $tokens->getNextTokenOfKind(0, [[T_OPEN_TAG]]);
if (null === $openTagIndex) {
return 1;
}
if ('after_open' === $location) {
return $openTagIndex + 1;
}
$index = $tokens->getNextMeaningfulToken($openTagIndex);
if (null === $index) {
return $openTagIndex + 1; // file without meaningful tokens but an open tag, comment should always be placed directly after the open tag
}
if (!$tokens[$index]->isGivenKind(T_DECLARE)) {
return $openTagIndex + 1;
}
$next = $tokens->getNextMeaningfulToken($index);
if (null === $next || !$tokens[$next]->equals('(')) {
return $openTagIndex + 1;
}
$next = $tokens->getNextMeaningfulToken($next);
if (null === $next || !$tokens[$next]->equals([T_STRING, 'strict_types'], false)) {
return $openTagIndex + 1;
}
$next = $tokens->getNextMeaningfulToken($next);
if (null === $next || !$tokens[$next]->equals('=')) {
return $openTagIndex + 1;
}
$next = $tokens->getNextMeaningfulToken($next);
if (null === $next || !$tokens[$next]->isGivenKind(T_LNUMBER)) {
return $openTagIndex + 1;
}
$next = $tokens->getNextMeaningfulToken($next);
if (null === $next || !$tokens[$next]->equals(')')) {
return $openTagIndex + 1;
}
$next = $tokens->getNextMeaningfulToken($next);
if (null === $next || !$tokens[$next]->equals(';')) { // don't insert after close tag
return $openTagIndex + 1;
}
return $next + 1;
}
private function fixWhiteSpaceAroundHeader(Tokens $tokens, int $headerIndex): void
{
$lineEnding = $this->whitespacesConfig->getLineEnding();
// fix lines after header comment
if (
('both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate'])
&& null !== $tokens->getNextMeaningfulToken($headerIndex)
) {
$expectedLineCount = 2;
} else {
$expectedLineCount = 1;
}
if ($headerIndex === \count($tokens) - 1) {
$tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount)]));
} else {
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, 1);
if ($lineBreakCount < $expectedLineCount) {
$missing = str_repeat($lineEnding, $expectedLineCount - $lineBreakCount);
if ($tokens[$headerIndex + 1]->isWhitespace()) {
$tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $missing.$tokens[$headerIndex + 1]->getContent()]);
} else {
$tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, $missing]));
}
} elseif ($lineBreakCount > $expectedLineCount && $tokens[$headerIndex + 1]->isWhitespace()) {
$newLinesToRemove = $lineBreakCount - $expectedLineCount;
$tokens[$headerIndex + 1] = new Token([
T_WHITESPACE,
Preg::replace("/^\\R{{$newLinesToRemove}}/", '', $tokens[$headerIndex + 1]->getContent()),
]);
}
}
// fix lines before header comment
$expectedLineCount = 'both' === $this->configuration['separate'] || 'top' === $this->configuration['separate'] ? 2 : 1;
$prev = $tokens->getPrevNonWhitespace($headerIndex);
$regex = '/\h$/';
if ($tokens[$prev]->isGivenKind(T_OPEN_TAG) && Preg::match($regex, $tokens[$prev]->getContent())) {
$tokens[$prev] = new Token([T_OPEN_TAG, Preg::replace($regex, $lineEnding, $tokens[$prev]->getContent())]);
}
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, -1);
if ($lineBreakCount < $expectedLineCount) {
// because of the way the insert index was determined for header comment there cannot be an empty token here
$tokens->insertAt($headerIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount - $lineBreakCount)]));
}
}
private function getLineBreakCount(Tokens $tokens, int $index, int $direction): int
{
$whitespace = '';
for ($index += $direction; isset($tokens[$index]); $index += $direction) {
$token = $tokens[$index];
if ($token->isWhitespace()) {
$whitespace .= $token->getContent();
continue;
}
if (-1 === $direction && $token->isGivenKind(T_OPEN_TAG)) {
$whitespace .= $token->getContent();
}
if ('' !== $token->getContent()) {
break;
}
}
return substr_count($whitespace, "\n");
}
private function removeHeader(Tokens $tokens, int $index): void
{
$prevIndex = $index - 1;
$prevToken = $tokens[$prevIndex];
$newlineRemoved = false;
if ($prevToken->isWhitespace()) {
$content = $prevToken->getContent();
if (Preg::match('/\R/', $content)) {
$newlineRemoved = true;
}
$content = Preg::replace('/\R?\h*$/', '', $content);
$tokens->ensureWhitespaceAtIndex($prevIndex, 0, $content);
}
$nextIndex = $index + 1;
$nextToken = $tokens[$nextIndex] ?? null;
if (!$newlineRemoved && null !== $nextToken && $nextToken->isWhitespace()) {
$content = Preg::replace('/^\R/', '', $nextToken->getContent());
$tokens->ensureWhitespaceAtIndex($nextIndex, 0, $content);
}
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
}
private function insertHeader(Tokens $tokens, int $index): void
{
$tokens->insertAt($index, new Token([self::HEADER_COMMENT === $this->configuration['comment_type'] ? T_COMMENT : T_DOC_COMMENT, $this->getHeaderAsComment()]));
$this->fixWhiteSpaceAroundHeader($tokens, $index);
}
}
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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;
/**
* @author Filippo Tessarotto <zoeslam@gmail.com>
*/
final class MultilineCommentOpeningClosingFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash.',
[
new CodeSample(
<<<'EOT'
<?php
/******
* Multiline comment with arbitrary asterisks count
******/
/**\
* Multiline comment that seems a DocBlock
*/
/**
* DocBlock with arbitrary asterisk count at the end
**/
EOT
),
]
);
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($tokens as $index => $token) {
$originalContent = $token->getContent();
if (
!$token->isGivenKind(T_DOC_COMMENT)
&& !($token->isGivenKind(T_COMMENT) && str_starts_with($originalContent, '/*'))
) {
continue;
}
$newContent = $originalContent;
// Fix opening
if ($token->isGivenKind(T_COMMENT)) {
$newContent = Preg::replace('/^\\/\\*{2,}(?!\\/)/', '/*', $newContent);
}
// Fix closing
$newContent = Preg::replace('/(?<!\\/)\\*{2,}\\/$/', '*/', $newContent);
if ($newContent !== $originalContent) {
$tokens[$index] = new Token([$token->getId(), $newContent]);
}
}
}
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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\Tokens;
final class NoEmptyCommentFixer extends AbstractFixer
{
private const TYPE_HASH = 1;
private const TYPE_DOUBLE_SLASH = 2;
private const TYPE_SLASH_ASTERISK = 3;
/**
* {@inheritdoc}
*
* Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer.
* Must run after PhpdocToCommentFixer.
*/
public function getPriority(): int
{
return 2;
}
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'There should not be any empty comments.',
[new CodeSample("<?php\n//\n#\n/* */\n")]
);
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_COMMENT);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
for ($index = 1, $count = \count($tokens); $index < $count; ++$index) {
if (!$tokens[$index]->isGivenKind(T_COMMENT)) {
continue;
}
[$blockStart, $index, $isEmpty] = $this->getCommentBlock($tokens, $index);
if (false === $isEmpty) {
continue;
}
for ($i = $blockStart; $i <= $index; ++$i) {
$tokens->clearTokenAndMergeSurroundingWhitespace($i);
}
}
}
/**
* Return the start index, end index and a flag stating if the comment block is empty.
*
* @param int $index T_COMMENT index
*/
private function getCommentBlock(Tokens $tokens, int $index): array
{
$commentType = $this->getCommentType($tokens[$index]->getContent());
$empty = $this->isEmptyComment($tokens[$index]->getContent());
if (self::TYPE_SLASH_ASTERISK === $commentType) {
return [$index, $index, $empty];
}
$start = $index;
$count = \count($tokens);
++$index;
for (; $index < $count; ++$index) {
if ($tokens[$index]->isComment()) {
if ($commentType !== $this->getCommentType($tokens[$index]->getContent())) {
break;
}
if ($empty) { // don't retest if already known the block not being empty
$empty = $this->isEmptyComment($tokens[$index]->getContent());
}
continue;
}
if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) {
break;
}
}
return [$start, $index - 1, $empty];
}
private function getCommentType(string $content): int
{
if (str_starts_with($content, '#')) {
return self::TYPE_HASH;
}
if ('*' === $content[1]) {
return self::TYPE_SLASH_ASTERISK;
}
return self::TYPE_DOUBLE_SLASH;
}
private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEnd): int
{
$lineCount = 0;
for ($i = $whiteStart; $i < $whiteEnd; ++$i) {
$lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent(), $matches);
}
return $lineCount;
}
private function isEmptyComment(string $content): bool
{
static $mapper = [
self::TYPE_HASH => '|^#\s*$|', // single line comment starting with '#'
self::TYPE_SLASH_ASTERISK => '|^/\*[\s\*]*\*+/$|', // comment starting with '/*' and ending with '*/' (but not a PHPDoc)
self::TYPE_DOUBLE_SLASH => '|^//\s*$|', // single line comment starting with '//'
];
$type = $this->getCommentType($content);
return 1 === Preg::match($mapper[$type], $content);
}
}
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class NoTrailingWhitespaceInCommentFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'There MUST be no trailing spaces inside comment or PHPDoc.',
[new CodeSample('<?php
// This is '.'
// a comment. '.'
')]
);
}
/**
* {@inheritdoc}
*
* Must run after PhpdocNoUselessInheritdocFixer.
*/
public function getPriority(): int
{
return 0;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($tokens as $index => $token) {
if ($token->isGivenKind(T_DOC_COMMENT)) {
$tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace('/(*ANY)[\h]+$/m', '', $token->getContent())]);
continue;
}
if ($token->isGivenKind(T_COMMENT)) {
if (str_starts_with($token->getContent(), '/*')) {
$tokens[$index] = new Token([T_COMMENT, Preg::replace('/(*ANY)[\h]+$/m', '', $token->getContent())]);
} elseif (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) {
$trimmedContent = ltrim($tokens[$index + 1]->getContent(), " \t");
$tokens->ensureWhitespaceAtIndex($index + 1, 0, $trimmedContent);
}
}
}
}
}
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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(
'<?php
//comment 1
#comment 2
/*comment 3*/
'
),
]
);
}
/**
* {@inheritdoc}
*
* Must run after PhpdocToCommentFixer.
*/
public function getPriority(): int
{
return 1;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->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);
}
}
@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* 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\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Filippo Tessarotto <zoeslam@gmail.com>
*/
final class SingleLineCommentStyleFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/**
* @var bool
*/
private $asteriskEnabled;
/**
* @var bool
*/
private $hashEnabled;
/**
* {@inheritdoc}
*/
public function configure(array $configuration): void
{
parent::configure($configuration);
$this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true);
$this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true);
}
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax.',
[
new CodeSample(
'<?php
/* asterisk comment */
$a = 1;
# hash comment
$b = 2;
/*
* multi-line
* comment
*/
$c = 3;
'
),
new CodeSample(
'<?php
/* first comment */
$a = 1;
/*
* second comment
*/
$b = 2;
/*
* third
* comment
*/
$c = 3;
',
['comment_types' => ['asterisk']]
),
new CodeSample(
"<?php # comment\n",
['comment_types' => ['hash']]
),
]
);
}
/**
* {@inheritdoc}
*
* Must run after HeaderCommentFixer, NoUselessReturnFixer, PhpdocToCommentFixer.
*/
public function getPriority(): int
{
return -31;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_COMMENT);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($tokens as $index => $token) {
if (!$token->isGivenKind(T_COMMENT)) {
continue;
}
$content = $token->getContent();
$commentContent = substr($content, 2, -2) ?: '';
if ($this->hashEnabled && str_starts_with($content, '#')) {
if (isset($content[1]) && '[' === $content[1]) {
continue; // This might be an attribute on PHP8, do not change
}
$tokens[$index] = new Token([$token->getId(), '//'.substr($content, 1)]);
continue;
}
if (
!$this->asteriskEnabled
|| str_contains($commentContent, '?>')
|| !str_starts_with($content, '/*')
|| 1 === Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent)
) {
continue;
}
$nextTokenIndex = $index + 1;
if (isset($tokens[$nextTokenIndex])) {
$nextToken = $tokens[$nextTokenIndex];
if (!$nextToken->isWhitespace() || 1 !== Preg::match('/\R/', $nextToken->getContent())) {
continue;
}
$tokens[$nextTokenIndex] = new Token([$nextToken->getId(), ltrim($nextToken->getContent(), " \t")]);
}
$content = '//';
if (1 === Preg::match('/[^\s\*]/', $commentContent)) {
$content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent);
}
$tokens[$index] = new Token([$token->getId(), $content]);
}
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('comment_types', 'List of comment types to fix'))
->setAllowedTypes(['array'])
->setAllowedValues([new AllowedValueSubset(['asterisk', 'hash'])])
->setDefault(['asterisk', 'hash'])
->getOption(),
]);
}
}