[PHPStan] Enable regex constant rule (#4279)

This commit is contained in:
Tomas Votruba 2020-09-23 11:16:40 +02:00 committed by GitHub
parent 52f02d8c91
commit 7aad4bbf2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 1083 additions and 404 deletions

View File

@ -42,8 +42,8 @@ jobs:
-
name: 'Validate PHPStan Compatibility'
run: |
bin/rector sync-types
bin/rector check-static-type-mappers
bin/rector sync-types --ansi
bin/rector check-static-type-mappers --ansi
-
name: 'PHP Linter'

View File

@ -37,6 +37,11 @@ final class StaticEasyPrefixer
'Doctrine\ORM\Mapping\*',
];
/**
* @var string
*/
private const QUOTED_VALUE_REGEX = '#\'\\\\(\w|@)#';
public static function prefixClass(string $class, string $prefix): string
{
foreach (self::EXCLUDED_NAMESPACES as $excludedNamespace) {
@ -65,7 +70,7 @@ final class StaticEasyPrefixer
public static function unPreSlashQuotedValues(string $content): string
{
return Strings::replace($content, '#\'\\\\(\w|@)#', "'$1");
return Strings::replace($content, self::QUOTED_VALUE_REGEX, "'$1");
}
/**

View File

@ -13,6 +13,11 @@ use Symplify\SmartFileSystem\SmartFileSystem;
final class JetbrainsStubsRenamer
{
/**
* @var string
*/
private const PHP_SUFFIX_COMMA_REGEX = '#\.php\',#m';
/**
* @var SymfonyStyle
*/
@ -67,7 +72,7 @@ final class JetbrainsStubsRenamer
}
$stubsMapContents = $this->smartFileSystem->readFile($stubsMapPath);
$stubsMapContents = Strings::replace($stubsMapContents, '#\.php\',#m', ".stub',");
$stubsMapContents = Strings::replace($stubsMapContents, self::PHP_SUFFIX_COMMA_REGEX, ".stub',");
$this->smartFileSystem->dumpFile($stubsMapPath, $stubsMapContents);
}

View File

@ -38,7 +38,8 @@
"symplify/set-config-resolver": "^8.3.5",
"symplify/smart-file-system": "^8.3.5",
"tracy/tracy": "^2.7",
"webmozart/assert": "^1.8"
"webmozart/assert": "^1.8",
"jean85/pretty-package-versions": "^1.5.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
@ -168,6 +169,7 @@
"Rector\\Caching\\Tests\\": "packages/caching/tests",
"Rector\\CodingStyle\\Tests\\": "rules/coding-style/tests",
"Rector\\Core\\Tests\\": "tests",
"Rector\\DocumentationGenerator\\Tests\\": "packages/documentation-generator/tests",
"Rector\\Decouple\\Tests\\": "rules/decouple/tests",
"Rector\\DeadCode\\Tests\\": "rules/dead-code/tests",
"Rector\\DoctrineCodeQuality\\Tests\\": "rules/doctrine-code-quality/tests",
@ -291,10 +293,10 @@
],
"phpstan": [
"vendor/bin/phpstan analyse --ansi --error-format symplify",
"vendor/bin/phpstan analyse config/set --ansi --error-format symplify"
"vendor/bin/phpstan analyse config --ansi --error-format symplify"
],
"phpstan-config": [
"vendor/bin/phpstan analyse config/set --ansi --error-format symplify"
"vendor/bin/phpstan analyse config --ansi --error-format symplify"
],
"changelog": [
"vendor/bin/changelog-linker dump-merges --in-categories --ansi",

View File

@ -37,23 +37,24 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->defaults()
->public()
->autowire();
->autowire()
->autoconfigure();
$services->load('Rector\Core\\', __DIR__ . '/../src')
->exclude([
__DIR__ . '/../src/Rector/*',
__DIR__ . '/../src/Testing/PHPUnit/*',
__DIR__ . '/../src/RectorDefinition/*',
__DIR__ . '/../src/Exception/*',
__DIR__ . '/../src/DependencyInjection/CompilerPass/*',
__DIR__ . '/../src/DependencyInjection/Loader/*',
__DIR__ . '/../src/PhpParser/Builder/*',
__DIR__ . '/../src/HttpKernel/*',
__DIR__ . '/../src/ValueObject/*',
__DIR__ . '/../src/Configuration/MinimalVersionChecker/*',
__DIR__ . '/../src/Bootstrap/*',
__DIR__ . '/../src/Rector',
__DIR__ . '/../src/Testing/PHPUnit',
__DIR__ . '/../src/RectorDefinition',
__DIR__ . '/../src/Exception',
__DIR__ . '/../src/DependencyInjection/CompilerPass',
__DIR__ . '/../src/DependencyInjection/Loader',
__DIR__ . '/../src/PhpParser/Builder',
__DIR__ . '/../src/HttpKernel',
__DIR__ . '/../src/ValueObject',
__DIR__ . '/../src/Configuration/MinimalVersionChecker',
__DIR__ . '/../src/Bootstrap',
// loaded for PHPStan factory
__DIR__ . '/../src/PHPStan/Type/*',
__DIR__ . '/../src/PHPStan/Type',
]);
$services->set(MinimalVersionChecker::class)
@ -64,11 +65,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(TextDescriptor::class);
$services->set(ParserFactory::class);
$services->set(BuilderFactory::class);
$services->set(CloningVisitor::class);
$services->set(NodeFinder::class);
$services->set(Parser::class)
@ -78,16 +76,12 @@ return static function (ContainerConfigurator $containerConfigurator): void {
->factory([ref(LexerFactory::class), 'create']);
$services->set(Filesystem::class);
$services->set(PrivatesAccessor::class);
$services->set(FinderSanitizer::class);
$services->set(FileSystemFilter::class);
$services->set(ParameterProvider::class);
$services->set(PrivatesCaller::class);
$services->set(FinderSanitizer::class);
$services->set(FileSystemFilter::class);
$services->set(ParameterProvider::class);
$services->set(SmartFileSystem::class);
$services->set(StringFormatConverter::class);
@ -95,10 +89,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->alias(EventDispatcherInterface::class, AutowiredEventDispatcher::class);
$services->set(SmartFileSystem::class);
$services->set(SymfonyStyleFactory::class);
$services->set(SymfonyStyle::class)
->factory([ref(SymfonyStyleFactory::class), 'create']);

View File

@ -11,6 +11,11 @@ use Rector\NodeNameResolver\NodeNameResolver;
final class ClassNodeAnalyzer
{
/**
* @var string
*/
private const ANONYMOUS_CLASS_REGEX = '#AnonymousClass\w+$#';
/**
* @var NodeNameResolver
*/
@ -33,6 +38,6 @@ final class ClassNodeAnalyzer
}
// match PHPStan pattern for anonymous classes
return (bool) Strings::match($className, '#AnonymousClass\w+$#');
return (bool) Strings::match($className, self::ANONYMOUS_CLASS_REGEX);
}
}

View File

