Missing dependancies
This commit is contained in:
+182
@@ -0,0 +1,182 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
* @author Julien Falque <julien.falque@gmail.com>
|
||||
*/
|
||||
final class AlignMultilineCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var null|int[]
|
||||
*/
|
||||
private $tokenKinds;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->tokenKinds = [T_DOC_COMMENT];
|
||||
if ('phpdocs_only' !== $this->configuration['comment_type']) {
|
||||
$this->tokenKinds[] = T_COMMENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* This is a DOC Comment
|
||||
with a line not prefixed with asterisk
|
||||
|
||||
*/
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/*
|
||||
* This is a doc-like multiline comment
|
||||
*/
|
||||
',
|
||||
['comment_type' => 'phpdocs_like']
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/*
|
||||
* This is a doc-like multiline comment
|
||||
with a line not prefixed with asterisk
|
||||
|
||||
*/
|
||||
',
|
||||
['comment_type' => 'all_multiline']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
|
||||
* Must run after ArrayIndentationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 27;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound($this->tokenKinds);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind($this->tokenKinds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$whitespace = '';
|
||||
$previousIndex = $index - 1;
|
||||
|
||||
if ($tokens[$previousIndex]->isWhitespace()) {
|
||||
$whitespace = $tokens[$previousIndex]->getContent();
|
||||
--$previousIndex;
|
||||
}
|
||||
|
||||
if ($tokens[$previousIndex]->isGivenKind(T_OPEN_TAG)) {
|
||||
$whitespace = Preg::replace('/\S/', '', $tokens[$previousIndex]->getContent()).$whitespace;
|
||||
}
|
||||
|
||||
if (1 !== Preg::match('/\R(\h*)$/', $whitespace, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_COMMENT) && 'all_multiline' !== $this->configuration['comment_type'] && 1 === Preg::match('/\R(?:\R|\s*[^\s\*])/', $token->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indentation = $matches[1];
|
||||
$lines = Preg::split('/\R/u', $token->getContent());
|
||||
|
||||
foreach ($lines as $lineNumber => $line) {
|
||||
if (0 === $lineNumber) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line = ltrim($line);
|
||||
|
||||
if ($token->isGivenKind(T_COMMENT) && (!isset($line[0]) || '*' !== $line[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($line[0])) {
|
||||
$line = '*';
|
||||
} elseif ('*' !== $line[0]) {
|
||||
$line = '* '.$line;
|
||||
}
|
||||
|
||||
$lines[$lineNumber] = $indentation.' '.$line;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$token->getId(), implode($lineEnding, $lines)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('comment_type', 'Whether to fix PHPDoc comments only (`phpdocs_only`), any multi-line comment whose lines all start with an asterisk (`phpdocs_like`) or any multi-line comment (`all_multiline`).'))
|
||||
->setAllowedValues(['phpdocs_only', 'phpdocs_like', 'all_multiline'])
|
||||
->setDefault('phpdocs_only')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+176
@@ -0,0 +1,176 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class GeneralPhpdocAnnotationRemoveFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Configured annotations should be omitted from PHPDoc.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @internal
|
||||
* @author John Doe
|
||||
* @AuThOr Jane Doe
|
||||
*/
|
||||
function foo() {}
|
||||
',
|
||||
['annotations' => ['author']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @internal
|
||||
* @author John Doe
|
||||
* @AuThOr Jane Doe
|
||||
*/
|
||||
function foo() {}
|
||||
',
|
||||
['annotations' => ['author'], 'case_sensitive' => false]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @author John Doe
|
||||
* @package ACME API
|
||||
* @subpackage Authorization
|
||||
* @version 1.0
|
||||
*/
|
||||
function foo() {}
|
||||
',
|
||||
['annotations' => ['package', 'subpackage']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, PhpdocLineSpanFixer, PhpdocSeparationFixer, PhpdocTrimFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (0 === \count($this->configuration['annotations'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$annotations = $this->getAnnotationsToRemove($doc);
|
||||
|
||||
// nothing to do if there are no annotations
|
||||
if (0 === \count($annotations)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
$annotation->remove();
|
||||
}
|
||||
|
||||
if ('' === $doc->getContent()) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
} else {
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('annotations', 'List of annotations to remove, e.g. `["author"]`.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('case_sensitive', 'Should annotations be case sensitive.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Annotation>
|
||||
*/
|
||||
private function getAnnotationsToRemove(DocBlock $doc): array
|
||||
{
|
||||
if (true === $this->configuration['case_sensitive']) {
|
||||
return $doc->getAnnotationsOfType($this->configuration['annotations']);
|
||||
}
|
||||
|
||||
$typesToSearchFor = array_map(function (string $type): string {
|
||||
return strtolower($type);
|
||||
}, $this->configuration['annotations']);
|
||||
|
||||
$annotations = [];
|
||||
|
||||
foreach ($doc->getAnnotations() as $annotation) {
|
||||
$tagName = strtolower($annotation->getTag()->getName());
|
||||
if (\in_array($tagName, $typesToSearchFor, true)) {
|
||||
$annotations[] = $annotation;
|
||||
}
|
||||
}
|
||||
|
||||
return $annotations;
|
||||
}
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\Exception\InvalidOptionsException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
final class GeneralPhpdocTagRenameFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Renames PHPDoc tags.',
|
||||
[
|
||||
new CodeSample("<?php\n/**\n * @inheritDocs\n * {@inheritdocs}\n */\n", [
|
||||
'replacements' => [
|
||||
'inheritDocs' => 'inheritDoc',
|
||||
],
|
||||
]),
|
||||
new CodeSample("<?php\n/**\n * @inheritDocs\n * {@inheritdocs}\n */\n", [
|
||||
'replacements' => [
|
||||
'inheritDocs' => 'inheritDoc',
|
||||
],
|
||||
'fix_annotation' => false,
|
||||
]),
|
||||
new CodeSample("<?php\n/**\n * @inheritDocs\n * {@inheritdocs}\n */\n", [
|
||||
'replacements' => [
|
||||
'inheritDocs' => 'inheritDoc',
|
||||
],
|
||||
'fix_inline' => false,
|
||||
]),
|
||||
new CodeSample("<?php\n/**\n * @inheritDocs\n * {@inheritdocs}\n */\n", [
|
||||
'replacements' => [
|
||||
'inheritDocs' => 'inheritDoc',
|
||||
],
|
||||
'case_sensitive' => true,
|
||||
]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
// must be run before PhpdocAddMissingParamAnnotationFixer
|
||||
return 11;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('fix_annotation', 'Whether annotation tags should be fixed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('fix_inline', 'Whether inline tags should be fixed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('replacements', 'A map of tags to replace.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setNormalizer(static function (Options $options, $value): array {
|
||||
$normalizedValue = [];
|
||||
|
||||
foreach ($value as $from => $to) {
|
||||
if (!\is_string($from)) {
|
||||
throw new InvalidOptionsException('Tag to replace must be a string.');
|
||||
}
|
||||
|
||||
if (!\is_string($to)) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Tag to replace to from "%s" must be a string.',
|
||||
$from
|
||||
));
|
||||
}
|
||||
|
||||
if (1 !== Preg::match('#^\S+$#', $to) || str_contains($to, '*/')) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Tag "%s" cannot be replaced by invalid tag "%s".',
|
||||
$from,
|
||||
$to
|
||||
));
|
||||
}
|
||||
|
||||
$from = trim($from);
|
||||
$to = trim($to);
|
||||
|
||||
if (!$options['case_sensitive']) {
|
||||
$lowercaseFrom = strtolower($from);
|
||||
|
||||
if (isset($normalizedValue[$lowercaseFrom]) && $normalizedValue[$lowercaseFrom] !== $to) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Tag "%s" cannot be configured to be replaced with several different tags when case sensitivity is off.',
|
||||
$from
|
||||
));
|
||||
}
|
||||
|
||||
$from = $lowercaseFrom;
|
||||
}
|
||||
|
||||
$normalizedValue[$from] = $to;
|
||||
}
|
||||
|
||||
foreach ($normalizedValue as $from => $to) {
|
||||
if (isset($normalizedValue[$to]) && $normalizedValue[$to] !== $to) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Cannot change tag "%1$s" to tag "%2$s", as the tag "%2$s" is configured to be replaced to "%3$s".',
|
||||
$from,
|
||||
$to,
|
||||
$normalizedValue[$to]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $normalizedValue;
|
||||
})
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('case_sensitive', 'Whether tags should be replaced only if they have exact same casing.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (0 === \count($this->configuration['replacements'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (true === $this->configuration['fix_annotation']) {
|
||||
if ($this->configuration['fix_inline']) {
|
||||
$regex = '/"[^"]*"(*SKIP)(*FAIL)|\b(?<=@)(%s)\b/';
|
||||
} else {
|
||||
$regex = '/"[^"]*"(*SKIP)(*FAIL)|(?<!\{@)(?<=@)(%s)(?!\})/';
|
||||
}
|
||||
} else {
|
||||
$regex = '/(?<={@)(%s)(?=[ \t}])/';
|
||||
}
|
||||
|
||||
$caseInsensitive = false === $this->configuration['case_sensitive'];
|
||||
$replacements = $this->configuration['replacements'];
|
||||
$regex = sprintf($regex, implode('|', array_keys($replacements)));
|
||||
|
||||
if ($caseInsensitive) {
|
||||
$regex .= 'i';
|
||||
}
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, Preg::replaceCallback(
|
||||
$regex,
|
||||
static function (array $matches) use ($caseInsensitive, $replacements) {
|
||||
if ($caseInsensitive) {
|
||||
$matches[1] = strtolower($matches[1]);
|
||||
}
|
||||
|
||||
return $replacements[$matches[1]];
|
||||
},
|
||||
$token->getContent()
|
||||
)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class NoBlankLinesAfterPhpdocFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There should not be blank lines between docblock and the documented element.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
/**
|
||||
* This is the bar class.
|
||||
*/
|
||||
|
||||
|
||||
class Bar {}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before HeaderCommentFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -20;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
static $forbiddenSuccessors = [
|
||||
T_BREAK,
|
||||
T_COMMENT,
|
||||
T_CONTINUE,
|
||||
T_DECLARE,
|
||||
T_DOC_COMMENT,
|
||||
T_GOTO,
|
||||
T_NAMESPACE,
|
||||
T_RETURN,
|
||||
T_THROW,
|
||||
T_USE,
|
||||
T_WHITESPACE,
|
||||
];
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
// get the next non-whitespace token inc comments, provided
|
||||
// that there is whitespace between it and the current token
|
||||
$next = $tokens->getNextNonWhitespace($index);
|
||||
if ($index + 2 === $next && false === $tokens[$next]->isGivenKind($forbiddenSuccessors)) {
|
||||
$this->fixWhitespace($tokens, $index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup a whitespace token.
|
||||
*/
|
||||
private function fixWhitespace(Tokens $tokens, int $index): void
|
||||
{
|
||||
$content = $tokens[$index]->getContent();
|
||||
// if there is more than one new line in the whitespace, then we need to fix it
|
||||
if (substr_count($content, "\n") > 1) {
|
||||
// the final bit of the whitespace must be the next statement's indentation
|
||||
$tokens[$index] = new Token([T_WHITESPACE, substr($content, strrpos($content, "\n"))]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoEmptyPhpdocFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There should not be empty PHPDoc blocks.',
|
||||
[new CodeSample("<?php /** */\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, NoSuperfluousPhpdocTagsFixer, PhpUnitNoExpectationAnnotationFixer, PhpUnitTestAnnotationFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Preg::match('#^/\*\*[\s\*]*\*/$#', $token->getContent())) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+667
@@ -0,0 +1,667 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\TypeExpression;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\NamespaceUsesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
private const NO_TYPE_INFO = [
|
||||
'types' => [],
|
||||
'allows_null' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes `@param`, `@return` and `@var` tags that don\'t provide any useful information.',
|
||||
[
|
||||
new CodeSample('<?php
|
||||
class Foo {
|
||||
/**
|
||||
* @param Bar $bar
|
||||
* @param mixed $baz
|
||||
*
|
||||
* @return Baz
|
||||
*/
|
||||
public function doFoo(Bar $bar, $baz): Baz {}
|
||||
}
|
||||
'),
|
||||
new CodeSample('<?php
|
||||
class Foo {
|
||||
/**
|
||||
* @param Bar $bar
|
||||
* @param mixed $baz
|
||||
*/
|
||||
public function doFoo(Bar $bar, $baz) {}
|
||||
}
|
||||
', ['allow_mixed' => true]),
|
||||
new CodeSample('<?php
|
||||
class Foo {
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function doFoo(Bar $bar, $baz) {}
|
||||
}
|
||||
', ['remove_inheritdoc' => true]),
|
||||
new CodeSample('<?php
|
||||
class Foo {
|
||||
/**
|
||||
* @param Bar $bar
|
||||
* @param mixed $baz
|
||||
* @param string|int|null $qux
|
||||
*/
|
||||
public function doFoo(Bar $bar, $baz /*, $qux = null */) {}
|
||||
}
|
||||
', ['allow_unused_params' => true]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, VoidReturnFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, FullyQualifiedStrictTypesFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocLineSpanFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
$namespaceUseAnalyzer = new NamespaceUsesAnalyzer();
|
||||
$shortNames = [];
|
||||
$currentSymbol = null;
|
||||
$currentSymbolEndIndex = null;
|
||||
|
||||
foreach ($namespaceUseAnalyzer->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) {
|
||||
$shortNames[strtolower($namespaceUseAnalysis->getShortName())] = '\\'.strtolower($namespaceUseAnalysis->getFullName());
|
||||
}
|
||||
|
||||
$symbolKinds = [T_CLASS, T_INTERFACE];
|
||||
if (\defined('T_ENUM')) { // @TODO drop the condition when requiring PHP 8.1+
|
||||
$symbolKinds[] = T_ENUM;
|
||||
}
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($index === $currentSymbolEndIndex) {
|
||||
$currentSymbol = null;
|
||||
$currentSymbolEndIndex = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind($symbolKinds)) {
|
||||
$currentSymbol = $tokens[$tokens->getNextMeaningfulToken($index)]->getContent();
|
||||
$currentSymbolEndIndex = $tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_CURLY_BRACE,
|
||||
$tokens->getNextTokenOfKind($index, ['{']),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$documentedElement = $this->findDocumentedElement($tokens, $index);
|
||||
|
||||
if (null === $documentedElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $initialContent = $token->getContent();
|
||||
|
||||
if (true === $this->configuration['remove_inheritdoc']) {
|
||||
$content = $this->removeSuperfluousInheritDoc($content);
|
||||
}
|
||||
|
||||
if ('function' === $documentedElement['type']) {
|
||||
$content = $this->fixFunctionDocComment($content, $tokens, $documentedElement, $currentSymbol, $shortNames);
|
||||
} elseif ('property' === $documentedElement['type']) {
|
||||
$content = $this->fixPropertyDocComment($content, $tokens, $documentedElement, $currentSymbol, $shortNames);
|
||||
} elseif ('classy' === $documentedElement['type']) {
|
||||
$content = $this->fixClassDocComment($content, $documentedElement);
|
||||
} else {
|
||||
throw new \RuntimeException('Unknown type.');
|
||||
}
|
||||
|
||||
if ('' === $content) {
|
||||
$content = '/** */';
|
||||
}
|
||||
|
||||
if ($content !== $initialContent) {
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $content]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('allow_mixed', 'Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`)'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('remove_inheritdoc', 'Remove `@inheritDoc` tags'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('allow_unused_params', 'Whether `param` annotation without actual signature is allowed (`true`) or considered superfluous (`false`)'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|array{
|
||||
* index: int,
|
||||
* type: 'classy'|'function'|'property',
|
||||
* modifiers: array<int, Token>,
|
||||
* types: array<int, Token>,
|
||||
* }
|
||||
*/
|
||||
private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?array
|
||||
{
|
||||
$modifierKinds = [
|
||||
T_PRIVATE,
|
||||
T_PROTECTED,
|
||||
T_PUBLIC,
|
||||
T_ABSTRACT,
|
||||
T_FINAL,
|
||||
T_STATIC,
|
||||
];
|
||||
|
||||
$typeKinds = [
|
||||
CT::T_NULLABLE_TYPE,
|
||||
CT::T_ARRAY_TYPEHINT,
|
||||
CT::T_TYPE_ALTERNATION,
|
||||
CT::T_TYPE_INTERSECTION,
|
||||
T_STRING,
|
||||
T_NS_SEPARATOR,
|
||||
];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$modifierKinds[] = T_READONLY;
|
||||
}
|
||||
|
||||
$element = [
|
||||
'modifiers' => [],
|
||||
'types' => [],
|
||||
];
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($docCommentIndex);
|
||||
|
||||
// @TODO: drop condition when PHP 8.0+ is required
|
||||
if (null !== $index && \defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
|
||||
do {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
} while (null !== $index && $tokens[$index]->isGivenKind(T_ATTRIBUTE));
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (null === $index) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isClassy()) {
|
||||
$element['index'] = $index;
|
||||
$element['type'] = 'classy';
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
$element['index'] = $index;
|
||||
$element['type'] = 'function';
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_VARIABLE)) {
|
||||
$element['index'] = $index;
|
||||
$element['type'] = 'property';
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind($modifierKinds)) {
|
||||
$element['modifiers'][$index] = $tokens[$index];
|
||||
} elseif ($tokens[$index]->isGivenKind($typeKinds)) {
|
||||
$element['types'][$index] = $tokens[$index];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* index: int,
|
||||
* type: 'function',
|
||||
* modifiers: array<int, Token>,
|
||||
* types: array<int, Token>,
|
||||
* } $element
|
||||
* @param array<string, string> $shortNames
|
||||
*/
|
||||
private function fixFunctionDocComment(
|
||||
string $content,
|
||||
Tokens $tokens,
|
||||
array $element,
|
||||
?string $currentSymbol,
|
||||
array $shortNames
|
||||
): string {
|
||||
$docBlock = new DocBlock($content);
|
||||
|
||||
$openingParenthesisIndex = $tokens->getNextTokenOfKind($element['index'], ['(']);
|
||||
$closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex);
|
||||
|
||||
$argumentsInfo = $this->getArgumentsInfo(
|
||||
$tokens,
|
||||
$openingParenthesisIndex + 1,
|
||||
$closingParenthesisIndex - 1
|
||||
);
|
||||
|
||||
foreach ($docBlock->getAnnotationsOfType('param') as $annotation) {
|
||||
$argumentName = $annotation->getVariableName();
|
||||
|
||||
if (null === $argumentName) {
|
||||
if ($this->annotationIsSuperfluous($annotation, self::NO_TYPE_INFO, $currentSymbol, $shortNames)) {
|
||||
$annotation->remove();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($argumentsInfo[$argumentName]) && true === $this->configuration['allow_unused_params']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($argumentsInfo[$argumentName]) || $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $currentSymbol, $shortNames)) {
|
||||
$annotation->remove();
|
||||
}
|
||||
}
|
||||
|
||||
$returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex);
|
||||
|
||||
foreach ($docBlock->getAnnotationsOfType('return') as $annotation) {
|
||||
if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $currentSymbol, $shortNames)) {
|
||||
$annotation->remove();
|
||||
}
|
||||
}
|
||||
|
||||
$this->removeSuperfluousModifierAnnotation($docBlock, $element);
|
||||
|
||||
return $docBlock->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* index: int,
|
||||
* type: 'property',
|
||||
* modifiers: array<int, Token>,
|
||||
* types: array<int, Token>,
|
||||
* } $element
|
||||
* @param array<string, string> $shortNames
|
||||
*/
|
||||
private function fixPropertyDocComment(
|
||||
string $content,
|
||||
Tokens $tokens,
|
||||
array $element,
|
||||
?string $currentSymbol,
|
||||
array $shortNames
|
||||
): string {
|
||||
if (\count($element['types']) > 0) {
|
||||
$propertyTypeInfo = $this->parseTypeHint($tokens, array_key_first($element['types']));
|
||||
} else {
|
||||
$propertyTypeInfo = self::NO_TYPE_INFO;
|
||||
}
|
||||
|
||||
$docBlock = new DocBlock($content);
|
||||
|
||||
foreach ($docBlock->getAnnotationsOfType('var') as $annotation) {
|
||||
if ($this->annotationIsSuperfluous($annotation, $propertyTypeInfo, $currentSymbol, $shortNames)) {
|
||||
$annotation->remove();
|
||||
}
|
||||
}
|
||||
|
||||
return $docBlock->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* index: int,
|
||||
* type: 'classy',
|
||||
* modifiers: array<int, Token>,
|
||||
* types: array<int, Token>,
|
||||
* } $element
|
||||
*/
|
||||
private function fixClassDocComment(string $content, array $element): string
|
||||
{
|
||||
$docBlock = new DocBlock($content);
|
||||
|
||||
$this->removeSuperfluousModifierAnnotation($docBlock, $element);
|
||||
|
||||
return $docBlock->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{types: list<string>, allows_null: bool}>
|
||||
*/
|
||||
private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array
|
||||
{
|
||||
$argumentsInfo = [];
|
||||
|
||||
for ($index = $start; $index <= $end; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind(T_VARIABLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$beforeArgumentIndex = $tokens->getPrevTokenOfKind($index, ['(', ',']);
|
||||
$typeIndex = $tokens->getNextMeaningfulToken($beforeArgumentIndex);
|
||||
|
||||
if ($typeIndex !== $index) {
|
||||
$info = $this->parseTypeHint($tokens, $typeIndex);
|
||||
} else {
|
||||
$info = self::NO_TYPE_INFO;
|
||||
}
|
||||
|
||||
if (!$info['allows_null']) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
if (
|
||||
$tokens[$nextIndex]->equals('=')
|
||||
&& $tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals([T_STRING, 'null'], false)
|
||||
) {
|
||||
$info['allows_null'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$argumentsInfo[$token->getContent()] = $info;
|
||||
}
|
||||
|
||||
return $argumentsInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{types: list<string>, allows_null: bool}
|
||||
*/
|
||||
private function getReturnTypeInfo(Tokens $tokens, int $closingParenthesisIndex): array
|
||||
{
|
||||
$colonIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex);
|
||||
|
||||
return $tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)
|
||||
? $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex))
|
||||
: self::NO_TYPE_INFO
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index The index of the first token of the type hint
|
||||
*
|
||||
* @return array{types: list<string>, allows_null: bool}
|
||||
*/
|
||||
private function parseTypeHint(Tokens $tokens, int $index): array
|
||||
{
|
||||
$allowsNull = false;
|
||||
|
||||
$types = [];
|
||||
|
||||
while (true) {
|
||||
$type = '';
|
||||
|
||||
if (\defined('T_READONLY') && $tokens[$index]->isGivenKind(T_READONLY)) { // @TODO: simplify condition when PHP 8.1+ is required
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE])) {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) {
|
||||
$allowsNull = true;
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
while ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STATIC, T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE])) {
|
||||
$type .= $tokens[$index]->getContent();
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
if ('' === $type) {
|
||||
break;
|
||||
}
|
||||
|
||||
$types[] = $type;
|
||||
|
||||
if (!$tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
return [
|
||||
'types' => $types,
|
||||
'allows_null' => $allowsNull,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $symbolShortNames
|
||||
*/
|
||||
private function annotationIsSuperfluous(
|
||||
Annotation $annotation,
|
||||
array $info,
|
||||
?string $currentSymbol,
|
||||
array $symbolShortNames
|
||||
): bool {
|
||||
if ('param' === $annotation->getTag()->getName()) {
|
||||
$regex = '{@param(?:\s+'.TypeExpression::REGEX_TYPES.')?(?:\s+(?:\&\s*)?(?:\.{3}\s*)?\$\S+)?(?:\s+(?<description>(?!\*+\/)\S+))?}sx';
|
||||
} elseif ('var' === $annotation->getTag()->getName()) {
|
||||
$regex = '{@var(?:\s+'.TypeExpression::REGEX_TYPES.')?(?:\s+\$\S+)?(?:\s+(?<description>(?!\*\/)\S+))?}sx';
|
||||
} else {
|
||||
$regex = '{@return(?:\s+'.TypeExpression::REGEX_TYPES.')?(?:\s+(?<description>(?!\*\/)\S+))?}sx';
|
||||
}
|
||||
|
||||
if (1 !== Preg::match($regex, $annotation->getContent(), $matches)) {
|
||||
// Unable to match the annotation, it must be malformed or has unsupported format.
|
||||
// Either way we don't want to tinker with it.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($matches['description'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($matches['types']) || '' === $matches['types']) {
|
||||
// If there's no type info in the annotation, further checks make no sense, exit early.
|
||||
return true;
|
||||
}
|
||||
|
||||
$annotationTypes = $this->toComparableNames($annotation->getTypes(), $currentSymbol, $symbolShortNames);
|
||||
|
||||
if (['null'] === $annotationTypes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (['mixed'] === $annotationTypes && [] === $info['types']) {
|
||||
return false === $this->configuration['allow_mixed'];
|
||||
}
|
||||
|
||||
$actualTypes = $info['types'];
|
||||
|
||||
if ($info['allows_null']) {
|
||||
$actualTypes[] = 'null';
|
||||
}
|
||||
|
||||
return $annotationTypes === $this->toComparableNames($actualTypes, $currentSymbol, $symbolShortNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes types to make them comparable.
|
||||
*
|
||||
* Converts given types to lowercase, replaces imports aliases with
|
||||
* their matching FQCN, and finally sorts the result.
|
||||
*
|
||||
* @param string[] $types The types to normalize
|
||||
* @param array<string, string> $symbolShortNames The imports aliases
|
||||
*
|
||||
* @return array The normalized types
|
||||
*/
|
||||
private function toComparableNames(array $types, ?string $currentSymbol, array $symbolShortNames): array
|
||||
{
|
||||
$normalized = array_map(
|
||||
static function (string $type) use ($currentSymbol, $symbolShortNames): string {
|
||||
if ('self' === $type && null !== $currentSymbol) {
|
||||
$type = $currentSymbol;
|
||||
}
|
||||
|
||||
$type = strtolower($type);
|
||||
|
||||
if (str_contains($type, '&')) {
|
||||
$intersects = explode('&', $type);
|
||||
sort($intersects);
|
||||
|
||||
return implode('&', $intersects);
|
||||
}
|
||||
|
||||
return $symbolShortNames[$type] ?? $type;
|
||||
},
|
||||
$types
|
||||
);
|
||||
|
||||
sort($normalized);
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private function removeSuperfluousInheritDoc(string $docComment): string
|
||||
{
|
||||
return Preg::replace('~
|
||||
# $1: before @inheritDoc tag
|
||||
(
|
||||
# beginning of comment or a PHPDoc tag
|
||||
(?:
|
||||
^/\*\*
|
||||
(?:
|
||||
\R
|
||||
[ \t]*(?:\*[ \t]*)?
|
||||
)*?
|
||||
|
|
||||
@\N+
|
||||
)
|
||||
|
||||
# empty comment lines
|
||||
(?:
|
||||
\R
|
||||
[ \t]*(?:\*[ \t]*?)?
|
||||
)*
|
||||
)
|
||||
|
||||
# spaces before @inheritDoc tag
|
||||
[ \t]*
|
||||
|
||||
# @inheritDoc tag
|
||||
(?:@inheritDocs?|\{@inheritDocs?\})
|
||||
|
||||
# $2: after @inheritDoc tag
|
||||
(
|
||||
# empty comment lines
|
||||
(?:
|
||||
\R
|
||||
[ \t]*(?:\*[ \t]*)?
|
||||
)*
|
||||
|
||||
# a PHPDoc tag or end of comment
|
||||
(?:
|
||||
@\N+
|
||||
|
|
||||
(?:
|
||||
\R
|
||||
[ \t]*(?:\*[ \t]*)?
|
||||
)*
|
||||
[ \t]*\*/$
|
||||
)
|
||||
)
|
||||
~ix', '$1$2', $docComment);
|
||||
}
|
||||
|
||||
private function removeSuperfluousModifierAnnotation(DocBlock $docBlock, array $element): void
|
||||
{
|
||||
foreach (['abstract' => T_ABSTRACT, 'final' => T_FINAL] as $annotationType => $modifierToken) {
|
||||
$annotations = $docBlock->getAnnotationsOfType($annotationType);
|
||||
|
||||
foreach ($element['modifiers'] as $token) {
|
||||
if ($token->isGivenKind($modifierToken)) {
|
||||
foreach ($annotations as $annotation) {
|
||||
$annotation->remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+279
@@ -0,0 +1,279 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
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\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocAddMissingParamAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPDoc should contain `@param` for all params.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param int $bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function f9(string $foo, $bar, $baz) {}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param int $bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function f9(string $foo, $bar, $baz) {}
|
||||
',
|
||||
['only_untyped' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param int $bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function f9(string $foo, $bar, $baz) {}
|
||||
',
|
||||
['only_untyped' => false]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, PhpdocOrderFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocTagRenameFixer, PhpdocIndentFixer, PhpdocNoAliasTagFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokenContent = $token->getContent();
|
||||
|
||||
if (false !== stripos($tokenContent, 'inheritdoc')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations
|
||||
if (!str_contains($tokenContent, "\n")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mainIndex = $index;
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (null === $index) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ($tokens[$index]->isGivenKind([
|
||||
T_ABSTRACT,
|
||||
T_FINAL,
|
||||
T_PRIVATE,
|
||||
T_PROTECTED,
|
||||
T_PUBLIC,
|
||||
T_STATIC,
|
||||
T_VAR,
|
||||
])) {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
|
||||
$arguments = [];
|
||||
|
||||
foreach ($argumentsAnalyzer->getArguments($tokens, $openIndex, $index) as $start => $end) {
|
||||
$argumentInfo = $this->prepareArgumentInformation($tokens, $start, $end);
|
||||
|
||||
if (false === $this->configuration['only_untyped'] || '' === $argumentInfo['type']) {
|
||||
$arguments[$argumentInfo['name']] = $argumentInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === \count($arguments)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($tokenContent);
|
||||
$lastParamLine = null;
|
||||
|
||||
foreach ($doc->getAnnotationsOfType('param') as $annotation) {
|
||||
$pregMatched = Preg::match('/^[^$]+(\$\w+).*$/s', $annotation->getContent(), $matches);
|
||||
|
||||
if (1 === $pregMatched) {
|
||||
unset($arguments[$matches[1]]);
|
||||
}
|
||||
|
||||
$lastParamLine = max($lastParamLine, $annotation->getEnd());
|
||||
}
|
||||
|
||||
if (0 === \count($arguments)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines = $doc->getLines();
|
||||
$linesCount = \count($lines);
|
||||
|
||||
Preg::match('/^(\s*).*$/', $lines[$linesCount - 1]->getContent(), $matches);
|
||||
$indent = $matches[1];
|
||||
|
||||
$newLines = [];
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
$type = $argument['type'] ?: 'mixed';
|
||||
|
||||
if (!str_starts_with($type, '?') && 'null' === strtolower($argument['default'])) {
|
||||
$type = 'null|'.$type;
|
||||
}
|
||||
|
||||
$newLines[] = new Line(sprintf(
|
||||
'%s* @param %s %s%s',
|
||||
$indent,
|
||||
$type,
|
||||
$argument['name'],
|
||||
$this->whitespacesConfig->getLineEnding()
|
||||
));
|
||||
}
|
||||
|
||||
array_splice(
|
||||
$lines,
|
||||
$lastParamLine ? $lastParamLine + 1 : $linesCount - 1,
|
||||
0,
|
||||
$newLines
|
||||
);
|
||||
|
||||
$tokens[$mainIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('only_untyped', 'Whether to add missing `@param` annotations for untyped parameters only.'))
|
||||
->setDefault(true)
|
||||
->setAllowedTypes(['bool'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{default: string, name: string, type: string}
|
||||
*/
|
||||
private function prepareArgumentInformation(Tokens $tokens, int $start, int $end): array
|
||||
{
|
||||
$info = [
|
||||
'default' => '',
|
||||
'name' => '',
|
||||
'type' => '',
|
||||
];
|
||||
|
||||
$sawName = false;
|
||||
|
||||
for ($index = $start; $index <= $end; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isComment() || $token->isWhitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_VARIABLE)) {
|
||||
$sawName = true;
|
||||
$info['name'] = $token->getContent();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals('=')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sawName) {
|
||||
$info['default'] .= $token->getContent();
|
||||
} elseif (!$token->equals('&')) {
|
||||
if ($token->isGivenKind(T_ELLIPSIS)) {
|
||||
if ('' === $info['type']) {
|
||||
$info['type'] = 'array';
|
||||
} else {
|
||||
$info['type'] .= '[]';
|
||||
}
|
||||
} else {
|
||||
$info['type'] .= $token->getContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\TypeExpression;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
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 Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocAlignFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const ALIGN_LEFT = 'left';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const ALIGN_VERTICAL = 'vertical';
|
||||
|
||||
private const ALIGNABLE_TAGS = [
|
||||
'param',
|
||||
'property',
|
||||
'property-read',
|
||||
'property-write',
|
||||
'return',
|
||||
'throws',
|
||||
'type',
|
||||
'var',
|
||||
'method',
|
||||
];
|
||||
|
||||
private const TAGS_WITH_NAME = [
|
||||
'param',
|
||||
'property',
|
||||
'property-read',
|
||||
'property-write',
|
||||
];
|
||||
|
||||
private const TAGS_WITH_METHOD_SIGNATURE = [
|
||||
'method',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $regex;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $regexCommentLine;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $align;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$tagsWithNameToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_NAME);
|
||||
$tagsWithMethodSignatureToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_METHOD_SIGNATURE);
|
||||
$tagsWithoutNameToAlign = array_diff($this->configuration['tags'], $tagsWithNameToAlign, $tagsWithMethodSignatureToAlign);
|
||||
$types = [];
|
||||
|
||||
$indent = '(?P<indent>(?:\ {2}|\t)*)';
|
||||
|
||||
// e.g. @param <hint> <$var>
|
||||
if ([] !== $tagsWithNameToAlign) {
|
||||
$types[] = '(?P<tag>'.implode('|', $tagsWithNameToAlign).')\s+(?P<hint>(?:'.TypeExpression::REGEX_TYPES.')?)\s+(?P<var>(?:&|\.{3})?\$\S+)';
|
||||
}
|
||||
|
||||
// e.g. @return <hint>
|
||||
if ([] !== $tagsWithoutNameToAlign) {
|
||||
$types[] = '(?P<tag2>'.implode('|', $tagsWithoutNameToAlign).')\s+(?P<hint2>(?:'.TypeExpression::REGEX_TYPES.')?)';
|
||||
}
|
||||
|
||||
// e.g. @method <hint> <signature>
|
||||
if ([] !== $tagsWithMethodSignatureToAlign) {
|
||||
$types[] = '(?P<tag3>'.implode('|', $tagsWithMethodSignatureToAlign).')(\s+(?P<static>static))?(\s+(?P<hint3>[^\s(]+)|)\s+(?P<signature>.+\))';
|
||||
}
|
||||
|
||||
// optional <desc>
|
||||
$desc = '(?:\s+(?P<desc>\V*))';
|
||||
|
||||
$this->regex = '/^'.$indent.'\ \*\ @(?J)(?:'.implode('|', $types).')'.$desc.'\s*$/ux';
|
||||
$this->regexCommentLine = '/^'.$indent.' \*(?! @)(?:\s+(?P<desc>\V+))(?<!\*\/)\r?$/u';
|
||||
$this->align = $this->configuration['align'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
$code = <<<'EOF'
|
||||
<?php
|
||||
/**
|
||||
* @param EngineInterface $templating
|
||||
* @param string $format
|
||||
* @param int $code an HTTP response status code
|
||||
* @param bool $debug
|
||||
* @param mixed &$reference a parameter passed by reference
|
||||
*/
|
||||
|
||||
EOF;
|
||||
|
||||
return new FixerDefinition(
|
||||
'All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically.',
|
||||
[
|
||||
new CodeSample($code),
|
||||
new CodeSample($code, ['align' => self::ALIGN_VERTICAL]),
|
||||
new CodeSample($code, ['align' => self::ALIGN_LEFT]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
/*
|
||||
* Should be run after all other docblock fixers. This because they
|
||||
* modify other annotations to change their type and or separation
|
||||
* which totally change the behavior of this fixer. It's important that
|
||||
* annotations are of the correct type, and are grouped correctly
|
||||
* before running this fixer.
|
||||
*/
|
||||
return -42;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
$docBlock = new DocBlock($content);
|
||||
$this->fixDocBlock($docBlock);
|
||||
$newContent = $docBlock->getContent();
|
||||
if ($newContent !== $content) {
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$tags = new FixerOptionBuilder('tags', 'The tags that should be aligned.');
|
||||
$tags
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(self::ALIGNABLE_TAGS)])
|
||||
->setDefault([
|
||||
'method',
|
||||
'param',
|
||||
'property',
|
||||
'return',
|
||||
'throws',
|
||||
'type',
|
||||
'var',
|
||||
])
|
||||
;
|
||||
|
||||
$align = new FixerOptionBuilder('align', 'Align comments');
|
||||
$align
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([self::ALIGN_LEFT, self::ALIGN_VERTICAL])
|
||||
->setDefault(self::ALIGN_VERTICAL)
|
||||
;
|
||||
|
||||
return new FixerConfigurationResolver([$tags->getOption(), $align->getOption()]);
|
||||
}
|
||||
|
||||
private function fixDocBlock(DocBlock $docBlock): void
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
for ($i = 0, $l = \count($docBlock->getLines()); $i < $l; ++$i) {
|
||||
$matches = $this->getMatches($docBlock->getLine($i)->getContent());
|
||||
|
||||
if (null === $matches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current = $i;
|
||||
$items = [$matches];
|
||||
|
||||
while (true) {
|
||||
if (null === $docBlock->getLine(++$i)) {
|
||||
break 2;
|
||||
}
|
||||
|
||||
$matches = $this->getMatches($docBlock->getLine($i)->getContent(), true);
|
||||
if (null === $matches) {
|
||||
break;
|
||||
}
|
||||
|
||||
$items[] = $matches;
|
||||
}
|
||||
|
||||
// compute the max length of the tag, hint and variables
|
||||
$hasStatic = false;
|
||||
$tagMax = 0;
|
||||
$hintMax = 0;
|
||||
$varMax = 0;
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (null === $item['tag']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasStatic = $hasStatic || $item['static'];
|
||||
$tagMax = max($tagMax, \strlen($item['tag']));
|
||||
$hintMax = max($hintMax, \strlen($item['hint']));
|
||||
$varMax = max($varMax, \strlen($item['var']));
|
||||
}
|
||||
|
||||
$currTag = null;
|
||||
|
||||
// update
|
||||
foreach ($items as $j => $item) {
|
||||
if (null === $item['tag']) {
|
||||
if ('@' === $item['desc'][0]) {
|
||||
$docBlock->getLine($current + $j)->setContent($item['indent'].' * '.$item['desc'].$lineEnding);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$extraIndent = 2;
|
||||
|
||||
if (\in_array($currTag, self::TAGS_WITH_NAME, true) || \in_array($currTag, self::TAGS_WITH_METHOD_SIGNATURE, true)) {
|
||||
$extraIndent = 3;
|
||||
}
|
||||
|
||||
if ($hasStatic) {
|
||||
$extraIndent += 7; // \strlen('static ');
|
||||
}
|
||||
|
||||
$line =
|
||||
$item['indent']
|
||||
.' * '
|
||||
.$this->getIndent(
|
||||
$tagMax + $hintMax + $varMax + $extraIndent,
|
||||
$this->getLeftAlignedDescriptionIndent($items, $j)
|
||||
)
|
||||
.$item['desc']
|
||||
.$lineEnding;
|
||||
|
||||
$docBlock->getLine($current + $j)->setContent($line);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$currTag = $item['tag'];
|
||||
|
||||
$line =
|
||||
$item['indent']
|
||||
.' * @'
|
||||
.$item['tag']
|
||||
;
|
||||
|
||||
if ($hasStatic) {
|
||||
$line .=
|
||||
$this->getIndent(
|
||||
$tagMax - \strlen($item['tag']) + 1,
|
||||
$item['static'] ? 1 : 0
|
||||
)
|
||||
.($item['static'] ?: $this->getIndent(6 /* \strlen('static') */, 0))
|
||||
;
|
||||
$hintVerticalAlignIndent = 1;
|
||||
} else {
|
||||
$hintVerticalAlignIndent = $tagMax - \strlen($item['tag']) + 1;
|
||||
}
|
||||
|
||||
$line .=
|
||||
$this->getIndent(
|
||||
$hintVerticalAlignIndent,
|
||||
$item['hint'] ? 1 : 0
|
||||
)
|
||||
.$item['hint']
|
||||
;
|
||||
|
||||
if (!empty($item['var'])) {
|
||||
$line .=
|
||||
$this->getIndent(($hintMax ?: -1) - \strlen($item['hint']) + 1)
|
||||
.$item['var']
|
||||
.(
|
||||
!empty($item['desc'])
|
||||
? $this->getIndent($varMax - \strlen($item['var']) + 1).$item['desc'].$lineEnding
|
||||
: $lineEnding
|
||||
)
|
||||
;
|
||||
} elseif (!empty($item['desc'])) {
|
||||
$line .= $this->getIndent($hintMax - \strlen($item['hint']) + 1).$item['desc'].$lineEnding;
|
||||
} else {
|
||||
$line .= $lineEnding;
|
||||
}
|
||||
|
||||
$docBlock->getLine($current + $j)->setContent($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|array<string, null|string>
|
||||
*/
|
||||
private function getMatches(string $line, bool $matchCommentOnly = false): ?array
|
||||
{
|
||||
if (Preg::match($this->regex, $line, $matches)) {
|
||||
if (!empty($matches['tag2'])) {
|
||||
$matches['tag'] = $matches['tag2'];
|
||||
$matches['hint'] = $matches['hint2'];
|
||||
$matches['var'] = '';
|
||||
}
|
||||
|
||||
if (!empty($matches['tag3'])) {
|
||||
$matches['tag'] = $matches['tag3'];
|
||||
$matches['hint'] = $matches['hint3'];
|
||||
$matches['var'] = $matches['signature'];
|
||||
|
||||
// Since static can be both a return type declaration & a keyword that defines static methods
|
||||
// we assume it's a type declaration when only one value is present
|
||||
if ('' === $matches['hint'] && '' !== $matches['static']) {
|
||||
$matches['hint'] = $matches['static'];
|
||||
$matches['static'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($matches['hint'])) {
|
||||
$matches['hint'] = trim($matches['hint']);
|
||||
}
|
||||
|
||||
if (!isset($matches['static'])) {
|
||||
$matches['static'] = '';
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
if ($matchCommentOnly && Preg::match($this->regexCommentLine, $line, $matches)) {
|
||||
$matches['tag'] = null;
|
||||
$matches['var'] = '';
|
||||
$matches['hint'] = '';
|
||||
$matches['static'] = '';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getIndent(int $verticalAlignIndent, int $leftAlignIndent = 1): string
|
||||
{
|
||||
$indent = self::ALIGN_VERTICAL === $this->align ? $verticalAlignIndent : $leftAlignIndent;
|
||||
|
||||
return str_repeat(' ', $indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array[] $items
|
||||
*/
|
||||
private function getLeftAlignedDescriptionIndent(array $items, int $index): int
|
||||
{
|
||||
if (self::ALIGN_LEFT !== $this->align) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find last tagged line:
|
||||
$item = null;
|
||||
for (; $index >= 0; --$index) {
|
||||
$item = $items[$index];
|
||||
if (null !== $item['tag']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No last tag found — no indent:
|
||||
if (null === $item) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Indent according to existing values:
|
||||
return
|
||||
$this->getSentenceIndent($item['static']) +
|
||||
$this->getSentenceIndent($item['tag']) +
|
||||
$this->getSentenceIndent($item['hint']) +
|
||||
$this->getSentenceIndent($item['var']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indent for sentence.
|
||||
*/
|
||||
private function getSentenceIndent(?string $sentence): int
|
||||
{
|
||||
if (null === $sentence) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$length = \strlen($sentence);
|
||||
|
||||
return 0 === $length ? 0 : $length + 1;
|
||||
}
|
||||
}
|
||||
Vendored
+134
@@ -0,0 +1,134 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
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 PhpdocAnnotationWithoutDotFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $tags = ['throws', 'return', 'param', 'internal', 'deprecated', 'var', 'type'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPDoc annotation descriptions should not be a sentence.',
|
||||
[new CodeSample('<?php
|
||||
/**
|
||||
* @param string $bar Some string.
|
||||
*/
|
||||
function foo ($bar) {}
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocToCommentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$annotations = $doc->getAnnotations();
|
||||
|
||||
if (0 === \count($annotations)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
if (
|
||||
!$annotation->getTag()->valid() || !\in_array($annotation->getTag()->getName(), $this->tags, true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lineAfterAnnotation = $doc->getLine($annotation->getEnd() + 1);
|
||||
if (null !== $lineAfterAnnotation) {
|
||||
$lineAfterAnnotationTrimmed = ltrim($lineAfterAnnotation->getContent());
|
||||
if ('' === $lineAfterAnnotationTrimmed || !str_starts_with($lineAfterAnnotationTrimmed, '*')) {
|
||||
// malformed PHPDoc, missing asterisk !
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$content = $annotation->getContent();
|
||||
|
||||
if (
|
||||
1 !== Preg::match('/[.。]\h*$/u', $content)
|
||||
|| 0 !== Preg::match('/[.。](?!\h*$)/u', $content, $matches)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endLine = $doc->getLine($annotation->getEnd());
|
||||
$endLine->setContent(Preg::replace('/(?<![.。])[.。]\h*(\H+)$/u', '\1', $endLine->getContent()));
|
||||
|
||||
$startLine = $doc->getLine($annotation->getStart());
|
||||
$optionalTypeRegEx = $annotation->supportTypes()
|
||||
? sprintf('(?:%s\s+(?:\$\w+\s+)?)?', preg_quote(implode('|', $annotation->getTypes()), '/'))
|
||||
: '';
|
||||
$content = Preg::replaceCallback(
|
||||
'/^(\s*\*\s*@\w+\s+'.$optionalTypeRegEx.')(\p{Lu}?(?=\p{Ll}|\p{Zs}))(.*)$/',
|
||||
static function (array $matches): string {
|
||||
return $matches[1].mb_strtolower($matches[2]).$matches[3];
|
||||
},
|
||||
$startLine->getContent(),
|
||||
1
|
||||
);
|
||||
$startLine->setContent($content);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
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;
|
||||
use PhpCsFixer\Utils;
|
||||
|
||||
/**
|
||||
* @author Ceeram <ceeram@cakephp.org>
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class PhpdocIndentFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Docblocks should have the same indentation as the documented subject.',
|
||||
[new CodeSample('<?php
|
||||
class DocBlocks
|
||||
{
|
||||
/**
|
||||
* Test constants
|
||||
*/
|
||||
const INDENT = 1;
|
||||
}
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
|
||||
* Must run after IndentationTypeFixer, PhpdocToCommentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
// skip if there is no next token or if next token is block end `}`
|
||||
if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $index - 1;
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
|
||||
// ignore inline docblocks
|
||||
if (
|
||||
$prevToken->isGivenKind(T_OPEN_TAG)
|
||||
|| ($prevToken->isWhitespace(" \t") && !$tokens[$index - 2]->isGivenKind(T_OPEN_TAG))
|
||||
|| $prevToken->equalsAny([';', ',', '{', '('])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$nextIndex - 1]->isWhitespace()) {
|
||||
$indent = Utils::calculateTrailingWhitespaceIndent($tokens[$nextIndex - 1]);
|
||||
} else {
|
||||
$indent = '';
|
||||
}
|
||||
|
||||
$newPrevContent = $this->fixWhitespaceBeforeDocblock($prevToken->getContent(), $indent);
|
||||
|
||||
if ('' !== $newPrevContent) {
|
||||
if ($prevToken->isArray()) {
|
||||
$tokens[$prevIndex] = new Token([$prevToken->getId(), $newPrevContent]);
|
||||
} else {
|
||||
$tokens[$prevIndex] = new Token($newPrevContent);
|
||||
}
|
||||
} else {
|
||||
$tokens->clearAt($prevIndex);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $this->fixDocBlock($token->getContent(), $indent)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix indentation of Docblock.
|
||||
*
|
||||
* @param string $content Docblock contents
|
||||
* @param string $indent Indentation to apply
|
||||
*
|
||||
* @return string Dockblock contents including correct indentation
|
||||
*/
|
||||
private function fixDocBlock(string $content, string $indent): string
|
||||
{
|
||||
return ltrim(Preg::replace('/^\h*\*/m', $indent.' *', $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content Whitespace before Docblock
|
||||
* @param string $indent Indentation of the documented subject
|
||||
*
|
||||
* @return string Whitespace including correct indentation for Dockblock after this whitespace
|
||||
*/
|
||||
private function fixWhitespaceBeforeDocblock(string $content, string $indent): string
|
||||
{
|
||||
return rtrim($content, " \t").$indent;
|
||||
}
|
||||
}
|
||||
Vendored
+121
@@ -0,0 +1,121 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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;
|
||||
|
||||
final class PhpdocInlineTagNormalizerFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Fixes PHPDoc inline tags.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @{TUTORIAL}\n * {{ @link }}\n * @inheritDoc\n */\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @{TUTORIAL}\n * {{ @link }}\n * @inheritDoc\n */\n",
|
||||
['tags' => ['TUTORIAL']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (0 === \count($this->configuration['tags'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move `@` inside tag, for example @{tag} -> {@tag}, replace multiple curly brackets,
|
||||
// remove spaces between '{' and '@', remove white space between end
|
||||
// of text and closing bracket and between the tag and inline comment.
|
||||
$content = Preg::replaceCallback(
|
||||
sprintf(
|
||||
'#(?:@{+|{+\h*@)\h*(%s)s?([^}]*)(?:}+)#i',
|
||||
implode('|', array_map(static function (string $tag): string {
|
||||
return preg_quote($tag, '/');
|
||||
}, $this->configuration['tags']))
|
||||
),
|
||||
static function (array $matches): string {
|
||||
$doc = trim($matches[2]);
|
||||
|
||||
if ('' === $doc) {
|
||||
return '{@'.$matches[1].'}';
|
||||
}
|
||||
|
||||
return '{@'.$matches[1].' '.$doc.'}';
|
||||
},
|
||||
$token->getContent()
|
||||
);
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $content]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('tags', 'The list of tags to normalize'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault(['example', 'id', 'internal', 'inheritdoc', 'inheritdocs', 'link', 'source', 'toc', 'tutorial'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
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\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Gert de Pagter <BackEndTea@gmail.com>
|
||||
*/
|
||||
final class PhpdocLineSpanFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only.',
|
||||
[
|
||||
new CodeSample("<?php\n\nclass Foo{\n /** @var bool */\n public \$var;\n}\n"),
|
||||
new CodeSample(
|
||||
"<?php\n\nclass Foo{\n /**\n * @var bool\n */\n public \$var;\n}\n",
|
||||
['property' => 'single']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('const', 'Whether const blocks should be single or multi line'))
|
||||
->setAllowedValues(['single', 'multi', null])
|
||||
->setDefault('multi')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('property', 'Whether property doc blocks should be single or multi line'))
|
||||
->setAllowedValues(['single', 'multi', null])
|
||||
->setDefault('multi')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('method', 'Whether method doc blocks should be single or multi line'))
|
||||
->setAllowedValues(['single', 'multi', null])
|
||||
->setDefault('multi')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
foreach ($analyzer->getClassyElements() as $index => $element) {
|
||||
if (!$this->hasDocBlock($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $element['type'];
|
||||
|
||||
if (!isset($this->configuration[$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docIndex = $this->getDocBlockIndex($tokens, $index);
|
||||
$doc = new DocBlock($tokens[$docIndex]->getContent());
|
||||
|
||||
if ('multi' === $this->configuration[$type]) {
|
||||
$doc->makeMultiLine(WhitespacesAnalyzer::detectIndent($tokens, $docIndex), $this->whitespacesConfig->getLineEnding());
|
||||
} elseif ('single' === $this->configuration[$type]) {
|
||||
$doc->makeSingleLine();
|
||||
}
|
||||
|
||||
$tokens->offsetSet($docIndex, new Token([T_DOC_COMMENT, $doc->getContent()]));
|
||||
}
|
||||
}
|
||||
|
||||
private function hasDocBlock(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $index);
|
||||
|
||||
return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
private function getDocBlockIndex(Tokens $tokens, int $index): int
|
||||
{
|
||||
$propertyPartKinds = [
|
||||
T_PUBLIC,
|
||||
T_PROTECTED,
|
||||
T_PRIVATE,
|
||||
T_FINAL,
|
||||
T_ABSTRACT,
|
||||
T_COMMENT,
|
||||
T_VAR,
|
||||
T_STATIC,
|
||||
T_STRING,
|
||||
T_NS_SEPARATOR,
|
||||
CT::T_ARRAY_TYPEHINT,
|
||||
CT::T_NULLABLE_TYPE,
|
||||
];
|
||||
|
||||
if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required
|
||||
$propertyPartKinds[] = T_ATTRIBUTE;
|
||||
}
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$propertyPartKinds[] = T_READONLY;
|
||||
}
|
||||
|
||||
do {
|
||||
$index = $tokens->getPrevNonWhitespace($index);
|
||||
|
||||
if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
|
||||
$index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]);
|
||||
}
|
||||
} while ($tokens[$index]->isGivenKind($propertyPartKinds));
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocNoAccessFixer extends AbstractProxyFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'`@access` annotations should be omitted from PHPDoc.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
* @access private
|
||||
*/
|
||||
private $bar;
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, PhpdocSeparationFixer, PhpdocTrimFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$fixer = new GeneralPhpdocAnnotationRemoveFixer();
|
||||
$fixer->configure(['annotations' => ['access']]);
|
||||
|
||||
return [$fixer];
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
|
||||
use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Case-sensitive tag replace fixer (does not process inline tags like {@inheritdoc}).
|
||||
*
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocNoAliasTagFixer extends AbstractProxyFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'No alias PHPDoc tags should be used.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @property string $foo
|
||||
* @property-read string $bar
|
||||
*
|
||||
* @link baz
|
||||
*/
|
||||
final class Example
|
||||
{
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @property string $foo
|
||||
* @property-read string $bar
|
||||
*
|
||||
* @link baz
|
||||
*/
|
||||
final class Example
|
||||
{
|
||||
}
|
||||
',
|
||||
['replacements' => ['link' => 'website']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocSingleLineVarSpacingFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
/** @var GeneralPhpdocTagRenameFixer $generalPhpdocTagRenameFixer */
|
||||
$generalPhpdocTagRenameFixer = $this->proxyFixers['general_phpdoc_tag_rename'];
|
||||
|
||||
try {
|
||||
$generalPhpdocTagRenameFixer->configure([
|
||||
'fix_annotation' => true,
|
||||
'fix_inline' => false,
|
||||
'replacements' => $this->configuration['replacements'],
|
||||
'case_sensitive' => true,
|
||||
]);
|
||||
} catch (InvalidConfigurationException $exception) {
|
||||
throw new InvalidFixerConfigurationException(
|
||||
$this->getName(),
|
||||
Preg::replace('/^\[.+?\] /', '', $exception->getMessage()),
|
||||
$exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('replacements', 'Mapping between replaced annotations with new ones.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault([
|
||||
'property-read' => 'property',
|
||||
'property-write' => 'property',
|
||||
'type' => 'var',
|
||||
'link' => 'see',
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
return [new GeneralPhpdocTagRenameFixer()];
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class PhpdocNoEmptyReturnFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'`@return void` and `@return null` annotations should be omitted from PHPDoc.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
function foo() {}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
function foo() {}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, PhpdocOrderFixer, PhpdocSeparationFixer, PhpdocTrimFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer, VoidReturnFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$annotations = $doc->getAnnotationsOfType('return');
|
||||
|
||||
if (0 === \count($annotations)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
$this->fixAnnotation($annotation);
|
||||
}
|
||||
|
||||
$newContent = $doc->getContent();
|
||||
|
||||
if ($newContent === $token->getContent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' === $newContent) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove `return void` or `return null` annotations.
|
||||
*/
|
||||
private function fixAnnotation(Annotation $annotation): void
|
||||
{
|
||||
$types = $annotation->getNormalizedTypes();
|
||||
|
||||
if (1 === \count($types) && ('null' === $types[0] || 'void' === $types[0])) {
|
||||
$annotation->remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocNoPackageFixer extends AbstractProxyFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'`@package` and `@subpackage` annotations should be omitted from PHPDoc.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @internal
|
||||
* @package Foo
|
||||
* subpackage Bar
|
||||
*/
|
||||
class Baz
|
||||
{
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, PhpdocSeparationFixer, PhpdocTrimFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$fixer = new GeneralPhpdocAnnotationRemoveFixer();
|
||||
$fixer->configure(['annotations' => ['package', 'subpackage']]);
|
||||
|
||||
return [$fixer];
|
||||
}
|
||||
}
|
||||
Vendored
+162
@@ -0,0 +1,162 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Remove inheritdoc tags from classy that does not inherit.
|
||||
*/
|
||||
final class PhpdocNoUselessInheritdocFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Classy that does not inherit must not have `@inheritdoc` tags.',
|
||||
[
|
||||
new CodeSample("<?php\n/** {@inheritdoc} */\nclass Sample\n{\n}\n"),
|
||||
new CodeSample("<?php\nclass Sample\n{\n /**\n * @inheritdoc\n */\n public function Test()\n {\n }\n}\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, NoTrailingWhitespaceInCommentFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
// min. offset 4 as minimal candidate is @: <?php\n/** @inheritdoc */class min{}
|
||||
for ($index = 1, $count = \count($tokens) - 4; $index < $count; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind([T_CLASS, T_INTERFACE])) {
|
||||
$index = $this->fixClassy($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixClassy(Tokens $tokens, int $index): int
|
||||
{
|
||||
// figure out where the classy starts
|
||||
$classOpenIndex = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
|
||||
// figure out where the classy ends
|
||||
$classEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenIndex);
|
||||
|
||||
// is classy extending or implementing some interface
|
||||
$extendingOrImplementing = $this->isExtendingOrImplementing($tokens, $index, $classOpenIndex);
|
||||
|
||||
if (!$extendingOrImplementing) {
|
||||
// PHPDoc of classy should not have inherit tag even when using traits as Traits cannot provide this information
|
||||
$this->fixClassyOutside($tokens, $index);
|
||||
}
|
||||
|
||||
// figure out if the classy uses a trait
|
||||
if (!$extendingOrImplementing && $this->isUsingTrait($tokens, $index, $classOpenIndex, $classEndIndex)) {
|
||||
$extendingOrImplementing = true;
|
||||
}
|
||||
|
||||
$this->fixClassyInside($tokens, $classOpenIndex, $classEndIndex, !$extendingOrImplementing);
|
||||
|
||||
return $classEndIndex;
|
||||
}
|
||||
|
||||
private function fixClassyInside(Tokens $tokens, int $classOpenIndex, int $classEndIndex, bool $fixThisLevel): void
|
||||
{
|
||||
for ($i = $classOpenIndex; $i < $classEndIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind(T_CLASS)) {
|
||||
$i = $this->fixClassy($tokens, $i);
|
||||
} elseif ($fixThisLevel && $tokens[$i]->isGivenKind(T_DOC_COMMENT)) {
|
||||
$this->fixToken($tokens, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixClassyOutside(Tokens $tokens, int $classIndex): void
|
||||
{
|
||||
$previousIndex = $tokens->getPrevNonWhitespace($classIndex);
|
||||
if ($tokens[$previousIndex]->isGivenKind(T_DOC_COMMENT)) {
|
||||
$this->fixToken($tokens, $previousIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixToken(Tokens $tokens, int $tokenIndex): void
|
||||
{
|
||||
$count = 0;
|
||||
$content = Preg::replaceCallback(
|
||||
'#(\h*(?:@{*|{*\h*@)\h*inheritdoc\h*)([^}]*)((?:}*)\h*)#i',
|
||||
static function (array $matches): string {
|
||||
return ' '.$matches[2];
|
||||
},
|
||||
$tokens[$tokenIndex]->getContent(),
|
||||
-1,
|
||||
$count
|
||||
);
|
||||
|
||||
if ($count) {
|
||||
$tokens[$tokenIndex] = new Token([T_DOC_COMMENT, $content]);
|
||||
}
|
||||
}
|
||||
|
||||
private function isExtendingOrImplementing(Tokens $tokens, int $classIndex, int $classOpenIndex): bool
|
||||
{
|
||||
for ($index = $classIndex; $index < $classOpenIndex; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isUsingTrait(Tokens $tokens, int $classIndex, int $classOpenIndex, int $classCloseIndex): bool
|
||||
{
|
||||
if ($tokens[$classIndex]->isGivenKind(T_INTERFACE)) {
|
||||
// cannot use Trait inside an interface
|
||||
return false;
|
||||
}
|
||||
|
||||
$useIndex = $tokens->getNextTokenOfKind($classOpenIndex, [[CT::T_USE_TRAIT]]);
|
||||
|
||||
return null !== $useIndex && $useIndex < $classCloseIndex;
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
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;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
* @author Andreas Möller <am@localheinz.com>
|
||||
*/
|
||||
final class PhpdocOrderByValueFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Order phpdoc tags by value.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @covers Foo
|
||||
* @covers Bar
|
||||
*/
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @author Bob
|
||||
* @author Alice
|
||||
*/
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{}
|
||||
',
|
||||
[
|
||||
'annotations' => [
|
||||
'author',
|
||||
],
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpUnitFqcnAnnotationFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if ([] === $this->configuration['annotations']) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
foreach ($this->configuration['annotations'] as $type => $typeLowerCase) {
|
||||
$findPattern = sprintf(
|
||||
'/@%s\s.+@%s\s/s',
|
||||
$type,
|
||||
$type
|
||||
);
|
||||
|
||||
if (
|
||||
!$tokens[$index]->isGivenKind(T_DOC_COMMENT)
|
||||
|| 0 === Preg::match($findPattern, $tokens[$index]->getContent())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docBlock = new DocBlock($tokens[$index]->getContent());
|
||||
|
||||
$annotations = $docBlock->getAnnotationsOfType($type);
|
||||
$annotationMap = [];
|
||||
|
||||
if (\in_array($type, ['property', 'property-read', 'property-write'], true)) {
|
||||
$replacePattern = sprintf(
|
||||
'/(?s)\*\s*@%s\s+(?P<optionalTypes>.+\s+)?\$(?P<comparableContent>[^\s]+).*/',
|
||||
$type
|
||||
);
|
||||
|
||||
$replacement = '\2';
|
||||
} elseif ('method' === $type) {
|
||||
$replacePattern = '/(?s)\*\s*@method\s+(?P<optionalReturnTypes>.+\s+)?(?P<comparableContent>.+)\(.*/';
|
||||
$replacement = '\2';
|
||||
} else {
|
||||
$replacePattern = sprintf(
|
||||
'/\*\s*@%s\s+(?P<comparableContent>.+)/',
|
||||
$typeLowerCase
|
||||
);
|
||||
|
||||
$replacement = '\1';
|
||||
}
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
$rawContent = $annotation->getContent();
|
||||
|
||||
$comparableContent = Preg::replace(
|
||||
$replacePattern,
|
||||
$replacement,
|
||||
strtolower(trim($rawContent))
|
||||
);
|
||||
|
||||
$annotationMap[$comparableContent] = $rawContent;
|
||||
}
|
||||
|
||||
$orderedAnnotationMap = $annotationMap;
|
||||
|
||||
ksort($orderedAnnotationMap, SORT_STRING);
|
||||
|
||||
if ($orderedAnnotationMap === $annotationMap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines = $docBlock->getLines();
|
||||
|
||||
foreach (array_reverse($annotations) as $annotation) {
|
||||
array_splice(
|
||||
$lines,
|
||||
$annotation->getStart(),
|
||||
$annotation->getEnd() - $annotation->getStart() + 1,
|
||||
array_pop($orderedAnnotationMap)
|
||||
);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, implode('', $lines)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$allowedValues = [
|
||||
'author',
|
||||
'covers',
|
||||
'coversNothing',
|
||||
'dataProvider',
|
||||
'depends',
|
||||
'group',
|
||||
'internal',
|
||||
'method',
|
||||
'mixin',
|
||||
'property',
|
||||
'property-read',
|
||||
'property-write',
|
||||
'requires',
|
||||
'throws',
|
||||
'uses',
|
||||
];
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('annotations', 'List of annotations to order, e.g. `["covers"]`.'))
|
||||
->setAllowedTypes([
|
||||
'array',
|
||||
])
|
||||
->setAllowedValues([
|
||||
new AllowedValueSubset($allowedValues),
|
||||
])
|
||||
->setNormalizer(static function (Options $options, $value): array {
|
||||
$normalized = [];
|
||||
|
||||
foreach ($value as $annotation) {
|
||||
// since we will be using "strtolower" on the input annotations when building the sorting
|
||||
// map we must match the type in lower case as well
|
||||
$normalized[$annotation] = strtolower($annotation);
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
})
|
||||
->setDefault([
|
||||
'covers',
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Jakub Kwaśniewski <jakub@zero-85.pl>
|
||||
*/
|
||||
final class PhpdocOrderFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @const string[]
|
||||
*
|
||||
* @TODO: 4.0 - change default to ['param', 'return', 'throws']
|
||||
*/
|
||||
private const ORDER_DEFAULT = ['param', 'throws', 'return'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
$code = <<<'EOF'
|
||||
<?php
|
||||
/**
|
||||
* Hello there!
|
||||
*
|
||||
* @throws Exception|RuntimeException foo
|
||||
* @custom Test!
|
||||
* @return int Return the number of changes.
|
||||
* @param string $foo
|
||||
* @param bool $bar Bar
|
||||
*/
|
||||
|
||||
EOF;
|
||||
|
||||
return new FixerDefinition(
|
||||
'Annotations in PHPDoc should be ordered in defined sequence.',
|
||||
[
|
||||
new CodeSample($code),
|
||||
new CodeSample($code, ['order' => self::ORDER_DEFAULT]),
|
||||
new CodeSample($code, ['order' => ['param', 'return', 'throws']]),
|
||||
new CodeSample($code, ['order' => ['param', 'custom', 'throws', 'return']]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer, PhpdocSeparationFixer, PhpdocTrimFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocNoEmptyReturnFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('order', 'Sequence in which annotations in PHPDoc should be ordered.'))
|
||||
->setAllowedTypes(['string[]'])
|
||||
->setAllowedValues([function ($order) {
|
||||
if (\count($order) < 2) {
|
||||
throw new InvalidOptionsException('The option "order" value is invalid. Minimum two tags are required.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault(self::ORDER_DEFAULT)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// assuming annotations are already grouped by tags
|
||||
$content = $token->getContent();
|
||||
|
||||
// sort annotations
|
||||
$successors = $this->configuration['order'];
|
||||
while (\count($successors) >= 3) {
|
||||
$predecessor = array_shift($successors);
|
||||
$content = $this->moveAnnotationsBefore($predecessor, $successors, $content);
|
||||
}
|
||||
|
||||
// we're parsing the content last time to make sure the internal
|
||||
// state of the docblock is correct after the modifications
|
||||
$predecessors = $this->configuration['order'];
|
||||
$last = array_pop($predecessors);
|
||||
$content = $this->moveAnnotationsAfter($last, $predecessors, $content);
|
||||
|
||||
// persist the content at the end
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $content]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all given annotations in before given set of annotations.
|
||||
*
|
||||
* @param string $move Tag of annotations that should be moved
|
||||
* @param string[] $before Tags of annotations that should moved annotations be placed before
|
||||
*/
|
||||
private function moveAnnotationsBefore(string $move, array $before, string $content): string
|
||||
{
|
||||
$doc = new DocBlock($content);
|
||||
$toBeMoved = $doc->getAnnotationsOfType($move);
|
||||
|
||||
// nothing to do if there are no annotations to be moved
|
||||
if (0 === \count($toBeMoved)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$others = $doc->getAnnotationsOfType($before);
|
||||
|
||||
if (0 === \count($others)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// get the index of the final line of the final toBoMoved annotation
|
||||
$end = end($toBeMoved)->getEnd();
|
||||
|
||||
$line = $doc->getLine($end);
|
||||
|
||||
// move stuff about if required
|
||||
foreach ($others as $other) {
|
||||
if ($other->getStart() < $end) {
|
||||
// we're doing this to maintain the original line indices
|
||||
$line->setContent($line->getContent().$other->getContent());
|
||||
$other->remove();
|
||||
}
|
||||
}
|
||||
|
||||
return $doc->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all given annotations after given set of annotations.
|
||||
*
|
||||
* @param string $move Tag of annotations that should be moved
|
||||
* @param string[] $after Tags of annotations that should moved annotations be placed after
|
||||
*/
|
||||
private function moveAnnotationsAfter(string $move, array $after, string $content): string
|
||||
{
|
||||
$doc = new DocBlock($content);
|
||||
$toBeMoved = $doc->getAnnotationsOfType($move);
|
||||
|
||||
// nothing to do if there are no annotations to be moved
|
||||
if (0 === \count($toBeMoved)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$others = $doc->getAnnotationsOfType($after);
|
||||
|
||||
// nothing to do if there are no other annotations
|
||||
if (0 === \count($others)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// get the index of the first line of the first toBeMoved annotation
|
||||
$start = $toBeMoved[0]->getStart();
|
||||
$line = $doc->getLine($start);
|
||||
|
||||
// move stuff about if required
|
||||
foreach (array_reverse($others) as $other) {
|
||||
if ($other->getEnd() > $start) {
|
||||
// we're doing this to maintain the original line indices
|
||||
$line->setContent($other->getContent().$line->getContent());
|
||||
$other->remove();
|
||||
}
|
||||
}
|
||||
|
||||
return $doc->getContent();
|
||||
}
|
||||
}
|
||||
Vendored
+231
@@ -0,0 +1,231 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static array $toTypes = [
|
||||
'$this',
|
||||
'static',
|
||||
'self',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The type of `@return` annotations of methods returning a reference to itself must the configured one.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
/**
|
||||
* @return this
|
||||
*/
|
||||
public function test1()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $self
|
||||
*/
|
||||
public function test2()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
/**
|
||||
* @return this
|
||||
*/
|
||||
public function test1()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $self
|
||||
*/
|
||||
public function test2()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
',
|
||||
['replacements' => ['this' => 'self']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return \count($tokens) > 10 && $tokens->isAllTokenKindsFound([T_DOC_COMMENT, T_FUNCTION]) && $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
foreach ($tokensAnalyzer->getClassyElements() as $index => $element) {
|
||||
if ('method' === $element['type']) {
|
||||
$this->fixMethod($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$default = [
|
||||
'this' => '$this',
|
||||
'@this' => '$this',
|
||||
'$self' => 'self',
|
||||
'@self' => 'self',
|
||||
'$static' => 'static',
|
||||
'@static' => 'static',
|
||||
];
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('replacements', 'Mapping between replaced return types with new ones.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setNormalizer(static function (Options $options, array $value) use ($default): array {
|
||||
$normalizedValue = [];
|
||||
|
||||
foreach ($value as $from => $to) {
|
||||
if (\is_string($from)) {
|
||||
$from = strtolower($from);
|
||||
}
|
||||
|
||||
if (!isset($default[$from])) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Unknown key "%s", expected any of "%s".',
|
||||
\gettype($from).'#'.$from,
|
||||
implode('", "', array_keys($default))
|
||||
));
|
||||
}
|
||||
|
||||
if (!\in_array($to, self::$toTypes, true)) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Unknown value "%s", expected any of "%s".',
|
||||
\is_object($to) ? \get_class($to) : \gettype($to).(\is_resource($to) ? '' : '#'.$to),
|
||||
implode('", "', self::$toTypes)
|
||||
));
|
||||
}
|
||||
|
||||
$normalizedValue[$from] = $to;
|
||||
}
|
||||
|
||||
return $normalizedValue;
|
||||
})
|
||||
->setDefault($default)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixMethod(Tokens $tokens, int $index): void
|
||||
{
|
||||
static $methodModifiers = [T_STATIC, T_FINAL, T_ABSTRACT, T_PRIVATE, T_PROTECTED, T_PUBLIC];
|
||||
|
||||
// find PHPDoc of method (if any)
|
||||
while (true) {
|
||||
$tokenIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (!$tokens[$tokenIndex]->isGivenKind($methodModifiers)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index = $tokenIndex;
|
||||
}
|
||||
|
||||
$docIndex = $tokens->getPrevNonWhitespace($index);
|
||||
if (!$tokens[$docIndex]->isGivenKind(T_DOC_COMMENT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find @return
|
||||
$docBlock = new DocBlock($tokens[$docIndex]->getContent());
|
||||
$returnsBlock = $docBlock->getAnnotationsOfType('return');
|
||||
|
||||
if (0 === \count($returnsBlock)) {
|
||||
return; // no return annotation found
|
||||
}
|
||||
|
||||
$returnsBlock = $returnsBlock[0];
|
||||
$types = $returnsBlock->getTypes();
|
||||
|
||||
if (0 === \count($types)) {
|
||||
return; // no return type(s) found
|
||||
}
|
||||
|
||||
$newTypes = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$newTypes[] = $this->configuration['replacements'][strtolower($type)] ?? $type;
|
||||
}
|
||||
|
||||
if ($types === $newTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
$returnsBlock->setTypes($newTypes);
|
||||
$tokens[$docIndex] = new Token([T_DOC_COMMENT, $docBlock->getContent()]);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractPhpdocTypesFixer;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class PhpdocScalarFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* The types to fix.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private static array $types = [
|
||||
'boolean' => 'bool',
|
||||
'callback' => 'callable',
|
||||
'double' => 'float',
|
||||
'integer' => 'int',
|
||||
'real' => 'float',
|
||||
'str' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`.',
|
||||
[
|
||||
new CodeSample('<?php
|
||||
/**
|
||||
* @param integer $a
|
||||
* @param boolean $b
|
||||
* @param real $c
|
||||
*
|
||||
* @return double
|
||||
*/
|
||||
function sample($a, $b, $c)
|
||||
{
|
||||
return sample2($a, $b, $c);
|
||||
}
|
||||
'),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param integer $a
|
||||
* @param boolean $b
|
||||
* @param real $c
|
||||
*/
|
||||
function sample($a, $b, $c)
|
||||
{
|
||||
return sample2($a, $b, $c);
|
||||
}
|
||||
',
|
||||
['types' => ['boolean']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
|
||||
* Must run after PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
/*
|
||||
* Should be run before all other docblock fixers apart from the
|
||||
* phpdoc_to_comment and phpdoc_indent fixer to make sure all fixers
|
||||
* apply correct indentation to new code they add. This should run
|
||||
* before alignment of params is done since this fixer might change
|
||||
* the type and thereby un-aligning the params. We also must run after
|
||||
* the phpdoc_types_fixer because it can convert types to things that
|
||||
* we can fix.
|
||||
*/
|
||||
return 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$types = array_keys(self::$types);
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('types', 'A list of types to fix.'))
|
||||
->setAllowedValues([new AllowedValueSubset($types)])
|
||||
->setDefault($types)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function normalize(string $type): string
|
||||
{
|
||||
if (\in_array($type, $this->configuration['types'], true)) {
|
||||
return self::$types[$type];
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\TagComparator;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Jakub Kwaśniewski <jakub@zero-85.pl>
|
||||
*/
|
||||
final class PhpdocSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string[][]
|
||||
*/
|
||||
private array $groups;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
$code = <<<'EOF'
|
||||
<?php
|
||||
/**
|
||||
* Hello there!
|
||||
*
|
||||
* @author John Doe
|
||||
* @custom Test!
|
||||
*
|
||||
* @throws Exception|RuntimeException foo
|
||||
* @param string $foo
|
||||
*
|
||||
* @param bool $bar Bar
|
||||
* @return int Return the number of changes.
|
||||
*/
|
||||
|
||||
EOF;
|
||||
|
||||
return new FixerDefinition(
|
||||
'Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other. Annotations of a different type are separated by a single blank line.',
|
||||
[
|
||||
new CodeSample($code),
|
||||
new CodeSample($code, ['groups' => [...TagComparator::DEFAULT_GROUPS, ['param', 'return']]]),
|
||||
new CodeSample($code, ['groups' => [['author', 'throws', 'custom'], ['return', 'param']]]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->groups = $this->configuration['groups'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$this->fixDescription($doc);
|
||||
$this->fixAnnotations($doc);
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$allowTagToBelongToOnlyOneGroup = function ($groups) {
|
||||
$tags = [];
|
||||
foreach ($groups as $groupIndex => $group) {
|
||||
foreach ($group as $member) {
|
||||
if (isset($tags[$member])) {
|
||||
if ($groupIndex === $tags[$member]) {
|
||||
throw new InvalidOptionsException(
|
||||
'The option "groups" value is invalid. '.
|
||||
'The "'.$member.'" tag is specified more than once.'
|
||||
);
|
||||
}
|
||||
|
||||
throw new InvalidOptionsException(
|
||||
'The option "groups" value is invalid. '.
|
||||
'The "'.$member.'" tag belongs to more than one group.'
|
||||
);
|
||||
}
|
||||
$tags[$member] = $groupIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('groups', 'Sets of annotation types to be grouped together.'))
|
||||
->setAllowedTypes(['string[][]'])
|
||||
->setDefault(TagComparator::DEFAULT_GROUPS)
|
||||
->setAllowedValues([$allowTagToBelongToOnlyOneGroup])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the description is separated from the annotations.
|
||||
*/
|
||||
private function fixDescription(DocBlock $doc): void
|
||||
{
|
||||
foreach ($doc->getLines() as $index => $line) {
|
||||
if ($line->containsATag()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($line->containsUsefulContent()) {
|
||||
$next = $doc->getLine($index + 1);
|
||||
|
||||
if (null !== $next && $next->containsATag()) {
|
||||
$line->addBlank();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the annotations are correctly separated.
|
||||
*/
|
||||
private function fixAnnotations(DocBlock $doc): void
|
||||
{
|
||||
foreach ($doc->getAnnotations() as $index => $annotation) {
|
||||
$next = $doc->getAnnotation($index + 1);
|
||||
|
||||
if (null === $next) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (TagComparator::shouldBeTogether($annotation->getTag(), $next->getTag(), $this->groups)) {
|
||||
$this->ensureAreTogether($doc, $annotation, $next);
|
||||
} else {
|
||||
$this->ensureAreSeparate($doc, $annotation, $next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the given annotations to immediately follow each other.
|
||||
*/
|
||||
private function ensureAreTogether(DocBlock $doc, Annotation $first, Annotation $second): void
|
||||
{
|
||||
$pos = $first->getEnd();
|
||||
$final = $second->getStart();
|
||||
|
||||
for ($pos = $pos + 1; $pos < $final; ++$pos) {
|
||||
$doc->getLine($pos)->remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the given annotations to have one empty line between each other.
|
||||
*/
|
||||
private function ensureAreSeparate(DocBlock $doc, Annotation $first, Annotation $second): void
|
||||
{
|
||||
$pos = $first->getEnd();
|
||||
$final = $second->getStart() - 1;
|
||||
|
||||
// check if we need to add a line, or need to remove one or more lines
|
||||
if ($pos === $final) {
|
||||
$doc->getLine($pos)->addBlank();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for ($pos = $pos + 1; $pos < $final; ++$pos) {
|
||||
$doc->getLine($pos)->remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+98
@@ -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\Phpdoc;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fixer for part of rule defined in PSR5 ¶7.22.
|
||||
*/
|
||||
final class PhpdocSingleLineVarSpacingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Single line `@var` PHPDoc should have proper spacing.',
|
||||
[new CodeSample("<?php /**@var MyClass \$a */\n\$a = test();\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocNoAliasTagFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
/** @var Token $token */
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
$fixedContent = $this->fixTokenContent($content);
|
||||
|
||||
if ($content !== $fixedContent) {
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $fixedContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixTokenContent(string $content): string
|
||||
{
|
||||
return Preg::replaceCallback(
|
||||
'#^/\*\*\h*@var\h+(\S+)\h*(\$\S+)?\h*([^\n]*)\*/$#',
|
||||
static function (array $matches) {
|
||||
$content = '/** @var';
|
||||
|
||||
for ($i = 1, $m = \count($matches); $i < $m; ++$i) {
|
||||
if ('' !== $matches[$i]) {
|
||||
$content .= ' '.$matches[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return rtrim($content).' */';
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\ShortDescription;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class PhpdocSummaryFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPDoc summary should end in either a full stop, exclamation mark, or question mark.',
|
||||
[new CodeSample('<?php
|
||||
/**
|
||||
* Foo function is great
|
||||
*/
|
||||
function foo () {}
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$end = (new ShortDescription($doc))->getEnd();
|
||||
|
||||
if (null !== $end) {
|
||||
$line = $doc->getLine($end);
|
||||
$content = rtrim($line->getContent());
|
||||
|
||||
if (!$this->isCorrectlyFormatted($content)) {
|
||||
$line->setContent($content.'.'.$this->whitespacesConfig->getLineEnding());
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the last line of the short description correctly formatted?
|
||||
*/
|
||||
private function isCorrectlyFormatted(string $content): bool
|
||||
{
|
||||
if (false !== stripos($content, '{@inheritdoc}')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $content !== rtrim($content, '.。!?¡¿!?');
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
|
||||
use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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;
|
||||
|
||||
final class PhpdocTagCasingFixer extends AbstractProxyFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Fixes casing of PHPDoc tags.',
|
||||
[
|
||||
new CodeSample("<?php\n/**\n * @inheritdoc\n */\n"),
|
||||
new CodeSample("<?php\n/**\n * @inheritdoc\n * @Foo\n */\n", [
|
||||
'tags' => ['foo'],
|
||||
]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$replacements = [];
|
||||
foreach ($this->configuration['tags'] as $tag) {
|
||||
$replacements[$tag] = $tag;
|
||||
}
|
||||
|
||||
/** @var GeneralPhpdocTagRenameFixer $generalPhpdocTagRenameFixer */
|
||||
$generalPhpdocTagRenameFixer = $this->proxyFixers['general_phpdoc_tag_rename'];
|
||||
|
||||
try {
|
||||
$generalPhpdocTagRenameFixer->configure([
|
||||
'fix_annotation' => true,
|
||||
'fix_inline' => true,
|
||||
'replacements' => $replacements,
|
||||
'case_sensitive' => false,
|
||||
]);
|
||||
} catch (InvalidConfigurationException $exception) {
|
||||
throw new InvalidFixerConfigurationException(
|
||||
$this->getName(),
|
||||
Preg::replace('/^\[.+?\] /', '', $exception->getMessage()),
|
||||
$exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('tags', 'List of tags to fix with their expected casing.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault(['inheritDoc'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
return [new GeneralPhpdocTagRenameFixer()];
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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\Exception\InvalidOptionsException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
final class PhpdocTagTypeFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
private const TAG_REGEX = '/^(?:
|
||||
(?<tag>
|
||||
(?:@(?<tag_name>.+?)(?:\s.+)?)
|
||||
)
|
||||
|
|
||||
{(?<inlined_tag>
|
||||
(?:@(?<inlined_tag_name>.+?)(?:\s.+)?)
|
||||
)}
|
||||
)$/x';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Forces PHPDoc tags to be either regular annotations or inline.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * {@api}\n */\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @inheritdoc\n */\n",
|
||||
['tags' => ['inheritdoc' => 'inline']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (0 === \count($this->configuration['tags'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$regularExpression = sprintf(
|
||||
'/({?@(?:%s).*?(?:(?=\s\*\/)|(?=\n)}?))/i',
|
||||
implode('|', array_map(
|
||||
static function (string $tag): string {
|
||||
return preg_quote($tag, '/');
|
||||
},
|
||||
array_keys($this->configuration['tags'])
|
||||
))
|
||||
);
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = Preg::split(
|
||||
$regularExpression,
|
||||
$token->getContent(),
|
||||
-1,
|
||||
PREG_SPLIT_DELIM_CAPTURE
|
||||
);
|
||||
|
||||
for ($i = 1, $max = \count($parts) - 1; $i < $max; $i += 2) {
|
||||
if (!Preg::match(self::TAG_REGEX, $parts[$i], $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' !== $matches['tag']) {
|
||||
$tag = $matches['tag'];
|
||||
$tagName = $matches['tag_name'];
|
||||
} else {
|
||||
$tag = $matches['inlined_tag'];
|
||||
$tagName = $matches['inlined_tag_name'];
|
||||
}
|
||||
|
||||
$tagName = strtolower($tagName);
|
||||
if (!isset($this->configuration['tags'][$tagName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('inline' === $this->configuration['tags'][$tagName]) {
|
||||
$parts[$i] = '{'.$tag.'}';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->tagIsSurroundedByText($parts, $i)) {
|
||||
$parts[$i] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, implode('', $parts)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('tags', 'The list of tags to fix'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([static function (array $value): bool {
|
||||
foreach ($value as $type) {
|
||||
if (!\in_array($type, ['annotation', 'inline'], true)) {
|
||||
throw new InvalidOptionsException("Unknown tag type \"{$type}\".");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault([
|
||||
'api' => 'annotation',
|
||||
'author' => 'annotation',
|
||||
'copyright' => 'annotation',
|
||||
'deprecated' => 'annotation',
|
||||
'example' => 'annotation',
|
||||
'global' => 'annotation',
|
||||
'inheritDoc' => 'annotation',
|
||||
'internal' => 'annotation',
|
||||
'license' => 'annotation',
|
||||
'method' => 'annotation',
|
||||
'package' => 'annotation',
|
||||
'param' => 'annotation',
|
||||
'property' => 'annotation',
|
||||
'return' => 'annotation',
|
||||
'see' => 'annotation',
|
||||
'since' => 'annotation',
|
||||
'throws' => 'annotation',
|
||||
'todo' => 'annotation',
|
||||
'uses' => 'annotation',
|
||||
'var' => 'annotation',
|
||||
'version' => 'annotation',
|
||||
])
|
||||
->setNormalizer(static function (Options $options, $value): array {
|
||||
$normalized = [];
|
||||
|
||||
foreach ($value as $tag => $type) {
|
||||
$normalized[strtolower($tag)] = $type;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
})
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $parts
|
||||
*/
|
||||
private function tagIsSurroundedByText(array $parts, int $index): bool
|
||||
{
|
||||
return
|
||||
Preg::match('/(^|\R)\h*[^@\s]\N*/', $this->cleanComment($parts[$index - 1]))
|
||||
|| Preg::match('/^.*?\R\s*[^@\s]/', $this->cleanComment($parts[$index + 1]))
|
||||
;
|
||||
}
|
||||
|
||||
private function cleanComment(string $comment): string
|
||||
{
|
||||
$comment = Preg::replace('/^\/\*\*|\*\/$/', '', $comment);
|
||||
|
||||
return Preg::replace('/(\R)(\h*\*)?\h*/', '$1', $comment);
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Ceeram <ceeram@cakephp.org>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocToCommentFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $ignoredTags = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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, SingleLineCommentSpacingFixer, SingleLineCommentStyleFixer.
|
||||
* Must run after CommentToPhpdocFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
/*
|
||||
* Should be run before all other docblock fixers so that these fixers
|
||||
* don't touch doc comments which are meant to be converted to regular
|
||||
* comments.
|
||||
*/
|
||||
return 25;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Docblocks should only be used on structural elements.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$first = true;// needed because by default first docblock is never fixed.
|
||||
|
||||
/** This should be a comment */
|
||||
foreach($connections as $key => $sqlite) {
|
||||
$sqlite->open($path);
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$first = true;// needed because by default first docblock is never fixed.
|
||||
|
||||
/** This should be a comment */
|
||||
foreach($connections as $key => $sqlite) {
|
||||
$sqlite->open($path);
|
||||
}
|
||||
|
||||
/** @todo This should be a PHPDoc as the tag is on "ignored_tags" list */
|
||||
foreach($connections as $key => $sqlite) {
|
||||
$sqlite->open($path);
|
||||
}
|
||||
',
|
||||
['ignored_tags' => ['todo']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration = null): 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 (matched case insensitively)'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$commentsAnalyzer = new CommentsAnalyzer();
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($commentsAnalyzer->isHeaderComment($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 < Preg::matchAll('~\@([a-zA-Z0-9_\\\\-]+)\b~', $token->getContent(), $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
if (\in_array(strtolower($match), $this->ignoredTags, true)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_COMMENT, '/*'.ltrim($token->getContent(), '/*')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
use PhpCsFixer\DocBlock\ShortDescription;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Nobu Funaki <nobu.funaki@gmail.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocTrimConsecutiveBlankLineSeparationFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes extra blank lines after summary and after description in PHPDoc.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* Summary.
|
||||
*
|
||||
*
|
||||
* Description that contain 4 lines,
|
||||
*
|
||||
*
|
||||
* while 2 of them are blank!
|
||||
*
|
||||
*
|
||||
* @param string $foo
|
||||
*
|
||||
*
|
||||
* @dataProvider provideFixCases
|
||||
*/
|
||||
function fnc($foo) {}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -41;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$summaryEnd = (new ShortDescription($doc))->getEnd();
|
||||
|
||||
if (null !== $summaryEnd) {
|
||||
$this->fixSummary($doc, $summaryEnd);
|
||||
$this->fixDescription($doc, $summaryEnd);
|
||||
}
|
||||
|
||||
$this->fixAllTheRest($doc);
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixSummary(DocBlock $doc, int $summaryEnd): void
|
||||
{
|
||||
$nonBlankLineAfterSummary = $this->findNonBlankLine($doc, $summaryEnd);
|
||||
|
||||
$this->removeExtraBlankLinesBetween($doc, $summaryEnd, $nonBlankLineAfterSummary);
|
||||
}
|
||||
|
||||
private function fixDescription(DocBlock $doc, int $summaryEnd): void
|
||||
{
|
||||
$annotationStart = $this->findFirstAnnotationOrEnd($doc);
|
||||
|
||||
// assuming the end of the Description appears before the first Annotation
|
||||
$descriptionEnd = $this->reverseFindLastUsefulContent($doc, $annotationStart);
|
||||
|
||||
if (null === $descriptionEnd || $summaryEnd === $descriptionEnd) {
|
||||
return; // no Description
|
||||
}
|
||||
|
||||
if ($annotationStart === \count($doc->getLines()) - 1) {
|
||||
return; // no content after Description
|
||||
}
|
||||
|
||||
$this->removeExtraBlankLinesBetween($doc, $descriptionEnd, $annotationStart);
|
||||
}
|
||||
|
||||
private function fixAllTheRest(DocBlock $doc): void
|
||||
{
|
||||
$annotationStart = $this->findFirstAnnotationOrEnd($doc);
|
||||
$lastLine = $this->reverseFindLastUsefulContent($doc, \count($doc->getLines()) - 1);
|
||||
|
||||
if (null !== $lastLine && $annotationStart !== $lastLine) {
|
||||
$this->removeExtraBlankLinesBetween($doc, $annotationStart, $lastLine);
|
||||
}
|
||||
}
|
||||
|
||||
private function removeExtraBlankLinesBetween(DocBlock $doc, int $from, int $to): void
|
||||
{
|
||||
for ($index = $from + 1; $index < $to; ++$index) {
|
||||
$line = $doc->getLine($index);
|
||||
$next = $doc->getLine($index + 1);
|
||||
$this->removeExtraBlankLine($line, $next);
|
||||
}
|
||||
}
|
||||
|
||||
private function removeExtraBlankLine(Line $current, Line $next): void
|
||||
{
|
||||
if (!$current->isTheEnd() && !$current->containsUsefulContent()
|
||||
&& !$next->isTheEnd() && !$next->containsUsefulContent()) {
|
||||
$current->remove();
|
||||
}
|
||||
}
|
||||
|
||||
private function findNonBlankLine(DocBlock $doc, int $after): ?int
|
||||
{
|
||||
foreach ($doc->getLines() as $index => $line) {
|
||||
if ($index <= $after) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($line->containsATag() || $line->containsUsefulContent() || $line->isTheEnd()) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findFirstAnnotationOrEnd(DocBlock $doc): int
|
||||
{
|
||||
$index = null;
|
||||
foreach ($doc->getLines() as $index => $line) {
|
||||
if ($line->containsATag()) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $index; // no Annotation, return the last line
|
||||
}
|
||||
|
||||
private function reverseFindLastUsefulContent(DocBlock $doc, int $from): ?int
|
||||
{
|
||||
for ($index = $from - 1; $index >= 0; --$index) {
|
||||
if ($doc->getLine($index)->containsUsefulContent()) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
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 Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class PhpdocTrimFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPDoc should start and end with content, excluding the very first and last line of the docblocks.',
|
||||
[new CodeSample('<?php
|
||||
/**
|
||||
*
|
||||
* Foo must be final class.
|
||||
*
|
||||
*
|
||||
*/
|
||||
final class Foo {}
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpUnitTestAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
$content = $this->fixStart($content);
|
||||
// we need re-parse the docblock after fixing the start before
|
||||
// fixing the end in order for the lines to be correctly indexed
|
||||
$content = $this->fixEnd($content);
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $content]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the first useful line starts immediately after the first line.
|
||||
*/
|
||||
private function fixStart(string $content): string
|
||||
{
|
||||
return Preg::replace(
|
||||
'~
|
||||
(^/\*\*) # DocComment begin
|
||||
(?:
|
||||
\R\h*(?:\*\h*)? # lines without useful content
|
||||
(?!\R\h*\*/) # not followed by a DocComment end
|
||||
)+
|
||||
(\R\h*(?:\*\h*)?\S) # first line with useful content
|
||||
~x',
|
||||
'$1$2',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the last useful line is immediately before the final line.
|
||||
*/
|
||||
private function fixEnd(string $content): string
|
||||
{
|
||||
return Preg::replace(
|
||||
'~
|
||||
(\R\h*(?:\*\h*)?\S.*?) # last line with useful content
|
||||
(?:
|
||||
(?<!/\*\*) # not preceded by a DocComment start
|
||||
\R\h*(?:\*\h*)? # lines without useful content
|
||||
)+
|
||||
(\R\h*\*/$) # DocComment end
|
||||
~xu',
|
||||
'$1$2',
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractPhpdocTypesFixer;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* Available types, grouped.
|
||||
*
|
||||
* @var array<string,string[]>
|
||||
*/
|
||||
private const POSSIBLE_TYPES = [
|
||||
'simple' => [
|
||||
'array',
|
||||
'bool',
|
||||
'callable',
|
||||
'float',
|
||||
'int',
|
||||
'iterable',
|
||||
'null',
|
||||
'object',
|
||||
'string',
|
||||
],
|
||||
'alias' => [
|
||||
'boolean',
|
||||
'callback',
|
||||
'double',
|
||||
'integer',
|
||||
'real',
|
||||
],
|
||||
'meta' => [
|
||||
'$this',
|
||||
'false',
|
||||
'mixed',
|
||||
'parent',
|
||||
'resource',
|
||||
'scalar',
|
||||
'self',
|
||||
'static',
|
||||
'true',
|
||||
'void',
|
||||
],
|
||||
];
|
||||
|
||||
private string $patternToFix = '';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$typesToFix = array_merge(...array_map(static function (string $group): array {
|
||||
return self::POSSIBLE_TYPES[$group];
|
||||
}, $this->configuration['groups']));
|
||||
|
||||
$this->patternToFix = sprintf(
|
||||
'/(?<![a-zA-Z0-9_\x80-\xff]\\\\)(\b|.(?=\$))(%s)\b(?!\\\\)/i',
|
||||
implode(
|
||||
'|',
|
||||
array_map(
|
||||
static function (string $type): string {
|
||||
return preg_quote($type, '/');
|
||||
},
|
||||
$typesToFix
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The correct case must be used for standard PHP types in PHPDoc.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param STRING|String[] $bar
|
||||
*
|
||||
* @return inT[]
|
||||
*/
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param BOOL $foo
|
||||
*
|
||||
* @return MIXED
|
||||
*/
|
||||
',
|
||||
['groups' => ['simple', 'alias']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
|
||||
* Must run after PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
/*
|
||||
* Should be run before all other docblock fixers apart from the
|
||||
* phpdoc_to_comment and phpdoc_indent fixer to make sure all fixers
|
||||
* apply correct indentation to new code they add. This should run
|
||||
* before alignment of params is done since this fixer might change
|
||||
* the type and thereby un-aligning the params. We also must run before
|
||||
* the phpdoc_scalar_fixer so that it can make changes after us.
|
||||
*/
|
||||
return 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function normalize(string $type): string
|
||||
{
|
||||
return Preg::replaceCallback(
|
||||
$this->patternToFix,
|
||||
function (array $matches): string {
|
||||
return strtolower($matches[0]);
|
||||
},
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$possibleGroups = array_keys(self::POSSIBLE_TYPES);
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('groups', 'Type groups to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset($possibleGroups)])
|
||||
->setDefault($possibleGroups)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\TypeExpression;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
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;
|
||||
|
||||
final class PhpdocTypesOrderFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Sorts PHPDoc types.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param string|null $bar
|
||||
*/
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param null|string $bar
|
||||
*/
|
||||
',
|
||||
['null_adjustment' => 'always_last']
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param null|string|int|\Foo $bar
|
||||
*/
|
||||
',
|
||||
['sort_algorithm' => 'alpha']
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param null|string|int|\Foo $bar
|
||||
*/
|
||||
',
|
||||
[
|
||||
'sort_algorithm' => 'alpha',
|
||||
'null_adjustment' => 'always_last',
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/**
|
||||
* @param null|string|int|\Foo $bar
|
||||
*/
|
||||
',
|
||||
[
|
||||
'sort_algorithm' => 'alpha',
|
||||
'null_adjustment' => 'none',
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('sort_algorithm', 'The sorting algorithm to apply.'))
|
||||
->setAllowedValues(['alpha', 'none'])
|
||||
->setDefault('alpha')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('null_adjustment', 'Forces the position of `null` (overrides `sort_algorithm`).'))
|
||||
->setAllowedValues(['always_first', 'always_last', 'none'])
|
||||
->setDefault('always_first')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
$annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes());
|
||||
|
||||
if (0 === \count($annotations)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
// fix main types
|
||||
$annotation->setTypes(
|
||||
$this->sortTypes(
|
||||
$annotation->getTypeExpression()
|
||||
)
|
||||
);
|
||||
|
||||
// fix @method parameters types
|
||||
$line = $doc->getLine($annotation->getStart());
|
||||
$line->setContent(Preg::replaceCallback('/(@method\s+.+?\s+\w+\()(.*)\)/', function (array $matches) {
|
||||
$sorted = Preg::replaceCallback('/([^\s,]+)([\s]+\$[^\s,]+)/', function (array $matches): string {
|
||||
return $this->sortJoinedTypes($matches[1]).$matches[2];
|
||||
}, $matches[2]);
|
||||
|
||||
return $matches[1].$sorted.')';
|
||||
}, $line->getContent()));
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function sortTypes(TypeExpression $typeExpression): array
|
||||
{
|
||||
$normalizeType = static function (string $type): string {
|
||||
return Preg::replace('/^\\??\\\?/', '', $type);
|
||||
};
|
||||
|
||||
$typeExpression->sortTypes(
|
||||
function (TypeExpression $a, TypeExpression $b) use ($normalizeType): int {
|
||||
$a = $normalizeType($a->toString());
|
||||
$b = $normalizeType($b->toString());
|
||||
$lowerCaseA = strtolower($a);
|
||||
$lowerCaseB = strtolower($b);
|
||||
|
||||
if ('none' !== $this->configuration['null_adjustment']) {
|
||||
if ('null' === $lowerCaseA && 'null' !== $lowerCaseB) {
|
||||
return 'always_last' === $this->configuration['null_adjustment'] ? 1 : -1;
|
||||
}
|
||||
if ('null' !== $lowerCaseA && 'null' === $lowerCaseB) {
|
||||
return 'always_last' === $this->configuration['null_adjustment'] ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ('alpha' === $this->configuration['sort_algorithm']) {
|
||||
return strcasecmp($a, $b);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $typeExpression->getTypes();
|
||||
}
|
||||
|
||||
private function sortJoinedTypes(string $types): string
|
||||
{
|
||||
$typeExpression = new TypeExpression($types, null, []);
|
||||
|
||||
return implode('|', $this->sortTypes($typeExpression));
|
||||
}
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
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 Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class PhpdocVarAnnotationCorrectOrderFixer extends AbstractFixer
|
||||
{
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'`@var` and `@type` annotations must have type and name in the correct order.',
|
||||
[new CodeSample('<?php
|
||||
/** @var $foo int */
|
||||
$foo = 2 + 2;
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false === stripos($token->getContent(), '@var') && false === stripos($token->getContent(), '@type')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newContent = Preg::replace(
|
||||
'/(@(?:type|var)\s*)(\$\S+)(\h+)([^\$](?:[^<\s]|<[^>]*>)*)(\s|\*)/i',
|
||||
'$1$4$3$2$5',
|
||||
$token->getContent()
|
||||
);
|
||||
|
||||
if ($newContent === $token->getContent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$token->getId(), $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
<?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\Phpdoc;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Dave van der Brugge <dmvdbrugge@gmail.com>
|
||||
*/
|
||||
final class PhpdocVarWithoutNameFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'`@var` and `@type` annotations of classy properties should not contain the name.',
|
||||
[new CodeSample('<?php
|
||||
final class Foo
|
||||
{
|
||||
/**
|
||||
* @var int $bar
|
||||
*/
|
||||
public $bar;
|
||||
|
||||
/**
|
||||
* @type $baz float
|
||||
*/
|
||||
public $baz;
|
||||
}
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (null === $nextIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// For people writing "static public $foo" instead of "public static $foo"
|
||||
if ($tokens[$nextIndex]->isGivenKind(T_STATIC)) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
}
|
||||
|
||||
// We want only doc blocks that are for properties and thus have specified access modifiers next
|
||||
$propertyModifierKinds = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$propertyModifierKinds[] = T_READONLY;
|
||||
}
|
||||
|
||||
if (!$tokens[$nextIndex]->isGivenKind($propertyModifierKinds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($token->getContent());
|
||||
|
||||
$firstLevelLines = $this->getFirstLevelLines($doc);
|
||||
$annotations = $doc->getAnnotationsOfType(['type', 'var']);
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
if (isset($firstLevelLines[$annotation->getStart()])) {
|
||||
$this->fixLine($firstLevelLines[$annotation->getStart()]);
|
||||
}
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixLine(Line $line): void
|
||||
{
|
||||
$content = $line->getContent();
|
||||
|
||||
Preg::matchAll('/ \$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $content, $matches);
|
||||
|
||||
if (isset($matches[0][0])) {
|
||||
$line->setContent(str_replace($matches[0][0], '', $content));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Line[]
|
||||
*/
|
||||
private function getFirstLevelLines(DocBlock $docBlock): array
|
||||
{
|
||||
$nested = 0;
|
||||
$lines = $docBlock->getLines();
|
||||
|
||||
foreach ($lines as $index => $line) {
|
||||
$content = $line->getContent();
|
||||
|
||||
if (Preg::match('/\s*\*\s*}$/', $content)) {
|
||||
--$nested;
|
||||
}
|
||||
|
||||
if ($nested > 0) {
|
||||
unset($lines[$index]);
|
||||
}
|
||||
|
||||
if (Preg::match('/\s\{$/', $content)) {
|
||||
++$nested;
|
||||
}
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user