@ -14,6 +14,11 @@ final class AttributeAwareTemplateTagValueNode extends TemplateTagValueNode impl
{
use AttributeTrait;
/**
* @var string
*/
private const AS_OF_PREPOSITOIN_REGEX = '#\s+(?<preposition>as|of)\s+#';
/**
* @var string
*/
@ -23,7 +28,7 @@ final class AttributeAwareTemplateTagValueNode extends TemplateTagValueNode impl
{
parent::__construct($name, $typeNode, $description);
$matches = Strings::match($originalContent, '#\s+(?<preposition>as|of)\s+#');
$matches = Strings::match($originalContent, self::AS_OF_PREPOSITOIN_REGEX);
$this->preposition = $matches['preposition'] ?? 'of';
}

View File

@ -14,6 +14,11 @@ final class AttributeAwareUnionTypeNode extends UnionTypeNode implements Attribu
{
use AttributeTrait;
/**
* @var string
*/
private const BRACKET_WRAPPING_REGEX = '#^\((.*?)\)#';
/**
* @var bool
*/
@ -26,7 +31,7 @@ final class AttributeAwareUnionTypeNode extends UnionTypeNode implements Attribu
{
parent::__construct($types);
$this->isWrappedWithBrackets = (bool) Strings::match($originalContent, '#^\((.*?)\)#');
$this->isWrappedWithBrackets = (bool) Strings::match($originalContent, self::BRACKET_WRAPPING_REGEX);
}
/**

View File

@ -18,6 +18,21 @@ use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
abstract class AbstractPhpDocNodeFactory
{
/**
* @var string
*/
private const CLASS_CONST_REGEX = '#::class#';
/**
* @var string
*/
private const OPENING_SPACE_REGEX = '#^\{(?<opening_space>\s+)#';
/**
* @var string
*/
private const CLOSING_SPACE_REGEX = '#(?<closing_space>\s+)\}$#';
/**
* @var NodeAnnotationReader
*/
@ -87,10 +102,10 @@ abstract class AbstractPhpDocNodeFactory
*/
protected function matchCurlyBracketOpeningAndClosingSpace(string $annotationContent): OpeningAndClosingSpace
{
$match = Strings::match($annotationContent, '#^\{(?<opening_space>\s+)#');
$match = Strings::match($annotationContent, self::OPENING_SPACE_REGEX);
$openingSpace = $match['opening_space'] ?? '';
$match = Strings::match($annotationContent, '#(?<closing_space>\s+)\}$#');
$match = Strings::match($annotationContent, self::CLOSING_SPACE_REGEX);
$closingSpace = $match['closing_space'] ?? '';
return new OpeningAndClosingSpace($openingSpace, $closingSpace);
@ -98,6 +113,6 @@ abstract class AbstractPhpDocNodeFactory
private function getCleanedUpTargetEntity(string $targetEntity): string
{
return Strings::replace($targetEntity, '#::class#', '');
return Strings::replace($targetEntity, self::CLASS_CONST_REGEX, '');
}
}

View File

@ -17,6 +17,11 @@ use Rector\Core\Exception\ShouldNotHappenException;
final class TablePhpDocNodeFactory extends AbstractPhpDocNodeFactory implements SpecificPhpDocNodeFactoryInterface
{
/**
* @var string
*/
private const SPACE_BEFORE_CLOSING_BRACKET_REGEX = '#,(\s+)?}$#m';
/**
* @var IndexPhpDocNodeFactory
*/
@ -68,7 +73,7 @@ final class TablePhpDocNodeFactory extends AbstractPhpDocNodeFactory implements
$indexesOpeningAndClosingSpace = $this->matchCurlyBracketOpeningAndClosingSpace($indexesContent);
$haveIndexesFinalComma = (bool) Strings::match($indexesContent, '#,(\s+)?}$#m');
$haveIndexesFinalComma = (bool) Strings::match($indexesContent, self::SPACE_BEFORE_CLOSING_BRACKET_REGEX);
$uniqueConstraintsContent = $this->annotationContentResolver->resolveNestedKey(
$annotationContent,
'uniqueConstraints'
@ -83,7 +88,10 @@ final class TablePhpDocNodeFactory extends AbstractPhpDocNodeFactory implements
$uniqueConstraintsContent
);
$haveUniqueConstraintsFinalComma = (bool) Strings::match($uniqueConstraintsContent, '#,(\s+)?}$#m');
$haveUniqueConstraintsFinalComma = (bool) Strings::match(
$uniqueConstraintsContent,
self::SPACE_BEFORE_CLOSING_BRACKET_REGEX
);
return new TableTagValueNode(
$table->name,

View File

@ -12,6 +12,11 @@ use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
final class AnnotationContentResolver
{
/**
* @var string
*/
private const MULTILINE_COMENT_ASTERISK_REGEX = '#(\s+)\*(\s+)#m';
/**
* @var TokenIteratorFactory
*/
@ -141,7 +146,7 @@ final class AnnotationContentResolver
private function cleanMultilineAnnotationContent(string $annotationContent): string
{
return Strings::replace($annotationContent, '#(\s+)\*(\s+)#m', '$1$3');
return Strings::replace($annotationContent, self::MULTILINE_COMENT_ASTERISK_REGEX, '$1$3');
}
private function tryStartWithKey(string $name, bool $start, TokenIterator $localTokenIterator): bool

View File

@ -34,6 +34,11 @@ use Symplify\PackageBuilder\Reflection\PrivatesCaller;
*/
final class BetterPhpDocParser extends PhpDocParser
{
/**
* @var string
*/
private const TAG_REGEX = '#@(var|param|return|throws|property|deprecated)#';
/**
* @var PhpDocNodeFactoryInterface[]
*/
@ -271,7 +276,7 @@ final class BetterPhpDocParser extends PhpDocParser
$tokenIterator->next();
// basic annotation
if (Strings::match($tag, '#@(var|param|return|throws|property|deprecated)#')) {
if (Strings::match($tag, self::TAG_REGEX)) {
return $tag;
}

View File

@ -16,6 +16,11 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface;
final class MultilineSpaceFormatPreserver
{
/**
* @var string
*/
public const NEWLINE_WITH_SPACE_REGEX = '#\n {1,}$#s';
public function resolveCurrentPhpDocNodeText(Node $node): ?string
{
if ($node instanceof PhpDocTagNode &&
@ -88,7 +93,7 @@ final class MultilineSpaceFormatPreserver
foreach ($newParts as $key => $newPart) {
$newText .= $newPart;
if (isset($oldSpaces[$key])) {
if (Strings::match($oldSpaces[$key][0], '#\n {1,}$#s')) {
if (Strings::match($oldSpaces[$key][0], self::NEWLINE_WITH_SPACE_REGEX)) {
// remove last extra space
$oldSpaces[$key][0] = Strings::substring($oldSpaces[$key][0], 0, -1);
}

View File

@ -10,6 +10,11 @@ use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
final class OriginalSpacingRestorer
{
/**
* @var string
*/
public const WHITESPACE_SPLIT_REGEX = '#\s+(\*)?#';
/**
* @var WhitespaceDetector
*/
@ -39,7 +44,7 @@ final class OriginalSpacingRestorer
$newNodeOutput = '';
// replace system whitespace by old ones, include \n*
$nodeOutputParts = Strings::split($nodeOutput, '#\s+(\*)?#');
$nodeOutputParts = Strings::split($nodeOutput, self::WHITESPACE_SPLIT_REGEX);
$oldWhitespaceCount = count($oldWhitespaces);
$nodeOutputPartCount = count($nodeOutputParts);

View File

@ -26,11 +26,36 @@ use Rector\Core\Exception\ShouldNotHappenException;
*/
final class PhpDocInfoPrinter
{
/**
* @var string
*/
public const CLOSING_DOCBLOCK_REGEX = '#\*\/(\s+)?$#';
/**
* @var string
*/
private const NEWLINE_ASTERISK = PHP_EOL . ' * ';
/**
* @var string
*/
private const OPENING_DOCBLOCK_REGEX = '#^(/\*\*)#';
/**
* @var string
*/
private const CALLABLE_REGEX = '#callable(\s+)\(#';
/**
* @var string
*/
private const DOCBLOCK_START_REGEX = '#^(\/\/|\/\*\*|\/\*|\#)#';
/**
* @var string
*/
private const SPACE_AFTER_ASTERISK_REGEX = '#([^*])\*[ \t]+$#sm';
/**
* @var int
*/
@ -127,7 +152,7 @@ final class PhpDocInfoPrinter
$phpDocString = $this->removeExtraSpacesAfterAsterisk($phpDocString);
// hotfix of extra space with callable ()
return Strings::replace($phpDocString, '#callable(\s+)\(#', 'callable(');
return Strings::replace($phpDocString, self::CALLABLE_REGEX, 'callable(');
}
private function printPhpDocNode(AttributeAwarePhpDocNode $attributeAwarePhpDocNode): string
@ -151,12 +176,15 @@ final class PhpDocInfoPrinter
$output = $this->printEnd($output);
// fix missing start
if (! Strings::match($output, '#^(\/\/|\/\*\*|\/\*|\#)#') && $output) {
if (! Strings::match($output, self::DOCBLOCK_START_REGEX) && $output) {
$output = '/**' . $output;
}
// fix missing end
if (Strings::match($output, '#^(/\*\*)#') && $output && ! Strings::match($output, '#\*\/(\s+)?$#')) {
if (Strings::match($output, self::OPENING_DOCBLOCK_REGEX) && $output && ! Strings::match(
$output,
self::CLOSING_DOCBLOCK_REGEX
)) {
$output .= ' */';
}
@ -165,7 +193,7 @@ final class PhpDocInfoPrinter
private function removeExtraSpacesAfterAsterisk(string $phpDocString): string
{
return Strings::replace($phpDocString, '#([^*])\*[ \t]+$#sm', '$1*');
return Strings::replace($phpDocString, self::SPACE_AFTER_ASTERISK_REGEX, '$1*');
}
private function printNode(

View File

@ -14,6 +14,11 @@ use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
final class WhitespaceDetector
{
/**
* @var string
*/
private const SPACE_BEFORE_ASTERISK_REGEX = '#\s+\*#m';
/**
* @param mixed[] $tokens
* @return string[]
@ -42,7 +47,7 @@ final class WhitespaceDetector
$tokens[$i - 1][1] === Lexer::TOKEN_PHPDOC_EOL
) {
$previousTokenValue = $tokens[$i - 1][0];
if (Strings::match($previousTokenValue, '#\s+\*#m')) {
if (Strings::match($previousTokenValue, self::SPACE_BEFORE_ASTERISK_REGEX)) {
$tokenValue = $previousTokenValue . $tokenValue;
}
}

View File

@ -12,6 +12,16 @@ use Nette\Utils\Strings;
*/
final class ArrayItemStaticHelper
{
/**
* @var string
*/
private const NON_EMPTY_SILENT_KEY_REGEX = '#()|\(\)#';
/**
* @var string
*/
private const ITEM_EQUALS_REGEX = '#(?<item>\w+)(\s+)?=(\s+)?#m';
/**
* @return string[]
*/
@ -24,7 +34,7 @@ final class ArrayItemStaticHelper
$itemsOrder = [];
$matches = Strings::matchAll($content, '#(?<item>\w+)(\s+)?=(\s+)?#m');
$matches = Strings::matchAll($content, self::ITEM_EQUALS_REGEX);
foreach ($matches as $match) {
$itemsOrder[] = $match['item'];
}
@ -84,7 +94,7 @@ final class ArrayItemStaticHelper
private static function isNotEmptyAndHasSilentKey(string $content, ?string $silentKey, array $itemsOrder): bool
{
if (! Strings::match($content, '#()|\(\)#')) {
if (! Strings::match($content, self::NON_EMPTY_SILENT_KEY_REGEX)) {
return false;
}

View File

@ -18,6 +18,26 @@ use Rector\BetterPhpDocParser\ValueObject\TagValueNodeConfiguration;
*/
final class TagValueNodeConfigurationFactory
{
/**
* @var string
*/
public const NEWLINE_AFTER_OPENING_REGEX = '#^(\(\s+|\n)#m';
/**
* @var string
*/
public const NEWLINE_BEFORE_CLOSING_REGEX = '#(\s+\)|\n(\s+)?)$#m';
/**
* @var string
*/
public const OPENING_BRACKET_REGEX = '#^\(#';
/**
* @var string
*/
public const CLOSING_BRACKET_REGEX = '#\)$#';
public function createFromOriginalContent(
?string $originalContent,
PhpDocTagValueNode $phpDocTagValueNode
@ -29,11 +49,11 @@ final class TagValueNodeConfigurationFactory
$silentKey = $this->resolveSilentKey($phpDocTagValueNode);
$orderedVisibleItems = ArrayItemStaticHelper::resolveAnnotationItemsOrder($originalContent, $silentKey);
$hasNewlineAfterOpening = (bool) Strings::match($originalContent, '#^(\(\s+|\n)#m');
$hasNewlineBeforeClosing = (bool) Strings::match($originalContent, '#(\s+\)|\n(\s+)?)$#m');
$hasNewlineAfterOpening = (bool) Strings::match($originalContent, self::NEWLINE_AFTER_OPENING_REGEX);
$hasNewlineBeforeClosing = (bool) Strings::match($originalContent, self::NEWLINE_BEFORE_CLOSING_REGEX);
$hasOpeningBracket = (bool) Strings::match($originalContent, '#^\(#');
$hasClosingBracket = (bool) Strings::match($originalContent, '#\)$#');
$hasOpeningBracket = (bool) Strings::match($originalContent, self::OPENING_BRACKET_REGEX);
$hasClosingBracket = (bool) Strings::match($originalContent, self::CLOSING_BRACKET_REGEX);
$keysByQuotedStatus = [];
foreach ($orderedVisibleItems as $orderedVisibleItem) {

View File

@ -23,6 +23,11 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
*/
public const NAME = 'console';
/**
* @var string
*/
private const ON_LINE_REGEX = '# on line #';
/**
* @var SymfonyStyle
*/
@ -160,7 +165,7 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
private function normalizePathsToRelativeWithLine(string $errorMessage): string
{
$errorMessage = Strings::replace($errorMessage, '#' . preg_quote(getcwd(), '#') . '/#');
return $errorMessage = Strings::replace($errorMessage, '# on line #', ':');
return $errorMessage = Strings::replace($errorMessage, self::ON_LINE_REGEX, ':');
}
private function reportRemovedNodes(ErrorAndDiffCollector $errorAndDiffCollector): void

View File

@ -9,6 +9,16 @@ use SebastianBergmann\Diff\Differ;
final class MarkdownDifferAndFormatter
{
/**
* @var string
*/
private const METADATA_REGEX = '#^(.*\n){1}#';
/**
* @var string
*/
private const SPACE_AND_NEWLINE_REGEX = '#( ){1,}\n#';
/**
* @var Differ
*/
@ -28,7 +38,7 @@ final class MarkdownDifferAndFormatter
$diff = $this->markdownDiffer->diff($old, $new);
// remove first line, just meta info added by UnifiedDiffOutputBuilder
$diff = Strings::replace($diff, '#^(.*\n){1}#');
$diff = Strings::replace($diff, self::METADATA_REGEX);
return $this->removeTrailingWhitespaces($diff);
}
@ -38,7 +48,7 @@ final class MarkdownDifferAndFormatter
*/
private function removeTrailingWhitespaces(string $diff): string
{
$diff = Strings::replace($diff, '#( ){1,}\n#', PHP_EOL);
$diff = Strings::replace($diff, self::SPACE_AND_NEWLINE_REGEX, PHP_EOL);
return rtrim($diff);
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Rector\DocumentationGenerator\Guard;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
final class PrePrintRectorGuard
{
public function ensureRectorRefinitionHasContent(RectorInterface $rector): void
{
$this->ensureRectorDefinitionExists($rector);
$this->ensureCodeSampleExists($rector);
}
private function ensureRectorDefinitionExists(RectorInterface $rector): void
{
$rectorDefinition = $rector->getDefinition();
if ($rectorDefinition->getDescription() !== '') {
return;
}
$message = sprintf(
'Rector "%s" is missing description. Complete it in "%s()" method.',
get_class($rector),
'getDefinition'
);
throw new ShouldNotHappenException($message);
}
private function ensureCodeSampleExists(RectorInterface $rector): void
{
$rectorDefinition = $rector->getDefinition();
if (count($rectorDefinition->getCodeSamples()) !== 0) {
return;
}
throw new ShouldNotHappenException(sprintf(
'Rector "%s" must have at least one code sample. Complete it in "%s()" method.',
get_class($rector),
'getDefinition'
));
}
}

View File

@ -6,6 +6,7 @@ namespace Rector\DocumentationGenerator\OutputFormatter;
use Nette\Utils\Strings;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\DocumentationGenerator\Printer\RectorPrinter;
use Rector\DocumentationGenerator\RectorMetadataResolver;
use Symfony\Component\Console\Style\SymfonyStyle;

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\DocumentationGenerator\OutputFormatter;
namespace Rector\DocumentationGenerator\Printer;
use Rector\ConsoleDiffer\MarkdownDifferAndFormatter;
use Rector\Core\Contract\Rector\RectorInterface;
@ -11,15 +11,12 @@ use Rector\Core\RectorDefinition\ComposerJsonAwareCodeSample;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\SymfonyPhpConfig\Printer\ReturnClosurePrinter;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @see \Rector\DocumentationGenerator\Tests\Printer\CodeSamplePrinter\CodeSamplePrinterTest
*/
final class CodeSamplePrinter
{
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
/**
* @var MarkdownDifferAndFormatter
*/
@ -31,29 +28,29 @@ final class CodeSamplePrinter
private $returnClosurePrinter;
public function __construct(
SymfonyStyle $symfonyStyle,
MarkdownDifferAndFormatter $markdownDifferAndFormatter,
ReturnClosurePrinter $returnClosurePrinter
) {
$this->symfonyStyle = $symfonyStyle;
$this->markdownDifferAndFormatter = $markdownDifferAndFormatter;
$this->returnClosurePrinter = $returnClosurePrinter;
}
public function printCodeSamples(RectorDefinition $rectorDefinition, RectorInterface $rector): void
public function printCodeSamples(RectorDefinition $rectorDefinition, RectorInterface $rector): string
{
foreach ($rectorDefinition->getCodeSamples() as $codeSample) {
$this->symfonyStyle->newLine();
$content = '';
$this->printConfiguration($rector, $codeSample);
$this->printCodeSample($codeSample);
foreach ($rectorDefinition->getCodeSamples() as $codeSample) {
$content .= $this->printConfiguration($rector, $codeSample);
$content .= $this->printCodeSample($codeSample);
}
return $content;
}
private function printConfiguration(RectorInterface $rector, CodeSampleInterface $codeSample): void
private function printConfiguration(RectorInterface $rector, CodeSampleInterface $codeSample): string
{
if (! $codeSample instanceof ConfiguredCodeSample) {
return;
return '';
}
$configuration = [
@ -61,50 +58,43 @@ final class CodeSamplePrinter
];
$phpConfigContent = $this->returnClosurePrinter->printServices($configuration);
$this->printCodeWrapped($phpConfigContent, 'php');
$wrappedPhpConfigContent = $this->printCodeWrapped($phpConfigContent, 'php');
$this->symfonyStyle->newLine();
$this->symfonyStyle->writeln('↓');
$this->symfonyStyle->newLine();
return $wrappedPhpConfigContent . PHP_EOL . '↓' . PHP_EOL . PHP_EOL;
}
private function printCodeSample(CodeSampleInterface $codeSample): void
private function printCodeSample(CodeSampleInterface $codeSample): string
{
$diff = $this->markdownDifferAndFormatter->bareDiffAndFormatWithoutColors(
$codeSample->getCodeBefore(),
$codeSample->getCodeAfter()
);
$this->printCodeWrapped($diff, 'diff');
$content = $this->printCodeWrapped($diff, 'diff');
$extraFileContent = $codeSample->getExtraFileContent();
if ($extraFileContent !== null) {
$this->symfonyStyle->newLine();
$this->symfonyStyle->writeln('**New file**');
$this->symfonyStyle->newLine();
$this->printCodeWrapped($extraFileContent, 'php');
$content .= PHP_EOL . '**New file**' . PHP_EOL;
$content .= $this->printCodeWrapped($extraFileContent, 'php');
}
$this->printComposerJsonAwareCodeSample($codeSample);
return $content . $this->printComposerJsonAwareCodeSample($codeSample);
}
private function printCodeWrapped(string $content, string $format): void
private function printCodeWrapped(string $content, string $format): string
{
$message = sprintf('```%s%s%s%s```', $format, PHP_EOL, rtrim($content), PHP_EOL);
$this->symfonyStyle->writeln($message);
return $message . PHP_EOL;
}
private function printComposerJsonAwareCodeSample(CodeSampleInterface $codeSample): void
private function printComposerJsonAwareCodeSample(CodeSampleInterface $codeSample): string
{
if (! $codeSample instanceof ComposerJsonAwareCodeSample) {
return;
return '';
}
$composerJsonContent = $codeSample->getComposerJsonContent();
$this->symfonyStyle->newLine(1);
$this->symfonyStyle->writeln('`composer.json`');
$this->symfonyStyle->newLine(1);
$this->printCodeWrapped($composerJsonContent, 'json');
$this->symfonyStyle->newLine();
return PHP_EOL . 'composer.json' . PHP_EOL . $this->printCodeWrapped($composerJsonContent, 'json') . PHP_EOL;
}
}

View File

@ -2,24 +2,20 @@
declare(strict_types=1);
namespace Rector\DocumentationGenerator\OutputFormatter;
namespace Rector\DocumentationGenerator\Printer;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DocumentationGenerator\Guard\PrePrintRectorGuard;
use Rector\DocumentationGenerator\PhpKeywordHighlighter;
use Rector\PHPUnit\TestClassResolver\TestClassResolver;
use ReflectionClass;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @see \Rector\DocumentationGenerator\Tests\Printer\RectorPrinter\RectorPrinterTest
*/
final class RectorPrinter
{
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
/**
* @var TestClassResolver
*/
@ -35,65 +31,62 @@ final class RectorPrinter
*/
private $phpKeywordHighlighter;
/**
* @var PrePrintRectorGuard
*/
private $prePrintRectorGuard;
public function __construct(
SymfonyStyle $symfonyStyle,
TestClassResolver $testClassResolver,
CodeSamplePrinter $rectorCodeSamplePrinter,
PhpKeywordHighlighter $phpKeywordHighlighter
PhpKeywordHighlighter $phpKeywordHighlighter,
PrePrintRectorGuard $prePrintRectorGuard
) {
$this->symfonyStyle = $symfonyStyle;
$this->testClassResolver = $testClassResolver;
$this->rectorCodeSamplePrinter = $rectorCodeSamplePrinter;
$this->phpKeywordHighlighter = $phpKeywordHighlighter;
$this->prePrintRectorGuard = $prePrintRectorGuard;
}
public function printRector(RectorInterface $rector, bool $isRectorProject): void
public function printRector(RectorInterface $rector, bool $isRectorProject): string
{
$content = '';
$headline = $this->getRectorClassWithoutNamespace($rector);
if ($isRectorProject) {
$message = sprintf('### `%s`', $headline);
$this->symfonyStyle->writeln($message);
$content .= sprintf('### `%s`', $headline) . PHP_EOL;
} else {
$message = sprintf('## `%s`', $headline);
$this->symfonyStyle->writeln($message);
$content .= sprintf('## `%s`', $headline) . PHP_EOL;
}
$rectorClass = get_class($rector);
$this->symfonyStyle->newLine();
$message = sprintf(
'- class: [`%s`](%s)',
get_class($rector),
$this->resolveClassFilePathOnGitHub($rectorClass)
);
$this->symfonyStyle->writeln($message);
$content .= PHP_EOL;
$content .= $this->createRectorFileLink($rector, $rectorClass) . PHP_EOL;
$rectorTestClass = $this->testClassResolver->resolveFromClassName($rectorClass);
if ($rectorTestClass !== null) {
$fixtureDirectoryPath = $this->resolveFixtureDirectoryPathOnGitHub($rectorTestClass);
if ($fixtureDirectoryPath !== null) {
$message = sprintf('- [test fixtures](%s)', $fixtureDirectoryPath);
$this->symfonyStyle->writeln($message);
$content .= $message . PHP_EOL;
}
}
$this->prePrintRectorGuard->ensureRectorRefinitionHasContent($rector);
$content .= PHP_EOL;
$rectorDefinition = $rector->getDefinition();
$this->ensureRectorDefinitionExists($rectorDefinition, $rector);
$this->symfonyStyle->newLine();
$description = $rectorDefinition->getDescription();
$codeHighlightedDescription = $this->phpKeywordHighlighter->highlight($description);
$this->symfonyStyle->writeln($codeHighlightedDescription);
$this->ensureCodeSampleExists($rectorDefinition, $rector);
$content .= $codeHighlightedDescription . PHP_EOL . PHP_EOL;
$this->rectorCodeSamplePrinter->printCodeSamples($rectorDefinition, $rector);
$content .= $this->rectorCodeSamplePrinter->printCodeSamples($rectorDefinition, $rector);
$content .= PHP_EOL . '<br><br>' . PHP_EOL;
$this->symfonyStyle->newLine();
$this->symfonyStyle->writeln('<br><br>');
$this->symfonyStyle->newLine();
return $content;
}
private function getRectorClassWithoutNamespace(RectorInterface $rector): string
@ -104,10 +97,13 @@ final class RectorPrinter
return $rectorClassParts[count($rectorClassParts) - 1];
}
private function resolveClassFilePathOnGitHub(string $className): string
private function createRectorFileLink(RectorInterface $rector, string $rectorClass): string
{
$classRelativePath = $this->getClassRelativePath($className);
return '/' . ltrim($classRelativePath, '/');
return sprintf(
'- class: [`%s`](%s)',
get_class($rector),
$this->resolveClassFilePathOnGitHub($rectorClass)
);
}
private function resolveFixtureDirectoryPathOnGitHub(string $className): ?string
@ -122,31 +118,10 @@ final class RectorPrinter
return null;
}
private function ensureRectorDefinitionExists(RectorDefinition $rectorDefinition, RectorInterface $rector): void
private function resolveClassFilePathOnGitHub(string $className): string
{
if ($rectorDefinition->getDescription() !== '') {
return;
}
$message = sprintf(
'Rector "%s" is missing description. Complete it in "%s()" method.',
get_class($rector),
'getDefinition'
);
throw new ShouldNotHappenException($message);
}
private function ensureCodeSampleExists(RectorDefinition $rectorDefinition, RectorInterface $rector): void
{
if (count($rectorDefinition->getCodeSamples()) !== 0) {
return;
}
throw new ShouldNotHappenException(sprintf(
'Rector "%s" must have at least one code sample. Complete it in "%s()" method.',
get_class($rector),
'getDefinition'
));
$classRelativePath = $this->getClassRelativePath($className);
return '/' . ltrim($classRelativePath, '/');
}
private function getClassRelativePath(string $className): string

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Rector\DocumentationGenerator\Tests\Printer\CodeSamplePrinter;
use Iterator;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\HttpKernel\RectorKernel;
use Rector\DocumentationGenerator\Printer\CodeSamplePrinter;
use Rector\Php74\Rector\Property\TypedPropertyRector;
use ReflectionClass;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
final class CodeSamplePrinterTest extends AbstractKernelTestCase
{
/**
* @var CodeSamplePrinter
*/
private $codeSamplePrinter;
protected function setUp(): void
{
$this->bootKernel(RectorKernel::class);
$this->codeSamplePrinter = self::$container->get(CodeSamplePrinter::class);
}
/**
* @dataProvider provideData()
*/
public function test(string $rectorClass, string $expectedPrintedCodeSampleFilePath): void
{
$reflectionClass = new ReflectionClass($rectorClass);
$rector = $reflectionClass->newInstanceWithoutConstructor();
/** @var RectorInterface $rector */
$this->assertInstanceOf(RectorInterface::class, $rector);
$printedCodeSamples = $this->codeSamplePrinter->printCodeSamples($rector->getDefinition(), $rector);
$this->assertStringEqualsFile($expectedPrintedCodeSampleFilePath, $printedCodeSamples);
}
public function provideData(): Iterator
{
yield [TypedPropertyRector::class, __DIR__ . '/Fixture/expected_typed_property_code_sample.txt'];
}
}

View File

@ -0,0 +1,25 @@
```php
<?php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Rector\Php74\Rector\Property\TypedPropertyRector;
return function (ContainerConfigurator $containerConfigurator) : void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyRector::class)
->call('configure', [[TypedPropertyRector::CLASS_LIKE_TYPE_ONLY => false]]);
};
```
```diff
final class SomeClass
{
- /**
- * @var int
- */
- private count;
+ private int count;
}
```

View File

@ -0,0 +1,34 @@
## `TypedPropertyRector`
- class: [`Rector\Php74\Rector\Property\TypedPropertyRector`](/rules/php74/src/Rector/Property/TypedPropertyRector.php)
- [test fixtures](/rules/php74/tests/Rector/Property/TypedPropertyRector/Fixture)
Changes property `@var` annotations from annotation to type.
```php
<?php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Rector\Php74\Rector\Property\TypedPropertyRector;
return function (ContainerConfigurator $containerConfigurator) : void {
$services = $containerConfigurator->services();
$services->set(TypedPropertyRector::class)
->call('configure', [[TypedPropertyRector::CLASS_LIKE_TYPE_ONLY => false]]);
};
```
```diff
final class SomeClass
{
- /**
- * @var int
- */
- private count;
+ private int count;
}
```
<br><br>

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Rector\DocumentationGenerator\Tests\Printer\RectorPrinter;
use Iterator;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\HttpKernel\RectorKernel;
use Rector\DocumentationGenerator\Printer\RectorPrinter;
use Rector\Php74\Rector\Property\TypedPropertyRector;
use ReflectionClass;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
final class RectorPrinterTest extends AbstractKernelTestCase
{
/**
* @var RectorPrinter
*/
private $rectorPrinter;
protected function setUp(): void
{
$this->bootKernel(RectorKernel::class);
$this->rectorPrinter = self::$container->get(RectorPrinter::class);
}
/**
* @dataProvider provideData()
*/
public function test(string $rectorClass, string $expectedContentFilePath): void
{
$reflectionClass = new ReflectionClass($rectorClass);
/** @var RectorInterface $rector */
$rector = $reflectionClass->newInstanceWithoutConstructor();
$printedRector = $this->rectorPrinter->printRector($rector, false);
$this->assertStringEqualsFile($expectedContentFilePath, $printedRector);
}
public function provideData(): Iterator
{
yield [TypedPropertyRector::class, __DIR__ . '/Fixture/expected_typed_property_rector.txt'];
}
}

View File

@ -33,6 +33,11 @@ use Symplify\SmartFileSystem\SmartFileInfo;
*/
final class PHPStanNodeScopeResolver
{
/**
* @var string
*/
private const ANONYMOUS_CLASS_START_REGEX = '#^AnonymousClass(\w+)#';
/**
* @var string[]
*/
@ -174,7 +179,7 @@ final class PHPStanNodeScopeResolver
$className = $this->resolveClassName($classLike);
// is anonymous class? - not possible to enter it since PHPStan 0.12.33, see https://github.com/phpstan/phpstan-src/commit/e87fb0ec26f9c8552bbeef26a868b1e5d8185e91
if ($classLike instanceof Class_ && Strings::match($className, '#^AnonymousClass(\w+)#')) {
if ($classLike instanceof Class_ && Strings::match($className, self::ANONYMOUS_CLASS_START_REGEX)) {
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($classLike, $scope);
} else {
$classReflection = $this->reflectionProvider->getClass($className);

View File

@ -20,6 +20,21 @@ use Rector\Renaming\ValueObject\RenameAnnotation;
final class DocBlockManipulator
{
/**
* @var string
*/
public const SPACE_OR_ASTERISK_REGEX = '#(\s|\*)+#';
/**
* @var string
*/
private const NEWLINE_CLOSING_DOC_REGEX = "#\n \*\/$#";
/**
* @var string
*/
private const NEWLINE_MIDDLE_DOC_REGEX = "#\n \* #";
/**
* @var PhpDocInfoPrinter
*/
@ -128,12 +143,12 @@ final class DocBlockManipulator
/** @var PhpDocInfo $phpDocInfo */
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
$relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
if ($relationTagValueNode === null) {
$doctrineRelationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
if ($doctrineRelationTagValueNode === null) {
return null;
}
return $relationTagValueNode->getFullyQualifiedTargetEntity();
return $doctrineRelationTagValueNode->getFullyQualifiedTargetEntity();
}
private function printPhpDocInfoToString(PhpDocInfo $phpDocInfo): string
@ -173,9 +188,9 @@ final class DocBlockManipulator
private function inlineDocContent(string $docContent): string
{
$docContent = Strings::replace($docContent, "#\n \* #", ' ');
$docContent = Strings::replace($docContent, self::NEWLINE_MIDDLE_DOC_REGEX, ' ');
return Strings::replace($docContent, "#\n \*\/$#", ' */');
return Strings::replace($docContent, self::NEWLINE_CLOSING_DOC_REGEX, ' */');
}
/**
@ -202,6 +217,6 @@ final class DocBlockManipulator
private function removeSpacesAndAsterisks(string $content): string
{
return Strings::replace($content, '#(\s|\*)+#');
return Strings::replace($content, self::SPACE_OR_ASTERISK_REGEX);
}
}

View File

@ -9,6 +9,11 @@ use Rector\PhpAttribute\Collector\PlaceholderToValueCollector;
final class ContentPhpAttributePlaceholderReplacer
{
/**
* @var string
*/
public const ATTRIBUTE_END_REGEX = '#>>\n\n#';
/**
* @var PlaceholderToValueCollector
*/
@ -39,7 +44,7 @@ final class ContentPhpAttributePlaceholderReplacer
}
// remove extra newline between attributes and node
return Strings::replace($content, '#>>\n\n#', '>>' . PHP_EOL);
return Strings::replace($content, self::ATTRIBUTE_END_REGEX, '>>' . PHP_EOL);
}
private function addLeftIndent(string $content, string $quotedPlaceholder, string $value): string

View File

@ -12,6 +12,21 @@ use Symplify\SmartFileSystem\SmartFileInfo;
final class TemplateFileSystem
{
/**
* @var string
*/
private const FIXTURE_SHORT_REGEX = '#/Fixture/#';
/**
* @var string
*/
private const PACKAGE_RULES_PATH_REGEX = '#(packages|rules)\/__package__#i';
/**
* @var string
*/
private const CONFIGURED_OR_EXTRA_REGEX = '#(__Configured|__Extra)#';
/**
* @param string[] $templateVariables
*/
@ -26,13 +41,13 @@ final class TemplateFileSystem
// normalize core package
if (! $rectorRecipe->isRectorRepository()) {
// special keyword for 3rd party Rectors, not for core Github contribution
$destination = Strings::replace($destination, '#(packages|rules)\/__package__#i', 'utils/rector');
$destination = Strings::replace($destination, self::PACKAGE_RULES_PATH_REGEX, 'utils/rector');
}
// remove _Configured|_Extra prefix
$destination = $this->applyVariables($destination, $templateVariables);
$destination = Strings::replace($destination, '#(__Configured|__Extra)#', '');
$destination = Strings::replace($destination, self::CONFIGURED_OR_EXTRA_REGEX, '');
// remove ".inc" protection from PHPUnit if not a test case
if ($this->isNonFixtureFileWithIncSuffix($destination)) {
@ -58,7 +73,7 @@ final class TemplateFileSystem
private function isNonFixtureFileWithIncSuffix(string $filePath): bool
{
if (Strings::match($filePath, '#/Fixture/#')) {
if (Strings::match($filePath, self::FIXTURE_SHORT_REGEX)) {
return false;
}

View File

@ -13,6 +13,11 @@ use Symplify\SmartFileSystem\SmartFileSystem;
final class FileGenerator
{
/**
* @var string
*/
public const RECTOR_UTILS_REGEX = '#Rector\\\\Utils#';
/**
* @var TemplateFileSystem
*/
@ -79,7 +84,7 @@ final class FileGenerator
// replace "Rector\Utils\" with "Utils\Rector\" for 3rd party packages
if (! $rectorRecipe->isRectorRepository()) {
$content = Strings::replace($content, '#Rector\\\\Utils#', 'Utils\Rector');
$content = Strings::replace($content, self::RECTOR_UTILS_REGEX, 'Utils\Rector');
}
$this->smartFileSystem->dumpFile($targetFilePath, $content);

View File

@ -16,6 +16,11 @@ use Symplify\SmartFileSystem\SmartFileInfo;
final class RectorSetProvider extends AbstractSetProvider
{
/**
* @var string
*/
private const DASH_NUMBER_REGEX = '#\-(\d+)#';
/**
* @var Set[]
*/
@ -71,7 +76,7 @@ final class RectorSetProvider extends AbstractSetProvider
$setName = StaticRectorStrings::constantToDashes($name);
// remove `-` before numbers
$setName = Strings::replace($setName, '#\-(\d+)#', '$1');
$setName = Strings::replace($setName, self::DASH_NUMBER_REGEX, '$1');
$this->sets[] = new Set($setName, new SmartFileInfo($setPath));
}
}

View File

@ -8,53 +8,66 @@ includes:
# see https://github.com/symplify/coding-standard
- vendor/symplify/coding-standard/config/symplify-rules.neon
rules:
# should be fixed in next part of symplify CS release
- Symplify\CodingStandard\Rules\NoClassWithStaticMethodWithoutStaticNameRule
- Symplify\CodingStandard\Rules\SeeAnnotationToTestRule
- Symplify\CodingStandard\Rules\NoReferenceRule
services:
-
class: Symplify\CodingStandard\Rules\SeeAnnotationToTestRule
tags: [phpstan.rules.rule]
arguments:
requiredSeeTypes:
- PHPStan\Rules\Rule
- Rector\Core\Rector\AbstractRector
- Rector\FileSystemRector\Rector\AbstractFileSystemRector
-
class: Symplify\CodingStandard\Rules\NoStaticCallRule
tags: [phpstan.rules.rule]
arguments:
allowedStaticCallClasses:
- PHPStan\Type\VerbosityLevel
- Rector\NodeTypeResolver\ClassExistenceStaticHelper
# this rule prevents bug in phar like these: https://github.com/rectorphp/rector/pull/3692/files
-
class: Symplify\CodingStandard\Rules\RequireStringArgumentInMethodCallRule
tags: [phpstan.rules.rule]
arguments:
stringArgByMethodByType:
Rector\Core\Rector\AbstractRector:
isObjectType: [1]
-
class: Symplify\CodingStandard\Rules\ClassNameRespectsParentSuffixRule
tags: [phpstan.rules.rule]
arguments:
parentClasses:
- Rector
-
class: Symplify\CodingStandard\Rules\PreferredClassRule
tags: [phpstan.rules.rule]
arguments:
oldToPrefferedClasses:
# prevent PHPStorm autocomplete mess
'Symfony\Component\DependencyInjection\Variable': 'PhpParser\Node\Expr\Variable'
'phpDocumentor\Reflection\Types\Expression': 'PhpParser\Node\Stmt\Expression'
'phpDocumentor\Reflection\DocBlock\Tags\Param': 'PhpParser\Node\Param'
'phpDocumentor\Reflection\DocBlock\Tags\Return_': 'PhpParser\Node\Stmt\Return_'
'Closure': 'PhpParser\Node\Expr\Closure'
'PHPUnit\TextUI\Configuration\Variable': 'PhpParser\Node\Expr\Variable'
'PhpCsFixer\FixerDefinition\CodeSample': 'Rector\Core\RectorDefinition\CodeSample'
'SebastianBergmann\Type\MixedType': 'PHPStan\Type\MixedType'
'Hoa\Protocol\Node\Node': 'PhpParser\Node'
'Nette\Utils\FileSystem': 'Symplify\SmartFileSystem\SmartFileSystem'
'Symfony\Component\Filesystem\Filesystem': 'Symplify\SmartFileSystem\SmartFileSystem'
parameters:
level: max
# see https://github.com/symplify/coding-standard
symplify:
# this rule prevents bug in phar like these: https://github.com/rectorphp/rector/pull/3692/files
string_arg_by_method_by_type:
Rector\Core\Rector\AbstractRector:
isObjectType: [1]
max_method_cognitive_complexity: 9 # default: 8
max_class_cognitive_complexity: 50
parent_classes:
- Rector
required_see_types:
- PHPStan\Rules\Rule
- Rector\Core\Rector\AbstractRector
- Rector\FileSystemRector\Rector\AbstractFileSystemRector
# allowed static types
# for \Symplify\CodingStandard\Rules\NoStaticCallRule
allowed_static_call_classes:
- PHPStan\Type\VerbosityLevel
- Rector\NodeTypeResolver\ClassExistenceStaticHelper
old_to_preffered_classes:
# prevent PHPStorm autocomplete mess
'Symfony\Component\DependencyInjection\Variable': 'PhpParser\Node\Expr\Variable'
'phpDocumentor\Reflection\Types\Expression': 'PhpParser\Node\Stmt\Expression'
'phpDocumentor\Reflection\DocBlock\Tags\Param': 'PhpParser\Node\Param'
'phpDocumentor\Reflection\DocBlock\Tags\Return_': 'PhpParser\Node\Stmt\Return_'
'Closure': 'PhpParser\Node\Expr\Closure'
'PHPUnit\TextUI\Configuration\Variable': 'PhpParser\Node\Expr\Variable'
'PhpCsFixer\FixerDefinition\CodeSample': 'Rector\Core\RectorDefinition\CodeSample'
'SebastianBergmann\Type\MixedType': 'PHPStan\Type\MixedType'
'Hoa\Protocol\Node\Node': 'PhpParser\Node'
'Nette\Utils\FileSystem': 'Symplify\SmartFileSystem\SmartFileSystem'
'Symfony\Component\Filesystem\Filesystem': 'Symplify\SmartFileSystem\SmartFileSystem'
# to allow installing with various phsptan versions without reporting old errors here
reportUnmatchedIgnoredErrors: false
@ -75,7 +88,7 @@ parameters:
- compiler/src
- utils
# this cannot be put it, because it wipes PHPStan cache on each run :( - must run in separate
#- config/set
#- config
excludes_analyse:
# iterable types
@ -304,7 +317,6 @@ parameters:
- '#Method (.*?) specified in iterable type Symfony\\Component\\Process\\Process#'
- '#Cannot cast PhpParser\\Node\\Expr\\Error\|PhpParser\\Node\\Identifier to string#'
- '#Cognitive complexity for "Rector\\Utils\\NodeDocumentationGenerator\\Command\\DumpNodesCommand\:\:(.*?)" is \d+, keep it under 9#'
- '#Parameter \#1 \$node of method Rector\\PostRector\\Collector\\NodesToAddCollector\:\:wrapToExpression\(\) expects PhpParser\\Node\\Expr\|PhpParser\\Node\\Stmt, PhpParser\\Node given#'
- '#Access to an undefined property PhpParser\\Node\\Expr\:\:\$class#'
@ -430,12 +442,8 @@ parameters:
-
message: '#Method "__construct\(\)" is using too many parameters \- \d+\. Make it under 10#'
paths:
- packages/better-php-doc-parser/src/PhpDocParser/BetterPhpDocParser.php
- packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php
# complex command
- '#Method "__construct\(\)" is using too many parameters \- \d+\. Make it under 10#'
# the smallest descriptive method
- '#Variable "\$inverseJoinColumnsOpeningAndClosingSpace" is too long with \d+ chars\. Narrow it under 40 chars#'
@ -446,7 +454,6 @@ parameters:
# symplify rules fix later
- '#Use another value object over string with value object arrays#'
- '#Use local named constant instead of inline string for regex to explain meaning by constant name#'
- '#Use decouled factory service to create "(.*?)" object#'
- '#Add regex101\.org link to that shows the regex in practise, so it will be easier to maintain in case of bug/extension in the future#'
@ -469,4 +476,22 @@ parameters:
# Temprory ignored
- '#Do not use scalar or array as constructor parameter\. Use ParameterProvider service instead#'
- '#Method Rector\\Utils\\NodeDocumentationGenerator\\NodeCodeSampleProvider\:\:getNodeClasses\(\) should return array<class\-string\> but returns array<int, string\>#'
-
message: '#Use local named constant instead of inline string for regex to explain meaning by constant name#'
paths:
- packages/better-php-doc-parser/src/PartPhpDocTagPrinter/Behavior/ArrayPartPhpDocTagPrinterTrait.php # 27
- '#Do not use trait#'
- '#Method "(.*?)" is using too many parameters \- \d+\. Make it under 8#'
-
message: '#Use local named constant instead of inline string for regex to explain meaning by constant name#'
paths:
# trait cannot extract constant, decouple this
- packages/better-php-doc-parser/src/PhpDocNode/PrintTagValueNodeTrait.php # 52
-
message: '#Instead of "Symfony\\Component\\Finder\\SplFileInfo" class/interface use "Symplify\\SmartFileSystem\\SmartFileInfo"#'
paths:
- src/FileSystem/FilesFinder.php

View File

@ -22,6 +22,11 @@ use Symplify\SmartFileSystem\SmartFileInfo;
*/
final class MoveEntitiesToEntityDirectoryRector extends AbstractFileMovingFileSystemRector
{
/**
* @var string
*/
private const ENTITY_PATH_REGEX = '#\bEntity\b#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Move entities to Entity namespace', [new CodeSample(
@ -65,7 +70,7 @@ CODE_SAMPLE
}
// is entity in expected directory?
if (Strings::match($smartFileInfo->getRealPath(), '#\bEntity\b#')) {
if (Strings::match($smartFileInfo->getRealPath(), self::ENTITY_PATH_REGEX)) {
return;
}

View File

@ -2,15 +2,31 @@
declare(strict_types=1);
namespace Rector\CakePHP;
namespace Rector\CakePHP\Naming;
use Nette\Utils\Strings;
use Rector\CakePHP\ImplicitNameResolver;
/**
* @inspired https://github.com/cakephp/upgrade/blob/756410c8b7d5aff9daec3fa1fe750a3858d422ac/src/Shell/Task/AppUsesTask.php
*/
final class FullyQualifiedClassNameResolver
final class CakePHPFullyQualifiedClassNameResolver
{
/**
* @var string
*/
public const LIB_NAMESPACE_PART_REGEX = '#\\\\Lib\\\\#';
/**
* @var string
*/
private const SLASH_REGEX = '#(/|\.)#';
/**
* @var string
*/
private const PLUGIN_OR_LIB_REGEX = '#(Plugin|Lib)#';
/**
* @var ImplicitNameResolver
*/
@ -38,7 +54,7 @@ final class FullyQualifiedClassNameResolver
// Chop Lib out as locations moves those files to the top level.
// But only if Lib is not the last folder.
if (Strings::match($pseudoNamespace, '#\\\\Lib\\\\#')) {
if (Strings::match($pseudoNamespace, self::LIB_NAMESPACE_PART_REGEX)) {
$pseudoNamespace = Strings::replace($pseudoNamespace, '#\\\\Lib#');
}
@ -49,7 +65,10 @@ final class FullyQualifiedClassNameResolver
}
// C. is not plugin nor lib custom App class?
if (Strings::contains($pseudoNamespace, '\\') && ! Strings::match($pseudoNamespace, '#(Plugin|Lib)#')) {
if (Strings::contains($pseudoNamespace, '\\') && ! Strings::match(
$pseudoNamespace,
self::PLUGIN_OR_LIB_REGEX
)) {
return 'App\\' . $pseudoNamespace . '\\' . $shortClass;
}
@ -58,6 +77,6 @@ final class FullyQualifiedClassNameResolver
private function normalizeFileSystemSlashes(string $pseudoNamespace): string
{
return Strings::replace($pseudoNamespace, '#(/|\.)#', '\\');
return Strings::replace($pseudoNamespace, self::SLASH_REGEX, '\\');
}
}

View File

@ -11,7 +11,7 @@ use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use Rector\CakePHP\FullyQualifiedClassNameResolver;
use Rector\CakePHP\Naming\CakePHPFullyQualifiedClassNameResolver;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
@ -27,13 +27,13 @@ use Rector\PHPStan\Type\FullyQualifiedObjectType;
final class AppUsesStaticCallToUseStatementRector extends AbstractRector
{
/**
* @var FullyQualifiedClassNameResolver
* @var CakePHPFullyQualifiedClassNameResolver
*/
private $fullyQualifiedClassNameResolver;
private $cakePHPFullyQualifiedClassNameResolver;
public function __construct(FullyQualifiedClassNameResolver $fullyQualifiedClassNameResolver)
public function __construct(CakePHPFullyQualifiedClassNameResolver $cakePHPFullyQualifiedClassNameResolver)
{
$this->fullyQualifiedClassNameResolver = $fullyQualifiedClassNameResolver;
$this->cakePHPFullyQualifiedClassNameResolver = $cakePHPFullyQualifiedClassNameResolver;
}
public function getDefinition(): RectorDefinition
@ -105,7 +105,7 @@ CODE_SAMPLE
/** @var string $namespaceName */
$namespaceName = $this->getValue($staticCall->args[1]->value);
return $this->fullyQualifiedClassNameResolver->resolveFromPseudoNamespaceAndShortClassName(
return $this->cakePHPFullyQualifiedClassNameResolver->resolveFromPseudoNamespaceAndShortClassName(
$namespaceName,
$shortClassName
);

View File

@ -26,6 +26,11 @@ use Symplify\SmartFileSystem\SmartFileInfo;
final class ShortNameResolver
{
/**
* @var string
*/
private const BIG_LETTER_START_REGEX = '#^[A-Z]#';
/**
* @var string[][]
*/
@ -193,7 +198,7 @@ final class ShortNameResolver
$tagName = ltrim($phpDocChildNode->name, '@');
// is annotation class - big letter?
if (Strings::match($tagName, '#^[A-Z]#')) {
if (Strings::match($tagName, self::BIG_LETTER_START_REGEX)) {
return $tagName;
}

View File

@ -15,6 +15,11 @@ use Rector\PHPStan\Type\AliasedObjectType;
final class DocAliasResolver
{
/**
* @var string
*/
private const DOC_ALIAS_REGEX = '#\@(?<possible_alias>\w+)(\\\\)?#s';
/**
* @var CallableNodeTraverser
*/
@ -47,7 +52,7 @@ final class DocAliasResolver
}
// e.g. "use Dotrine\ORM\Mapping as ORM" etc.
$matches = Strings::matchAll($node->getDocComment()->getText(), '#\@(?<possible_alias>\w+)(\\\\)?#s');
$matches = Strings::matchAll($node->getDocComment()->getText(), self::DOC_ALIAS_REGEX);
foreach ($matches as $match) {
$possibleDocAliases[] = $match['possible_alias'];
}

View File

@ -33,6 +33,17 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class ManualJsonStringToJsonEncodeArrayRector extends AbstractRector
{
/**
* @var string
* @see https://regex101.com/r/85PZHm/1
*/
private const UNQUOTED_OBJECT_HASH_REGEX = '#(?<start>[^\"])(?<hash>____\w+____)#';
/**
* @var string
*/
private const JSON_STRING_REGEX = '#{(.*?\:.*?)}#s';
/**
* @var ConcatJoiner
*/
@ -149,7 +160,7 @@ CODE_SAMPLE
private function isJsonString(string $stringValue): bool
{
if (! (bool) Strings::match($stringValue, '#{(.*?\:.*?)}#s')) {
if (! (bool) Strings::match($stringValue, self::JSON_STRING_REGEX)) {
return false;
}
@ -219,8 +230,7 @@ CODE_SAMPLE
array $placeholderNodes,
Assign $assign
): ?Assign {
// quote object hashes if needed - https://regex101.com/r/85PZHm/1
$stringValue = Strings::replace($stringValue, '#(?<start>[^\"])(?<hash>____\w+____)#', '$1"$2"');
$stringValue = Strings::replace($stringValue, self::UNQUOTED_OBJECT_HASH_REGEX, '$1"$2"');
if (! $this->isJsonString($stringValue)) {
return null;
}

View File

@ -22,6 +22,11 @@ use Rector\Core\RectorDefinition\RectorDefinition;
*/
final class RemoveDoubleUnderscoreInMethodNameRector extends AbstractRector
{
/**
* @var string
*/
private const DOUBLE_UNDERSCORE_START_REGEX = '#__(.*?)#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Non-magic PHP object methods cannot start with "__"', [
@ -71,7 +76,7 @@ CODE_SAMPLE
return null;
}
if (! Strings::match($methodName, '#__(.*?)#')) {
if (! Strings::match($methodName, self::DOUBLE_UNDERSCORE_START_REGEX)) {
return null;
}

View File

@ -48,6 +48,11 @@ final class VersionCompareFuncCallToConstantRector extends AbstractRector
'le' => SmallerOrEqual::class,
];
/**
* @var string
*/
private const SEMANTIC_VERSION_REGEX = '#^\d+\.\d+\.\d+$#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Changes use of call to version compare function to use of PHP version constant', [
@ -132,7 +137,7 @@ CODE_SAMPLE
throw new ShouldNotHappenException();
}
if (! Strings::match($expr->value, '#^\d+\.\d+\.\d+$#')) {
if (! Strings::match($expr->value, self::SEMANTIC_VERSION_REGEX)) {
throw new ShouldNotHappenException();
}

View File

@ -17,6 +17,11 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class SymplifyQuoteEscapeRector extends AbstractRector
{
/**
* @var string
*/
private const ESCAPED_CHAR_REGEX = '#\\\\|\$#sim';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Prefer quote that are not inside the string', [
@ -77,7 +82,7 @@ CODE_SAMPLE
{
if ($doubleQuoteCount === 0 && $singleQuoteCount > 0) {
// contains chars tha will be newly escaped
$matches = Strings::match($string->value, '#\\\\|\$#sim');
$matches = Strings::match($string->value, self::ESCAPED_CHAR_REGEX);
if ($matches) {
return;
}

View File

@ -18,6 +18,12 @@ use Rector\Core\RectorDefinition\RectorDefinition;
*/
final class UseClassKeywordForClassNameResolutionRector extends AbstractRector
{
/**
* @var string
* @see https://regex101.com/r/Vv41Qr/1/
*/
private const CLASS_BEFORE_STATIC_ACCESS_REGEX = '#([\\\\a-zA-Z0-9_\\x80-\\xff]*)::#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
@ -68,8 +74,7 @@ CODE_SAMPLE
*/
public function getExistingClasses(String_ $string): array
{
// @see https://regex101.com/r/Vv41Qr/1/
$matches = Strings::matchAll($string->value, '#([\\\\a-zA-Z0-9_\\x80-\\xff]*)::#', PREG_PATTERN_ORDER);
$matches = Strings::matchAll($string->value, self::CLASS_BEFORE_STATIC_ACCESS_REGEX, PREG_PATTERN_ORDER);
return array_filter($matches[1], function (string $className): bool {
return class_exists($className);

View File

@ -14,13 +14,23 @@ final class DefaultDoctrineEntityAndRepositoryMapper implements DoctrineEntityAn
*/
private const REPOSITORY = 'Repository';
/**
* @var string
*/
private const REPOSITORY_REGEX = '#Repository#';
/**
* @var string
*/
private const ENTITY_REGEX = '#Entity#';
public function mapRepositoryToEntity(string $repository): ?string
{
// "SomeRepository" => "Some"
$withoutSuffix = Strings::substring($repository, 0, - strlen(self::REPOSITORY));
// "App\Repository\Some" => "App\Entity\Some"
return Strings::replace($withoutSuffix, '#Repository#', 'Entity');
return Strings::replace($withoutSuffix, self::REPOSITORY_REGEX, 'Entity');
}
public function mapEntityToRepository(string $entity): ?string
@ -29,6 +39,6 @@ final class DefaultDoctrineEntityAndRepositoryMapper implements DoctrineEntityAn
$withSuffix = $entity . self::REPOSITORY;
// "App\Entity\SomeRepository" => "App\Repository\SomeRepository"
return Strings::replace($withSuffix, '#Entity#', self::REPOSITORY);
return Strings::replace($withSuffix, self::ENTITY_REGEX, self::REPOSITORY);
}
}

View File

@ -24,6 +24,16 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
final class EntityUuidNodeFactory
{
/**
* @var string
*/
private const SERIALIZER_SHORT_ANNOTATION_REGEX = '#(\@Serializer\\\\Type\(")(int)("\))#';
/**
* @var string
*/
private const ORM_VAR_DOC_LINE_REGEX = '#^(\s+)\*(\s+)\@(var|ORM)(.*?)$#ms';
/**
* @var PhpDocTagNodeFactory
*/
@ -98,7 +108,7 @@ final class EntityUuidNodeFactory
return;
}
$clearedDocCommentText = Strings::replace($docComment->getText(), '#^(\s+)\*(\s+)\@(var|ORM)(.*?)$#ms');
$clearedDocCommentText = Strings::replace($docComment->getText(), self::ORM_VAR_DOC_LINE_REGEX);
$node->setDocComment(new Doc($clearedDocCommentText));
}
@ -114,7 +124,7 @@ final class EntityUuidNodeFactory
$stringTypeText = Strings::replace(
$docComment->getText(),
'#(\@Serializer\\\\Type\(")(int)("\))#',
self::SERIALIZER_SHORT_ANNOTATION_REGEX,
'$1string$3'
);

View File

@ -22,6 +22,11 @@ use ReflectionClass;
final class DoctrineDocBlockResolver
{
/**
* @var string
*/
private const ORM_ENTITY_EMBEDDABLE_SHORT_ANNOTATION_REGEX = '#@ORM\\\\(Entity|Embeddable)#';
/**
* @var ParsedNodeCollector
*/
@ -142,7 +147,7 @@ final class DoctrineDocBlockResolver
// dummy check of 3rd party code without running it
$docCommentContent = (string) $reflectionClass->getDocComment();
return (bool) Strings::match($docCommentContent, '#@ORM\\\\(Entity|Embeddable)#');
return (bool) Strings::match($docCommentContent, self::ORM_ENTITY_EMBEDDABLE_SHORT_ANNOTATION_REGEX);
}
private function hasPropertyDoctrineIdTag(Property $property): bool

View File

@ -17,6 +17,11 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
final class EntityWithMissingUuidProvider
{
/**
* @var string
*/
private const UUID_PREFIX_REGEX = '#^uuid(_binary)?$#';
/**
* @var Class_[]
*/
@ -113,6 +118,6 @@ final class EntityWithMissingUuidProvider
return false;
}
return (bool) Strings::match($columnTagValueNode->getType(), '#^uuid(_binary)?$#');
return (bool) Strings::match($columnTagValueNode->getType(), self::UUID_PREFIX_REGEX);
}
}

View File

@ -48,6 +48,11 @@ final class InjectAnnotationClassRector extends AbstractRector implements Config
JMSInject::class => JMSInjectTagValueNode::class,
];
/**
* @var string
*/
private const BETWEEN_PERCENT_CHARS_REGEX = '#%(.*?)%#';
/**
* @var string[]
*/
@ -184,7 +189,7 @@ CODE_SAMPLE
return false;
}
return (bool) Strings::match($serviceName, '#%(.*?)%#');
return (bool) Strings::match($serviceName, self::BETWEEN_PERCENT_CHARS_REGEX);
}
private function resolveType(Node $node, PhpDocTagValueNode $phpDocTagValueNode): Type

View File

@ -33,6 +33,11 @@ use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
*/
final class BreakingVariableRenameGuard
{
/**
* @var string
*/
private const AT_NAMING_REGEX = '#[\w+]At$#';
/**
* @var BetterNodeFinder
*/
@ -288,6 +293,6 @@ final class BreakingVariableRenameGuard
return false;
}
return (bool) Strings::match($currentName, '#[\w+]At$#');
return (bool) Strings::match($currentName, self::AT_NAMING_REGEX . '');
}
}

View File

@ -47,6 +47,17 @@ final class PropertyNaming
*/
private const PREFIXED_CLASS_METHODS_REGEX = '#^(is|are|was|were|has|have|had|can)[A-Z].+#';
/**
* @var string
*/
private const I_PREFIX_REGEX = '#^I[A-Z]#';
/**
* @see https://regex101.com/r/hnU5pm/2/
* @var string
*/
private const GET_PREFIX_REGEX = '#^get([A-Z].+)#';
/**
* @var TypeUnwrapper
*/
@ -81,8 +92,7 @@ final class PropertyNaming
public function getExpectedNameFromMethodName(string $methodName): ?ExpectedName
{
// @see https://regex101.com/r/hnU5pm/2/
$matches = Strings::match($methodName, '#^get([A-Z].+)#');
$matches = Strings::match($methodName, self::GET_PREFIX_REGEX);
if ($matches === null) {
return null;
}
@ -197,8 +207,8 @@ final class PropertyNaming
private function removePrefixesAndSuffixes(string $shortClassName): string
{
// is SomeInterface
if (Strings::endsWith($shortClassName, 'Interface')) {
$shortClassName = Strings::substring($shortClassName, 0, -strlen('Interface'));
if (Strings::endsWith($shortClassName, self::INTERFACE)) {
$shortClassName = Strings::substring($shortClassName, 0, -strlen(self::INTERFACE));
}
// is ISomeClass
@ -277,11 +287,11 @@ final class PropertyNaming
}
// starts with "I\W+"?
if (Strings::match($shortName, '#^I[A-Z]#')) {
if (Strings::match($shortName, self::I_PREFIX_REGEX)) {
return Strings::substring($shortName, 1);
}
if (Strings::match($shortName, '#Interface$#')) {
if (Strings::endsWith($shortName, self::INTERFACE)) {
return Strings::substring($shortName, -strlen(self::INTERFACE));
}

View File

@ -9,6 +9,12 @@ use Nette\Utils\Strings;
final class RectorNamingInflector
{
/**
* @var string
* @see https://regex101.com/r/VqVvke/3
*/
private const DATA_INFO_SUFFIX_REGEX = '#^(.+)(Data|Info)$#';
/**
* @var Inflector
*/
@ -21,8 +27,7 @@ final class RectorNamingInflector
public function singularize(string $name): string
{
// @see https://regex101.com/r/VqVvke/3
$matches = Strings::match($name, '#^(.+)(Data|Info)$$#');
$matches = Strings::match($name, self::DATA_INFO_SUFFIX_REGEX);
if ($matches === null) {
return $this->inflector->singularize($name);
}

View File

@ -15,6 +15,16 @@ use Symplify\SmartFileSystem\SmartFileInfo;
*/
final class RenameTesterTestToPHPUnitToTestFileRector extends AbstractFileSystemRector
{
/**
* @var string
*/
private const PHP_SUFFIX_REGEX = '#\.php$#';
/**
* @var string
*/
private const PHPT_SUFFIX_REGEX = '#\.phpt$#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Rename "*.phpt" file to "*Test.php" file', [
@ -48,11 +58,11 @@ CODE_SAMPLE
private function createNewRealPath(string $oldRealPath): string
{
// file suffix
$newRealPath = Strings::replace($oldRealPath, '#\.phpt$#', '.php');
$newRealPath = Strings::replace($oldRealPath, self::PHPT_SUFFIX_REGEX, '.php');
// Test suffix
if (! Strings::endsWith($newRealPath, 'Test.php')) {
return Strings::replace($newRealPath, '#\.php$#', 'Test.php');
return Strings::replace($newRealPath, self::PHP_SUFFIX_REGEX, 'Test.php');
}
return $newRealPath;

View File

@ -33,6 +33,11 @@ use ReflectionMethod;
*/
final class RouterListToControllerAnnotationsRector extends AbstractRector
{
/**
* @var string
*/
private const ACTION_RENDER_NAME_MATCHING_REGEX = '#^(action|render)(?<short_action_name>.*?$)#sm';
/**
* @var RouteInfoFactory
*/
@ -328,7 +333,7 @@ CODE_SAMPLE
$presenterPart = Strings::substring($presenterPart, 0, -Strings::length('Presenter'));
$presenterPart = StaticRectorStrings::camelCaseToDashes($presenterPart);
$match = (array) Strings::match($this->getName($classMethod), '#^(action|render)(?<short_action_name>.*?$)#sm');
$match = (array) Strings::match($this->getName($classMethod), self::ACTION_RENDER_NAME_MATCHING_REGEX);
$actionPart = lcfirst($match['short_action_name']);
return $presenterPart . '/' . $actionPart;

View File

@ -21,6 +21,11 @@ use Rector\Core\RectorDefinition\RectorDefinition;
*/
final class WrapTransParameterNameRector extends AbstractRector
{
/**
* @var string
*/
private const BETWEEN_PERCENT_CHARS_REGEX = '#%(.*?)%#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Adds %% to placeholder name of trans() method if missing', [
@ -101,7 +106,7 @@ CODE_SAMPLE
continue;
}
if (Strings::match($arrayItem->key->value, '#%(.*?)%#')) {
if (Strings::match($arrayItem->key->value, self::BETWEEN_PERCENT_CHARS_REGEX)) {
continue;
}

View File

@ -39,6 +39,12 @@ final class PregFunctionToNetteUtilsStringsRector extends AbstractRector
'preg_replace_callback' => 'replace',
];
/**
* @see https://regex101.com/r/05MPWa/1/
* @var string
*/
private const SLASH_REGEX = '#[^\\\\]\(#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Use Nette\Utils\Strings over bare preg_split() and preg_replace() functions', [
@ -196,8 +202,7 @@ CODE_SAMPLE
return $staticCall;
}
// @see https://regex101.com/r/05MPWa/1/
$match = Strings::match($patternValue, '#[^\\\\]\(#');
$match = Strings::match($patternValue, self::SLASH_REGEX);
if ($match === null) {
return $staticCall;
}

View File

@ -17,6 +17,16 @@ use Symplify\SmartFileSystem\SmartFileInfo;
*/
final class RenameSpecFileToTestFileRector extends AbstractFileSystemRector
{
/**
* @var string
*/
private const SPEC_REGEX = '#\/spec\/#';
/**
* @var string
*/
private const SPEC_SUFFIX_REGEX = '#Spec\.php$#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Rename "*Spec.php" file to "*Test.php" file',
@ -54,9 +64,9 @@ CODE_SAMPLE
private function createNewRealPath(string $oldRealPath): string
{
// suffix
$newRealPath = Strings::replace($oldRealPath, '#Spec\.php$#', 'Test.php');
$newRealPath = Strings::replace($oldRealPath, self::SPEC_SUFFIX_REGEX, 'Test.php');
// directory
return Strings::replace($newRealPath, '#\/spec\/#', '/tests/');
return Strings::replace($newRealPath, self::SPEC_REGEX, '/tests/');
}
}

View File

@ -21,6 +21,11 @@ use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
final class AnonymousFunctionNodeFactory
{
/**
* @var string
*/
private const DIM_FETCH_REGEX = '#(\\$|\\\\|\\x0)(?<number>\d+)#';
/**
* @var Parser
*/
@ -59,7 +64,7 @@ final class AnonymousFunctionNodeFactory
return $node;
}
$match = Strings::match($node->value, '#(\\$|\\\\|\\x0)(?<number>\d+)#');
$match = Strings::match($node->value, self::DIM_FETCH_REGEX);
if (! $match) {
return $node;
}

View File

@ -13,6 +13,16 @@ use Rector\Core\PhpParser\Node\Value\ValueResolver;
final class RegexMatcher
{
/**
* @var string
*/
private const LAST_E_REGEX = '#(\w+)?e(\w+)?$#';
/**
* @var string
*/
private const LETTER_SUFFIX_REGEX = '#(?<modifiers>\w+)$#';
/**
* @var ValueResolver
*/
@ -68,7 +78,7 @@ final class RegexMatcher
return null;
}
$matches = Strings::match($lastItem->value, '#(?<modifiers>\w+)$#');
$matches = Strings::match($lastItem->value, self::LETTER_SUFFIX_REGEX);
if (! isset($matches['modifiers'])) {
return null;
}
@ -78,7 +88,7 @@ final class RegexMatcher
}
// replace last "e" in the code
$lastItem->value = Strings::replace($lastItem->value, '#(\w+)?e(\w+)?$#', '$1$2');
$lastItem->value = Strings::replace($lastItem->value, self::LAST_E_REGEX, '$1$2');
return $expr;
}

View File

@ -33,6 +33,14 @@ final class EregToPcreTransformer
':xdigit:' => '[:xdigit:]',
];
/**
* @var string
*/
private const BOUND_REGEX = '/^(\d|[1-9]\d|1\d\d|
2[0-4]\d|25[0-5])
(,(\d|[1-9]\d|1\d\d|
2[0-4]\d|25[0-5])?)?$/x';
/**
* @var string
*/
@ -258,10 +266,7 @@ final class EregToPcreTransformer
$length = (int) $ii - ($i + 1);
$bound = Strings::substring($s, $start, $length);
$matches = Strings::match($bound, '/^(\d|[1-9]\d|1\d\d|
2[0-4]\d|25[0-5])
(,(\d|[1-9]\d|1\d\d|
2[0-4]\d|25[0-5])?)?$/x');
$matches = Strings::match($bound, self::BOUND_REGEX);
if (! $matches) {
throw new InvalidEregException('an invalid bound');
}

View File

@ -22,6 +22,11 @@ use Rector\Core\ValueObject\PhpVersionFeature;
*/
final class ExceptionHandlerTypehintRector extends AbstractRector
{
/**
* @var string
*/
private const HANDLE_INSENSITIVE_REGEX = '#handle#i';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
@ -78,7 +83,7 @@ CODE_SAMPLE
}
// is probably handling exceptions
if (! Strings::match((string) $node->name, '#handle#i')) {
if (! Strings::match((string) $node->name, self::HANDLE_INSENSITIVE_REGEX)) {
return null;
}

View File

@ -20,6 +20,11 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class BarewordStringRector extends AbstractRector
{
/**
* @var string
*/
private const UNDEFINED_CONSTANT_REGEX = '#Use of undefined constant (?<constant>\w+)#';
/**
* @var string[]
*/
@ -60,7 +65,7 @@ final class BarewordStringRector extends AbstractRector
$this->undefinedConstants = [];
$previousErrorHandler = set_error_handler(
function (int $severity, string $message, string $file, int $line): bool {
$match = Strings::match($message, '#Use of undefined constant (?<constant>\w+)#');
$match = Strings::match($message, self::UNDEFINED_CONSTANT_REGEX);
if ($match) {
$this->undefinedConstants[] = $match['constant'];
}

View File

@ -22,6 +22,21 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class PHPStormVarAnnotationRector extends AbstractRector
{
/**
* @var string
*/
private const SINGLE_ASTERISK_COMMENT_START_REGEX = '#^\/\* #';
/**
* @var string
*/
private const VAR_ANNOTATION_REGEX = '#\@var(\s)+\$#';
/**
* @var string
*/
private const VARIABLE_NAME_AND_TYPE_MATCH_REGEX = '#(?<variableName>\$\w+)(?<space>\s+)(?<type>[\\\\\w]+)#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change various @var annotation formats to one PHPStorm understands', [
@ -137,16 +152,12 @@ CODE_SAMPLE
// starts with "/*", instead of "/**"
if (Strings::startsWith($docContent, '/* ')) {
$docContent = Strings::replace($docContent, '#^\/\* #', '/** ');
$docContent = Strings::replace($docContent, self::SINGLE_ASTERISK_COMMENT_START_REGEX, '/** ');
}
// $value is first, instead of type is first
if (Strings::match($docContent, '#\@var(\s)+\$#')) {
$docContent = Strings::replace(
$docContent,
'#(?<variableName>\$\w+)(?<space>\s+)(?<type>[\\\\\w]+)#',
'$3$2$1'
);
if (Strings::match($docContent, self::VAR_ANNOTATION_REGEX)) {
$docContent = Strings::replace($docContent, self::VARIABLE_NAME_AND_TYPE_MATCH_REGEX, '$3$2$1');
}
return new Doc($docContent);

View File

@ -32,6 +32,11 @@ final class AddDoesNotPerformAssertionToNonAssertingTestRector extends AbstractP
*/
private const MAX_LOOKING_FOR_ASSERT_METHOD_CALL_NESTING_LEVEL = 3;
/**
* @var string
*/
private const DOES_NOT_PERFORM_ASSERTION_REGEX = '#@(doesNotPerformAssertion|expectedException\b)#';
/**
* This should prevent segfaults while going too deep into to parsed code.
* Without it, it might end-up with segfault
@ -135,7 +140,7 @@ CODE_SAMPLE
if ($classMethod->getDocComment() !== null) {
$doc = $classMethod->getDocComment();
if (Strings::match($doc->getText(), '#@(doesNotPerformAssertion|expectedException\b)#')) {
if (Strings::match($doc->getText(), self::DOES_NOT_PERFORM_ASSERTION_REGEX)) {
return true;
}
}

View File

@ -22,6 +22,16 @@ use Rector\VendorLocker\NodeVendorLocker\ClassMethodVisibilityVendorLockResolver
*/
final class PrivatizeLocalOnlyMethodRector extends AbstractRector implements ZeroCacheRectorInterface
{
/**
* @var string
*/
private const COMMON_PUBLIC_METHOD_CONTROLLER_REGEX = '#^(render|action|handle|inject)#';
/**
* @var string
*/
private const CONTROLLER_PRESENTER_SUFFIX_REGEX = '#(Controller|Presenter)$#';
/**
* @var ClassMethodVisibilityVendorLockResolver
*/
@ -159,13 +169,13 @@ CODE_SAMPLE
return false;
}
if (! Strings::match($className, '#(Controller|Presenter)$#')) {
if (! Strings::match($className, self::CONTROLLER_PRESENTER_SUFFIX_REGEX)) {
return false;
}
$classMethodName = $this->getName($classMethod);
if ((bool) Strings::match($classMethodName, '#^(render|action|handle|inject)#')) {
if ((bool) Strings::match($classMethodName, self::COMMON_PUBLIC_METHOD_CONTROLLER_REGEX)) {
return true;
}

View File

@ -16,6 +16,31 @@ use Rector\Sensio\BundleClassResolver;
*/
final class TemplateGuesser
{
/**
* @var string
*/
private const BUNDLE_SUFFIX_REGEX = '#Bundle$#';
/**
* @var string
*/
private const BUNDLE_NAME_MATCHING_REGEX = '#(?<bundle>[\w]*Bundle)#';
/**
* @var string
*/
private const SMALL_LETTER_BIG_LETTER_REGEX = '#([a-z\d])([A-Z])#';
/**
* @var string
*/
private const CONTROLLER_NAME_MATCH_REGEX = '#Controller\\\(.+)Controller$#';
/**
* @var string
*/
private const ACTION_MATCH_REGEX = '#Action$#';
/**
* @var NodeNameResolver
*/
@ -60,7 +85,7 @@ final class TemplateGuesser
$bundle = $this->resolveBundle($class, $namespace);
$controller = $this->resolveController($class);
$action = Strings::replace($method, '#Action$#');
$action = Strings::replace($method, self::ACTION_MATCH_REGEX);
$fullPath = '';
if ($bundle !== '') {
@ -81,19 +106,19 @@ final class TemplateGuesser
return '@' . $shortBundleClass;
}
$bundle = Strings::match($namespace, '#(?<bundle>[\w]*Bundle)#')['bundle'] ?? '';
$bundle = Strings::replace($bundle, '#Bundle$#');
$bundle = Strings::match($namespace, self::BUNDLE_NAME_MATCHING_REGEX)['bundle'] ?? '';
$bundle = Strings::replace($bundle, self::BUNDLE_SUFFIX_REGEX);
return $bundle !== '' ? '@' . $bundle : '';
}
private function resolveController(string $class): string
{
$match = Strings::match($class, '#Controller\\\(.+)Controller$#');
$match = Strings::match($class, self::CONTROLLER_NAME_MATCH_REGEX);
if (! $match) {
return '';
}
$controller = Strings::replace($match[1], '#([a-z\d])([A-Z])#', '\\1_\\2');
$controller = Strings::replace($match[1], self::SMALL_LETTER_BIG_LETTER_REGEX, '\\1_\\2');
return str_replace('\\', '/', $controller);
}
}

View File

@ -44,6 +44,11 @@ final class RepeatedLiteralToClassConstantRector extends AbstractRector
*/
private const MINIMAL_VALUE_OCCURRENCE = 3;
/**
* @var string
*/
private const SLASH_AND_DASH_REGEX = '#[-\\\/]#';
/**
* @var ClassInsertManipulator
*/
@ -229,7 +234,7 @@ CODE_SAMPLE
private function createConstName(string $value): string
{
// replace slashes and dashes
$value = Strings::replace($value, '#[-\\\/]#', self::UNDERSCORE);
$value = Strings::replace($value, self::SLASH_AND_DASH_REGEX, self::UNDERSCORE);
// find beginning numbers
$beginningNumbers = '';

View File

@ -49,6 +49,16 @@ final class EventListenerToEventSubscriberRector extends AbstractRector
*/
private const CONSOLE_EVENTS_CLASS = 'Symfony\Component\Console\ConsoleEvents';
/**
* @var string
*/
private const LISTENER_MATCH_REGEX = '#^(.*?)(Listener)?$#';
/**
* @var string
*/
private const SYMFONY_FAMILY_REGEX = '#^(Symfony|Sensio|Doctrine)\b#';
/**
* @var bool
*/
@ -204,7 +214,7 @@ CODE_SAMPLE
foreach ($eventListeners as $eventListener) {
// skip Symfony core listeners
if (Strings::match((string) $eventListener->getClass(), '#^(Symfony|Sensio|Doctrine)\b#')) {
if (Strings::match((string) $eventListener->getClass(), self::SYMFONY_FAMILY_REGEX)) {
continue;
}
@ -232,7 +242,7 @@ CODE_SAMPLE
$classShortName = (string) $class->name;
// remove suffix
$classShortName = Strings::replace($classShortName, '#^(.*?)(Listener)?$#', '$1');
$classShortName = Strings::replace($classShortName, self::LISTENER_MATCH_REGEX, '$1');
$class->name = new Identifier($classShortName . 'EventSubscriber');

View File

@ -106,8 +106,6 @@ final class ReturnClosurePrinter
$rootStmts = array_merge($this->useStmts, [new Nop(), $return]);
$printedContent = $this->betterStandardPrinter->prettyPrintFile($rootStmts);
$printedContent = $this->indentArray($printedContent);
return $this->indentFluentCallToNewline($printedContent);
}
@ -147,23 +145,6 @@ final class ReturnClosurePrinter
return $stmts;
}
/**
* @todo replace with https://github.com/symplify/symplify/issues/2055 when done
*/
private function indentArray(string $printedContent): string
{
// open array
$printedContent = Strings::replace($printedContent, '#\[\[#', '[[' . PHP_EOL . str_repeat(' ', 12));
// nested array
$printedContent = Strings::replace($printedContent, '#\=> \[#', '=> [' . PHP_EOL . str_repeat(' ', 16));
// close array
$printedContent = Strings::replace($printedContent, '#\]\]\)#', PHP_EOL . str_repeat(' ', 8) . ']])');
return $printedContent;
}
private function indentFluentCallToNewline(string $content): string
{
$nextCallIndentReplacement = ')' . PHP_EOL . Strings::indent('->', 8, ' ');

View File

@ -20,6 +20,21 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class ParseFileRector extends AbstractRector
{
/**
* @var string
*/
private const YAML_SUFFIX_IN_QUOTE_REGEX = '#\.(yml|yaml)(\'|")$#';
/**
* @var string
*/
private const FILE_SUFFIX_REGEX = '#\File$#';
/**
* @var string
*/
private const YAML_SUFFIX_REGEX = '#\.(yml|yaml)$#';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('session > use_strict_mode is true by default and can be removed', [
@ -67,12 +82,12 @@ final class ParseFileRector extends AbstractRector
$possibleFileNodeAsString = $this->print($possibleFileNode);
// is yml/yaml file
if (Strings::match($possibleFileNodeAsString, '#\.(yml|yaml)(\'|")$#')) {
if (Strings::match($possibleFileNodeAsString, self::YAML_SUFFIX_IN_QUOTE_REGEX)) {
return true;
}
// is probably a file variable
if (Strings::match($possibleFileNodeAsString, '#\File$#')) {
if (Strings::match($possibleFileNodeAsString, self::FILE_SUFFIX_REGEX)) {
return true;
}
@ -83,6 +98,9 @@ final class ParseFileRector extends AbstractRector
}
$nodeType = $nodeScope->getType($possibleFileNode);
return $nodeType instanceof ConstantStringType && Strings::match($nodeType->getValue(), '#\.(yml|yaml)$#');
return $nodeType instanceof ConstantStringType && Strings::match(
$nodeType->getValue(),
self::YAML_SUFFIX_REGEX
);
}
}

View File

@ -4,13 +4,12 @@ declare(strict_types=1);
namespace Rector\Core\Configuration;
use Jean85\PrettyVersions;
use OndraM\CiDetector\CiDetector;
use PackageVersions\Versions;
use Rector\ChangesReporting\Output\CheckstyleOutputFormatter;
use Rector\ChangesReporting\Output\JsonOutputFormatter;
use Rector\Core\Exception\Configuration\InvalidConfigurationException;
use Rector\Core\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Rector\Core\ValueObject\Jean85\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Symplify\SmartFileSystem\SmartFileInfo;
@ -163,7 +162,7 @@ final class Configuration
public function getPrettyVersion(): string
{
$version = new Version(Versions::getVersion('rector/rector'));
$version = PrettyVersions::getVersion('rector/rector');
return $version->getPrettyVersion();
}

View File

@ -12,6 +12,11 @@ use Rector\Core\Exception\Configuration\RectorRuleNotFoundException;
*/
final class OnlyRuleResolver
{
/**
* @var string
*/
private const SLASH_REGEX = '#\\\\#';
/**
* @var RectorClassesProvider
*/
@ -44,7 +49,7 @@ final class OnlyRuleResolver
// 3. class without slashes
foreach ($rectorClasses as $rectorClass) {
$rectorClassWithoutSlashes = Strings::replace($rectorClass, '#\\\\#');
$rectorClassWithoutSlashes = Strings::replace($rectorClass, self::SLASH_REGEX);
if ($rectorClassWithoutSlashes === $rule) {
return $rectorClass;
}

View File

@ -33,7 +33,7 @@ abstract class AbstractCommand extends Command
try {
return parent::run($input, $output);
} catch (RuntimeException $runtimeException) {
if (Strings::match($runtimeException->getMessage(), '#Not enough arguments#')) {
if (Strings::contains($runtimeException->getMessage(), 'Not enough arguments')) {
// sometimes there is "command" argument, not really needed on fail of chosen command and missing argument
$arguments = $this->getDefinition()->getArguments();
if (isset($arguments['command'])) {

View File

@ -19,6 +19,11 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class ExcludeByDocBlockExclusionCheck implements ExclusionCheckInterface
{
/**
* @var string
*/
private const NO_RECTORE_ANNOTATION_WITH_CLASS_REGEX = '#\@noRector(\s)+[^\w\\\\]#i';
public function isNodeSkippedByRector(PhpRectorInterface $phpRector, Node $node): bool
{
if ($node instanceof PropertyProperty || $node instanceof Const_) {
@ -45,7 +50,7 @@ final class ExcludeByDocBlockExclusionCheck implements ExclusionCheckInterface
private function hasNoRectorComment(PhpRectorInterface $phpRector, Doc $doc): bool
{
// bare @noRector ignored all rules
if (Strings::match($doc->getText(), '#\@noRector(\s)+[^\w\\\\]#')) {
if (Strings::match($doc->getText(), self::NO_RECTORE_ANNOTATION_WITH_CLASS_REGEX)) {
return true;
}

View File

@ -18,6 +18,16 @@ use Symplify\SmartFileSystem\SmartFileInfo;
*/
final class FilesFinder
{
/**
* @var string
*/
private const STARTS_WITH_ASTERISK_REGEX = '#^\*(.*?)[^*]$#';
/**
* @var string
*/
private const ENDS_WITH_ASTERISK_REGEX = '#^[^*](.*?)\*$#';
/**
* @var SmartFileInfo[][]
*/
@ -186,12 +196,12 @@ final class FilesFinder
private function normalizeForFnmatch(string $path): string
{
// ends with *
if (Strings::match($path, '#^[^*](.*?)\*$#')) {
if (Strings::match($path, self::ENDS_WITH_ASTERISK_REGEX)) {
return '*' . $path;
}
// starts with *
if (Strings::match($path, '#^\*(.*?)[^*]$#')) {
if (Strings::match($path, self::STARTS_WITH_ASTERISK_REGEX)) {
return $path . '*';
}

View File

@ -14,6 +14,16 @@ final class TypeAnalyzer
*/
private const EXTRA_TYPES = ['object'];
/**
* @var string
*/
private const ARRAY_TYPE_REGEX = '#array<(.*?)>#';
/**
* @var string
*/
private const SQUARE_BRACKET_REGEX = '#(\[\])+$#';
/**
* @var string[]
*/
@ -49,7 +59,7 @@ final class TypeAnalyzer
$singleType = strtolower($singleType);
// remove [] from arrays
$singleType = Strings::replace($singleType, '#(\[\])+$#');
$singleType = Strings::replace($singleType, self::SQUARE_BRACKET_REGEX);
if (in_array($singleType, array_merge($this->phpSupportedTypes, self::EXTRA_TYPES), true)) {
return true;
@ -77,7 +87,7 @@ final class TypeAnalyzer
return 'callable';
}
if (Strings::match(strtolower($type), '#array<(.*?)>#')) {
if (Strings::match(strtolower($type), self::ARRAY_TYPE_REGEX)) {
return 'array';
}

View File

@ -18,6 +18,11 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
final class NodeTransformer
{
/**
* @var string
*/
private const PERCENT_TEXT_REGEX = '#^%\w$#';
/**
* From:
* - sprintf("Hi %s", $name);
@ -40,7 +45,7 @@ final class NodeTransformer
$arrayMessageParts = [];
foreach ($messageParts as $messagePart) {
if (Strings::match($messagePart, '#^%\w$#')) {
if (Strings::match($messagePart, self::PERCENT_TEXT_REGEX)) {
/** @var Expr $messagePartNode */
$messagePartNode = array_shift($arrayItems);
} else {

View File

@ -20,6 +20,16 @@ use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
final class InlineCodeParser
{
/**
* @var string
*/
private const PRESLASHED_DOLLAR_REGEX = '#\\\\\$#';
/**
* @var string
*/
private const CURLY_BRACKET_WRAPPER_REGEX = "#'{(\\\$.*?)}'#";
/**
* @var Parser
*/
@ -76,9 +86,9 @@ final class InlineCodeParser
// remove "
$content = trim($this->betterStandardPrinter->print($content), '""');
// use \$ → $
$content = Strings::replace($content, '#\\\\\$#', '$');
$content = Strings::replace($content, self::PRESLASHED_DOLLAR_REGEX, '$');
// use \'{$...}\' → $...
return Strings::replace($content, "#'{(\\\$.*?)}'#", '$1');
return Strings::replace($content, self::CURLY_BRACKET_WRAPPER_REGEX, '$1');
}
if ($content instanceof Concat) {

View File

@ -30,6 +30,46 @@ use Symplify\SmartFileSystem\SmartFileInfo;
*/
final class BetterStandardPrinter extends Standard
{
/**
* @var string
*/
private const START_COMMENT_REGEX = '#\/*\*(.*?)\*\/#s';
/**
* @var string
*/
private const START_GRID_COMMENT_REGEX = '#^(\s+)?\#(.*?)$#m';
/**
* @var string
*/
private const START_DOUBLE_SLASH_COMMENT_REGEX = '#\/\/(.*?)$#m';
/**
* @var string
*/
private const NEWLINE_END_REGEX = "#\n$#";
/**
* @var string
*/
private const FOUR_SPACE_START_REGEX = '#^ {4}#m';
/**
* @var string
*/
private const USE_REGEX = '#( use)\(#';
/**
* @var string
*/
private const QUOTED_SLASH_REGEX = "#'|\\\\(?=[\\\\']|$)#";
/**
* @var string
*/
private const EXTRA_SPACE_BEFORE_NOP_REGEX = '#^[ \t]+$#m';
/**
* Use space by default
* @var string
@ -80,7 +120,7 @@ final class BetterStandardPrinter extends Standard
$content = parent::printFormatPreserving($stmts, $origStmts, $origTokens);
// add new line in case of added stmts
if (count($stmts) !== count($origStmts) && ! (bool) Strings::match($content, "#\n$#")) {
if (count($stmts) !== count($origStmts) && ! (bool) Strings::match($content, self::NEWLINE_END_REGEX)) {
$content .= $this->nl;
}
@ -218,7 +258,7 @@ final class BetterStandardPrinter extends Standard
}
// remove extra spaces before new Nop_ nodes, @see https://regex101.com/r/iSvroO/1
return Strings::replace($content, '#^[ \t]+$#m');
return Strings::replace($content, self::EXTRA_SPACE_BEFORE_NOP_REGEX);
}
/**
@ -232,7 +272,7 @@ final class BetterStandardPrinter extends Standard
*/
protected function pSingleQuotedString(string $string): string
{
return "'" . Strings::replace($string, "#'|\\\\(?=[\\\\']|$)#", '\\\\$0') . "'";
return "'" . Strings::replace($string, self::QUOTED_SLASH_REGEX, '\\\\$0') . "'";
}
/**
@ -257,7 +297,7 @@ final class BetterStandardPrinter extends Standard
{
$closureContent = parent::pExpr_Closure($closure);
return Strings::replace($closureContent, '#( use)\(#', '$1 (');
return Strings::replace($closureContent, self::USE_REGEX, '$1 (');
}
/**
@ -398,7 +438,7 @@ final class BetterStandardPrinter extends Standard
continue;
}
$whitespaces = count(Strings::matchAll($fileInfo->getContents(), '#^ {4}#m'));
$whitespaces = count(Strings::matchAll($fileInfo->getContents(), self::FOUR_SPACE_START_REGEX));
$tabs = count(Strings::matchAll($fileInfo->getContents(), '#^\t#m'));
// tab vs space
@ -409,16 +449,16 @@ final class BetterStandardPrinter extends Standard
private function removeComments(string $printerNode): string
{
// remove /** ... */
$printerNode = Strings::replace($printerNode, '#\/*\*(.*?)\*\/#s');
$printerNode = Strings::replace($printerNode, self::START_COMMENT_REGEX);
// remove /* ... */
$printerNode = Strings::replace($printerNode, '#\/*\*(.*?)\*\/#s');
$printerNode = Strings::replace($printerNode, self::START_COMMENT_REGEX);
// remove # ...
$printerNode = Strings::replace($printerNode, '#^(\s+)?\#(.*?)$#m');
$printerNode = Strings::replace($printerNode, self::START_GRID_COMMENT_REGEX);
// remove // ...
return Strings::replace($printerNode, '#\/\/(.*?)$#m');
return Strings::replace($printerNode, self::START_DOUBLE_SLASH_COMMENT_REGEX);
}
private function moveCommentsFromAttributeObjectToCommentsAttribute(array $nodes): void

View File

@ -13,6 +13,11 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
abstract class AbstractPHPUnitRector extends AbstractRector
{
/**
* @var string
*/
private const TEST_ANNOTATOIN_REGEX = '#@test\b#';
protected function isTestClassMethod(ClassMethod $classMethod): bool
{
if (! $classMethod->isPublic()) {
@ -25,7 +30,7 @@ abstract class AbstractPHPUnitRector extends AbstractRector
$docComment = $classMethod->getDocComment();
if ($docComment !== null) {
return (bool) Strings::match($docComment->getText(), '#@test\b#');
return (bool) Strings::match($docComment->getText(), self::TEST_ANNOTATOIN_REGEX);
}
return false;

View File

@ -11,6 +11,16 @@ use Nette\Utils\Strings;
*/
final class StaticRectorStrings
{
/**
* @var string
*/
private const UNDERSCORE_REGEX = '#_#';
/**
* @var string
*/
private const CAMEL_CASE_SPLIT_REGEX = '#([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)#';
/**
* @param string[] $array
*/
@ -92,7 +102,7 @@ final class StaticRectorStrings
public static function constantToDashes(string $string): string
{
$string = strtolower($string);
return Strings::replace($string, '#_#', '-');
return Strings::replace($string, self::UNDERSCORE_REGEX, '-');
}
private static function camelCaseToGlue(string $input, string $glue): string
@ -101,7 +111,7 @@ final class StaticRectorStrings
return $input;
}
$matches = Strings::matchAll($input, '#([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)#');
$matches = Strings::matchAll($input, self::CAMEL_CASE_SPLIT_REGEX);
$parts = [];
foreach ($matches as $match) {
$parts[] = $match[0] === strtoupper($match[0]) ? strtolower($match[0]) : lcfirst($match[0]);

View File

@ -1,62 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\ValueObject\Jean85;
use Nette\Utils\Strings;
/**
* Temporary local fork, until PHP 8.0 gets merged and tagged
* https://github.com/Jean85/pretty-package-versions/pull/25
*/
final class Version
{
/**
* @var int
*/
private const SHORT_COMMIT_LENGTH = 7;
/**
* @var string
*/
private $shortVersion;
/**
* @var string
*/
private $commitHash;
/**
* @var bool
*/
private $versionIsTagged = false;
public function __construct(string $version)
{
$splittedVersion = explode('@', $version);
$this->shortVersion = $splittedVersion[0];
$this->commitHash = $splittedVersion[1];
$this->versionIsTagged = (bool) Strings::match($this->shortVersion, '#[^v\d\.]#');
}
public function getPrettyVersion(): string
{
if ($this->versionIsTagged) {
return $this->shortVersion;
}
return $this->getVersionWithShortCommit();
}
private function getVersionWithShortCommit(): string
{
return $this->shortVersion . '@' . $this->getShortCommitHash();
}
private function getShortCommitHash(): string
{
return Strings::substring($this->commitHash, 0, self::SHORT_COMMIT_LENGTH);
}
}

View File

@ -66,11 +66,11 @@ final class ConfigurableRectorRule implements Rule
private function hasRectorInClassName(Class_ $class): bool
{
if ($class->name === null) {
if ($class->namespacedName === null) {
return false;
}
return Strings::match($class->name->toString(), '#Rector$#') !== null;
return Strings::endsWith((string) $class->namespacedName, 'Rector');
}
private function implementsConfigurableInterface(Class_ $class): bool

View File

@ -27,6 +27,11 @@ final class RectorRuleAndValueObjectHaveSameStartsRule implements Rule
*/
public const ERROR = 'Value Object class name "%s" is incorrect. The correct class name is "%s".';
/**
* @var string
*/
private const RECTOR_SUFFIX_REGEX = '#Rector$#';
public function getNodeType(): string
{
return MethodCall::class;
@ -60,7 +65,7 @@ final class RectorRuleAndValueObjectHaveSameStartsRule implements Rule
self::ERROR,
$valueObjectClassName,
// @see https://regex101.com/r/F8z9PY/1
Strings::replace($rectorRuleClassName, '#Rector$#', '')
Strings::replace($rectorRuleClassName, self::RECTOR_SUFFIX_REGEX, '')
)];
}

View File

@ -9,6 +9,11 @@ use Nette\Utils\Strings;
final class PHPStanTypeClassFinder
{
/**
* @var string
*/
private const ACCESSORY_SEPARATED_REGEX = '#\bAccessory\b#';
/**
* @return class-string[]
*/
@ -52,7 +57,7 @@ final class PHPStanTypeClassFinder
continue;
}
if (Strings::match($classLike, '#\\\\Accessory\\\\#')) {
if (Strings::match($classLike, self::ACCESSORY_SEPARATED_REGEX)) {
continue;
}

View File

@ -13,6 +13,11 @@ use Throwable;
final class ParallelTaskRunner
{
/**
* @var string
*/
private const FATAL_ERROR_REGEX = '#(Fatal error)|(\[ERROR\])#';
/**
* @var string
*/
@ -205,7 +210,7 @@ final class ParallelTaskRunner
$fullOutput = array_filter([$process->getOutput(), $process->getErrorOutput()]);
$output = implode("\n", $fullOutput);
$actualErrorHappened = Strings::match($output, '#(Fatal error)|(\[ERROR\])#');
$actualErrorHappened = Strings::match($output, self::FATAL_ERROR_REGEX);
if (! $actualErrorHappened) {
return;