#!/usr/bin/env php * Dariusz Rumiński * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * @author Fabien Potencier * @author Dariusz Rumiński */ if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { error_reporting(-1); } if (defined('HHVM_VERSION_ID')) { fwrite(STDERR, "HHVM is not supported.\n"); if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); } else { exit(1); } } elseif (!defined('PHP_VERSION_ID') || \PHP_VERSION_ID < 50600 || \PHP_VERSION_ID >= 70400) { fwrite(STDERR, "PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.3.*.\n"); if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); } else { exit(1); } } set_error_handler(function ($severity, $message, $file, $line) { if ($severity & error_reporting()) { throw new ErrorException($message, 0, $severity, $file, $line); } }); $require = true; if (class_exists('Phar')) { // Maybe this file is used as phar-stub? Let's try! try { Phar::mapPhar('php-cs-fixer.phar'); require_once 'phar://php-cs-fixer.phar/vendor/autoload.php'; $require = false; } catch (PharException $e) { } } if ($require) { // OK, it's not, let give Composer autoloader a try! if (file_exists($a = __DIR__.'/../../autoload.php')) { require_once $a; } else { require_once __DIR__.'/vendor/autoload.php'; } } unset($require); use Composer\XdebugHandler\XdebugHandler; use PhpCsFixer\Console\Application; // Restart if xdebug is loaded, unless the environment variable PHP_CS_FIXER_ALLOW_XDEBUG is set. $xdebug = new XdebugHandler('PHP_CS_FIXER', '--ansi'); $xdebug->check(); unset($xdebug); $application = new Application(); $application->run(); __HALT_COMPILER(); ?> bdev-tools/ci-integration.sh`\|U{dev-tools/info-extractor.phpL`\L*d)src/AbstractLinesBeforeNamespaceFixer.php'`\'z/'src/AbstractFixer.php`\lsrc/ConfigInterface.php\`\\-̶src/Linter/ProcessLinter.php1 `\1 + %src/Linter/LintingResultInterface.phps`\sO:Nsrc/Linter/TokenizerLinter.php`\Ķsrc/Linter/LintingException.phpg`\gKsrc/Linter/CachingLinter.php`\H\Gζ)src/Linter/UnavailableLinterException.phps`\sGɶ*src/Linter/ProcessLinterProcessBuilder.phpR`\RCsrc/Linter/Linter.php8`\8X϶%src/Linter/TokenizerLintingResult.php`\Xnt#src/Linter/ProcessLintingResult.php`\yȶsrc/Linter/LinterInterface.php`\/Xƶsrc/FileRemoval.phpW`\W_]&src/FixerConfiguration/FixerOption.php`\wW-src/FixerConfiguration/AllowedValueSubset.php`\8/src/FixerConfiguration/FixerOptionInterface.php\`\\j,0src/FixerConfiguration/DeprecatedFixerOption.php`\3j)4src/FixerConfiguration/AliasedFixerOptionBuilder.php`\hs-src/FixerConfiguration/FixerOptionBuilder.php>`\>Ɠ? 5src/FixerConfiguration/FixerConfigurationResolver.phpo`\o_H9src/FixerConfiguration/DeprecatedFixerOptionInterface.php`\$=src/FixerConfiguration/FixerConfigurationResolverRootless.php`\K3-src/FixerConfiguration/AliasedFixerOption.php`\y>src/FixerConfiguration/FixerConfigurationResolverInterface.php`\#8src/FixerConfiguration/InvalidOptionsForEnvException.php`\Rsrc/PharCheckerInterface.php`\ѝsrc/ToolInfo.php`\ID!src/Runner/FileFilterIterator.php\`\\q薶)src/Runner/FileCachingLintingIterator.php`\ev src/Runner/Runner.phpy`\y4"src/Runner/FileLintingIterator.php;`\;>|src/FixerFileProcessedEvent.php`\-_&src/AbstractFunctionReferenceFixer.php`\'- src/Preg.php; `\; U)src/PregException.phpg`\g`\>|Y5src/Console/Command/DescribeNameNotFoundException.php{`\{BM)src/Console/Command/SelfUpdateCommand.php`\,%src/Console/Command/ReadmeCommand.php`\axsrc/WordMatcher.php`\xsrc/Finder.php.`\.8Ͷ'src/AbstractDoctrineAnnotationFixer.phpk `\k ;"src/AbstractNoUselessElseFixer.php `\ w`{src/Differ/UnifiedDiffer.php`\/1src/Differ/NullDiffer.php`\D+src/Differ/SebastianBergmannShortDiffer.phpb`\bZ!5#src/Differ/DiffConsoleFormatter.phpd`\djsrc/Differ/FullDiffer.php`\Am&src/Differ/SebastianBergmannDiffer.php=`\=qsrc/Differ/DifferInterface.php|`\|hB,src/Fixer/WhitespacesAwareFixerInterface.php`\gs3src/Fixer/ConfigurationDefinitionFixerInterface.php`\M㵶>src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.phpa"`\a"I>src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php& `\& aCsrc/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php(`\( Gsrc/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php`\+L.src/Fixer/Whitespace/ArrayIndentationFixer.php `\ Q2src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.phpY `\Y nA0src/Fixer/Whitespace/HeredocIndentationFixer.php `\ 2src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php`\q7src/Fixer/Whitespace/MethodChainingIndentationFixer.phpt`\t_z(src/Fixer/Whitespace/LineEndingFixer.phpz`\z 5src/Fixer/Whitespace/CompactNullableTypehintFixer.php`\ig:5src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php`\J/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php. `\. f6src/Fixer/Whitespace/BlankLineBeforeStatementFixer.phpR`\R3P2src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php`\O7src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php/`\/ж-src/Fixer/Whitespace/IndentationTypeFixer.phph `\h VӶ:src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.php;`\;Sж(src/Fixer/PhpTag/FullOpeningTagFixer.php`\Z(src/Fixer/PhpTag/NoShortEchoTagFixer.php`\^h2src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php`\.t&src/Fixer/PhpTag/NoClosingTagFixer.php`\DҫĶ2src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php`\O*N7src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.php`\BuQ2src/Fixer/ReturnNotation/ReturnAssignmentFixer.php`\pEk1src/Fixer/ReturnNotation/NoUselessReturnFixer.phpp`\p`3Ͷ6src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php `\ Az/src/Fixer/ClassUsage/DateTimeImmutableFixer.php `\ ɀ=src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.php`\Q<=src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php `\ uŶ+src/Fixer/LanguageConstruct/IsNullFixer.php`\/ 5src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php`\jBq0src/Fixer/LanguageConstruct/DirConstantFixer.php4 `\4 ݹ=src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php9 `\9 nr7src/Fixer/LanguageConstruct/FunctionToConstantFixer.phpV`\Vz6src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.phpV`\V׶=src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php`\Z#:src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php `\ ;7src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php`\Bn)*src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php`\-Ha&src/Fixer/Phpdoc/PhpdocIndentFixer.php.`\.^U%src/Fixer/Phpdoc/PhpdocOrderFixer.php9 `\9 \׷4src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php4`\4O]9src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php`\7)src/Fixer/Phpdoc/PhpdocInlineTagFixer.php;`\;NL'src/Fixer/Phpdoc/NoEmptyPhpdocFixer.phpv`\vX%src/Fixer/Phpdoc/PhpdocTypesFixer.phpw`\wPuu-src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php`\I)src/Fixer/Phpdoc/PhpdocToCommentFixer.php`\X&src/Fixer/Phpdoc/PhpdocScalarFixer.php>`\> xz7src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php}`\}/K]))src/Fixer/Phpdoc/PhpdocNoPackageFixer.php`\Vp!(3src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php `\ 7c3src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php`\<'src/Fixer/Phpdoc/PhpdocSummaryFixer.php`\ .src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.phpk`\k|N1src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php`\%src/Fixer/Phpdoc/PhpdocAlignFixer.phpQ`\Q&Ҷ/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php@ `\@ W(src/Fixer/Phpdoc/PhpdocNoAccessFixer.php`\Ɨ:9src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php`\v4src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.phpA `\A +tV$src/Fixer/Phpdoc/PhpdocTrimFixer.php`\nd1src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.phpc`\c;R*src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.phpL `\L $+*src/Fixer/Phpdoc/PhpdocSeparationFixer.phpT `\T XξBsrc/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php `\ 5Csrc/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php`\착)<src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php`\ N`Asrc/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php6`\6t'@src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php`\v&src/Fixer/DeprecatedFixerInterface.php`\=rT*src/Fixer/Naming/NoHomoglyphNamesFixer.php `\ p(src/Fixer/ConfigurableFixerInterface.php`\<src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php`\N aGsrc/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php`\!?src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.phpP`\P4src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php`\$ݓ0src/Fixer/ArrayNotation/TrimArraySpacesFixer.php`\7"Asrc/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php`\1&ն,src/Fixer/ArrayNotation/ArraySyntaxFixer.php@ `\@ Y>src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php `\ ў^j+src/Fixer/Casing/LowercaseKeywordsFixer.phpM`\M 2src/Fixer/Casing/LowercaseStaticReferenceFixer.php`\ёE-src/Fixer/Casing/MagicConstantCasingFixer.php>`\>6ݶ,src/Fixer/Casing/LowercaseConstantsFixer.php`\= .src/Fixer/Casing/NativeFunctionCasingFixer.phpt`\t+src/Fixer/Casing/MagicMethodCasingFixer.phpK`\K&%src/Fixer/Strict/StrictParamFixer.phpe `\e 1*src/Fixer/Strict/StrictComparisonFixer.phpB`\B9K,src/Fixer/Strict/DeclareStrictTypesFixer.php `\ "9src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php`\78x7src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php`\vȶ1src/Fixer/ControlStructure/NoUselessElseFixer.php`\̠*src/Fixer/ControlStructure/ElseifFixer.php`\+-src/Fixer/ControlStructure/YodaStyleFixer.php3`\3q"7src/Fixer/ControlStructure/NoSuperfluousElseifFixer.phpj`\jk43src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php`\=+src/Fixer/ControlStructure/IncludeFixer.php< `\< 舜L@src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.phpk`\k>src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php`\,=src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php`\YN2src/Fixer/ControlStructure/NoBreakCommentFixer.phpD!`\D!#src/Fixer/DefinedFixerInterface.php`\ܶ*src/Fixer/ListNotation/ListSyntaxFixer.php `\ }src/Fixer/FixerInterface.php`\L;src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php `\ Ӷ-src/Fixer/StringNotation/SingleQuoteFixer.php`\XYh8src/Fixer/StringNotation/ExplicitStringVariableFixer.php`\f0src/Fixer/StringNotation/NoBinaryStringFixer.php`\9t1src/Fixer/StringNotation/HeredocToNowdocFixer.phpF`\Fg2src/Fixer/StringNotation/StringLineEndingFixer.php`\!0src/Fixer/Import/SingleLineAfterImportsFixer.php `\ Qw,5.src/Fixer/Import/NoLeadingImportSlashFixer.php`\軹)src/Fixer/Import/NoUnusedImportsFixer.php`\u3src/Fixer/Import/FullyQualifiedStrictTypesFixer.php> `\> Tʶ(src/Fixer/Import/OrderedImportsFixer.phpN)`\N)t[H2src/Fixer/Import/SingleImportPerStatementFixer.php[`\[*(src/Fixer/Operator/PreIncrementFixer.php`\0src/Fixer/Operator/StandardizeNotEqualsFixer.php`\-src/Fixer/Operator/AlignEqualsFixerHelper.php`\9src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php`\#jζ*src/Fixer/Operator/IncrementStyleFixer.php`\r;src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php`\MsĶ3src/Fixer/Operator/TernaryToNullCoalescingFixer.php%`\%Ͷ0src/Fixer/Operator/StandardizeIncrementFixer.phpH `\H U]2src/Fixer/Operator/AlignDoubleArrowFixerHelper.phpY `\Y d)src/Fixer/Operator/NewWithBracesFixer.php$ `\$ [w0ֶ0src/Fixer/Operator/BinaryOperatorSpacesFixer.phpC`\C aC1src/Fixer/Operator/TernaryOperatorSpacesFixer.php2`\2fx/src/Fixer/Operator/UnaryOperatorSpacesFixer.phpZ`\Z^40src/Fixer/Operator/NotOperatorWithSpaceFixer.php,`\,,src/Fixer/Operator/LogicalOperatorsFixer.phpQ`\Qh'src/Fixer/Operator/ConcatSpaceFixer.php~ `\~ eD+1src/Fixer/Comment/SingleLineCommentStyleFixer.php `\ Wn8src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.phpp`\p~B(src/Fixer/Comment/HeaderCommentFixer.php0`\0|ڶ*src/Fixer/Comment/CommentToPhpdocFixer.php `\ n-src/Fixer/Comment/HashToSlashCommentFixer.php`\MZ`9src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php`\Q)src/Fixer/Comment/NoEmptyCommentFixer.php `\ Gf#src/Fixer/Alias/EregToPregFixer.php `\ ^'src/Fixer/Alias/MbStrFunctionsFixer.php `\ \&src/Fixer/Alias/SetTypeToCastFixer.php`\׶)src/Fixer/Alias/NoAliasFunctionsFixer.php`\1O)src/Fixer/Alias/NoMixedEchoPrintFixer.php `\ :8?,src/Fixer/Alias/PowToExponentiationFixer.phpH`\Ho,src/Fixer/Alias/BacktickToShellExecFixer.php1 `\1 mur+src/Fixer/Alias/RandomApiMigrationFixer.php`\26src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.phpu`\u>src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.phpo`\o2src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php`\Lt=src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.phpY`\Yt0src/Fixer/ClassNotation/ClassDefinitionFixer.php)`\)<ʶ3src/Fixer/ClassNotation/VisibilityRequiredFixer.phpI`\I=.3src/Fixer/ClassNotation/ProtectedToPrivateFixer.phpg`\g$?src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php`\g-src/Fixer/ClassNotation/SelfAccessorFixer.php`\V[5src/Fixer/ClassNotation/OrderedClassElementsFixer.phpI%`\I%\3src/Fixer/ClassNotation/FinalInternalClassFixer.php*`\*:src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php`\Ʈ1src/Fixer/ClassNotation/MethodSeparationFixer.php`\T.<src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php`\/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.phpe`\e]X9src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php `\ }ת*src/Fixer/PhpUnit/PhpUnitTargetVersion.php`\I0src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php$(`\$(M-src/Fixer/PhpUnit/PhpUnitExpectationFixer.php``\`AB,src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php`\5H۶+src/Fixer/PhpUnit/PhpUnitConstructFixer.php`\ľڶ9src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php `\ Dk.src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php`\%/0src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.phpM`\M#Z$̶0src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php*`\*7t/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php`\EͶ(src/Fixer/PhpUnit/PhpUnitStrictFixer.phpi`\i&src/Fixer/PhpUnit/PhpUnitMockFixer.phpK`\K^ԣ;src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php2/`\2/Է9src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.phpv`\v%S箶0src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php `\ E϶@src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php`\ΡCsrc/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php`\58Bsrc/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.php`\.6src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php`\_k-src/Fixer/Semicolon/NoEmptyStatementFixer.php `\ od/src/Fixer/CastNotation/NoShortBoolCastFixer.php`\E+src/Fixer/CastNotation/NoUnsetCastFixer.php`\N*Y/src/Fixer/CastNotation/ShortScalarCastFixer.php`\l*src/Fixer/CastNotation/CastSpacesFixer.phpE`\Ec5src/Fixer/CastNotation/ModernizeTypesCastingFixer.php `\ I-src/Fixer/CastNotation/LowercaseCastFixer.phpr`\r\src/Fixer/Basic/BracesFixer.php"]`\"]f.src/Fixer/Basic/NonPrintableCharacterFixer.php;`\;϶src/Fixer/Basic/Psr4Fixer.php`\[src/Fixer/Basic/Psr0Fixer.php`\YQ!src/Fixer/Basic/EncodingFixer.php:`\:>ݶ7src/Fixer/FunctionNotation/FunctionDeclarationFixer.php5`\5\68src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php`\ȹ0src/Fixer/FunctionNotation/StaticLambdaFixer.phpt`\tKL7src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php(`\(&9src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php `\ ~=src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php `\ 8Esrc/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php`\#6src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php `\ i^Џ.src/Fixer/FunctionNotation/VoidReturnFixer.php@`\@f@<src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php4`\4Qv/src/Fixer/FunctionNotation/ImplodeCallFixer.phpJ `\J Q.src/Fixer/FunctionNotation/FopenFlagsFixer.php`\9src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php`\A?W 2src/Fixer/FunctionNotation/FopenFlagOrderFixer.phpX`\XB src/FileReader.php`\1src/AbstractProxyFixer.php3`\3nm5src/Tokenizer/Transformer/NullableTypeTransformer.php`\ZP2src/Tokenizer/Transformer/ReturnRefTransformer.php`\>,src/Tokenizer/Transformer/UseTransformer.php`\9':src/Tokenizer/Transformer/NamespaceOperatorTransformer.php`\t2src/Tokenizer/Transformer/TypeColonTransformer.phpj`\ju}6src/Tokenizer/Transformer/ArrayTypehintTransformer.php`\q:src/Tokenizer/Transformer/WhitespacyCommentTransformer.php`\嬆3src/Tokenizer/Transformer/CurlyBraceTransformer.php`\Şk4src/Tokenizer/Transformer/SquareBraceTransformer.php `\ @src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php`\Զ8src/Tokenizer/Transformer/TypeAlternationTransformer.phpi`\i96src/Tokenizer/Transformer/ClassConstantTransformer.php`\/src/Tokenizer/Transformer/ImportTransformer.php#`\# P%src/Tokenizer/AbstractTransformer.php{`\{t &src/Tokenizer/TransformerInterface.phpP`\PY,src/Tokenizer/Analyzer/ArgumentsAnalyzer.php `\ {8src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php`\ˆ5src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php `\ 74src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.phpB`\BmNt0src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php,`\,õ>src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php`\`90src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php`\j~+src/Tokenizer/Analyzer/CommentsAnalyzer.php`\:-src/Tokenizer/Analyzer/NamespacesAnalyzer.php`\sb,src/Tokenizer/Analyzer/FunctionsAnalyzer.php`\Dsrc/Tokenizer/CT.php`\ src/Tokenizer/TokensAnalyzer.php_.`\_.G絶src/Tokenizer/Token.php!`\!&TYsrc/Tokenizer/Tokens.phpaR`\aRsrc/Tokenizer/Transformers.php`\3J{src/Tokenizer/CodeHasher.php`\VJ0src/Tokenizer/Resolver/TypeShortNameResolver.phpZ`\Z]g:src/Tokenizer/Generator/NamespacedStringTokenGenerator.php`\݊j src/Utils.php`\`cmsrc/Report/XmlReporter.phpO `\O {w src/Report/ReporterInterface.php`\Y9!src/Report/CheckstyleReporter.php`\gsrc/Report/JsonReporter.php`\ (ʶsrc/Report/ReporterFactory.php`\Tȶsrc/Report/JunitReporter.php `\ /src/Report/ReportSummary.php`\dsrc/Report/TextReporter.phpf`\fN X src/Cache/DirectoryInterface.php`\Fd#src/Cache/CacheManagerInterface.php`\D src/Cache/SignatureInterface.php`\ȶsrc/Cache/FileCacheManager.php`\ Y4src/Cache/Cache.php_`\_j}Csrc/Cache/Directory.phpP`\PT"src/Cache/FileHandlerInterface.php`\I:src/Cache/FileHandler.phpB`\BfA̶src/Cache/NullCacheManager.php`\4nsrc/Cache/Signature.php$`\$Jzsrc/Cache/CacheInterface.php`\'뭶src/AbstractFopenFlagFixer.php`\'aж,src/FixerDefinition/VersionSpecification.phpm`\m0E 5src/FixerDefinition/VersionSpecificationInterface.php`\ݬ0src/FixerDefinition/FixerDefinitionInterface.phpb`\b4/:src/FixerDefinition/VersionSpecificCodeSampleInterface.php`\l7src/FixerDefinition/FileSpecificCodeSampleInterface.php`\vO.src/FixerDefinition/FileSpecificCodeSample.phpj`\jd"src/FixerDefinition/CodeSample.php`\@1src/FixerDefinition/VersionSpecificCodeSample.php`\S+src/FixerDefinition/CodeSampleInterface.php`\4/'src/FixerDefinition/FixerDefinition.php`\z=src/RuleSetInterface.php[`\[src/Error/Error.php1`\1?~src/Error/ErrorsManager.php`\_xsrc/FixerNameValidator.php`\Rsrc/RuleSet.php/`\/۶ src/AbstractPhpdocTypesFixer.php`\#src/AbstractPsrAutoloadingFixer.php `\ Fsrc/FixerFactory.phpd`\daضAsrc/ConfigurationException/InvalidFixerConfigurationException.php@`\@Gsrc/ConfigurationException/InvalidForEnvFixerConfigurationException.php`\wSζBsrc/ConfigurationException/RequiredFixerConfigurationException.php`\<src/ConfigurationException/InvalidConfigurationException.php`\)U!src/Doctrine/Annotation/Token.php`\y7"src/Doctrine/Annotation/Tokens.php `\ 9src/DocBlock/Tag.php`\;]˶src/DocBlock/Annotation.php `\ ؑsrc/DocBlock/DocBlock.php`\k ksrc/DocBlock/TagComparator.phpp`\pUY!src/DocBlock/ShortDescription.php`\^xsrc/DocBlock/Line.php,`\,3 src/ToolInfoInterface.phpB`\B-Wsrc/Config.php `\ Ls*src/Indicator/PhpUnitTestCaseIndicator.phpc`\cYӍsrc/WhitespacesFixerConfig.php`\Bsrc/StdinFileInfo.php#`\#`)D src/AbstractAlignFixerHelper.php`\jI%vendor/psr/log/Psr/Log/NullLogger.php`\*vendor/psr/log/Psr/Log/LoggerInterface.php`\sg)vendor/psr/log/Psr/Log/AbstractLogger.php;`\;>3[&vendor/psr/log/Psr/Log/LoggerTrait.phpi`\i35޶+vendor/psr/log/Psr/Log/LoggerAwareTrait.php`\TB3vendor/psr/log/Psr/Log/InvalidArgumentException.php``\` X1/vendor/psr/log/Psr/Log/LoggerAwareInterface.php|`\|$#vendor/psr/log/Psr/Log/LogLevel.php`\j8"vendor/composer/autoload_files.php5`\5|p#vendor/composer/autoload_static.php*`\*a!vendor/composer/autoload_real.php`\eD!vendor/composer/autoload_psr4.php`\l'vendor/composer/ClassLoader.php`\x`4vendor/composer/xdebug-handler/src/XdebugHandler.php!`\!2|C0vendor/composer/xdebug-handler/src/PhpConfig.php`\*D-vendor/composer/xdebug-handler/src/Status.php `\ .vendor/composer/xdebug-handler/src/Process.phpt`\tی&,vendor/composer/semver/src/VersionParser.php*`\*)mƶ%vendor/composer/semver/src/Semver.phpv`\vț4vendor/composer/semver/src/Constraint/Constraint.php `\ O7=vendor/composer/semver/src/Constraint/ConstraintInterface.php`\0C,<vendor/composer/semver/src/Constraint/AbstractConstraint.php`\>9vendor/composer/semver/src/Constraint/EmptyConstraint.php`\!-ؙ9vendor/composer/semver/src/Constraint/MultiConstraint.php1`\1xU)vendor/composer/semver/src/Comparator.php`\wl%vendor/composer/autoload_classmap.php`\_O"'vendor/composer/autoload_namespaces.php`\9֊Z3vendor/symfony/finder/Comparator/DateComparator.php#`\#ζ/vendor/symfony/finder/Comparator/Comparator.php`\5vendor/symfony/finder/Comparator/NumberComparator.php|`\|ITͶ vendor/symfony/finder/Finder.php!`\!es3%vendor/symfony/finder/SplFileInfo.php`\9vendor/symfony/finder/Iterator/FilenameFilterIterator.phpr`\rtu<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php5`\5;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php`\͝qAvendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php`\f_9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpZ`\Z(&3vendor/symfony/finder/Iterator/SortableIterator.php+`\+ =vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php `\ ;+>5vendor/symfony/finder/Iterator/PathFilterIterator.php`\c7vendor/symfony/finder/Iterator/CustomFilterIterator.phpX`\XDVSB1vendor/symfony/finder/Iterator/FilterIterator.php`\ =vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php `\ ƺ:vendor/symfony/finder/Iterator/DateRangeFilterIterator.phps`\s=x:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php``\`!ky6vendor/symfony/finder/Exception/ExceptionInterface.php`\L~9vendor/symfony/finder/Exception/AccessDeniedException.php`\svendor/symfony/finder/Glob.php`\t'%vendor/symfony/debug/ErrorHandler.phpA`\A\i)vendor/symfony/debug/DebugClassLoader.phpv*`\v*i)vendor/symfony/debug/ExceptionHandler.phpo`\o:(vendor/symfony/debug/BufferingLogger.php``\`Dm۶vendor/symfony/debug/Debug.php+`\+9bMvendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.phps`\skնIvendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php%`\%zEvendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php`\ĹBVKvendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpJ`\J3vendor/symfony/debug/Exception/FlattenException.phpn`\nw 6vendor/symfony/debug/Exception/FatalErrorException.php.`\.J06vendor/symfony/debug/Exception/FatalThrowableError.phpW`\W?K;vendor/symfony/debug/Exception/UndefinedMethodException.php`\nض7vendor/symfony/debug/Exception/OutOfMemoryException.php~`\~o8vendor/symfony/debug/Exception/ContextErrorException.php`\^@=vendor/symfony/debug/Exception/UndefinedFunctionException.php`\J7vendor/symfony/debug/Exception/SilencedErrorContext.php(`\(9vendor/symfony/debug/Exception/ClassNotFoundException.php`\i3vendor/symfony/options-resolver/OptionsResolver.php8`\8ѡH+vendor/symfony/options-resolver/Options.php{`\{|Evendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php~`\~]L@vendor/symfony/options-resolver/Exception/ExceptionInterface.phpn`\nyCvendor/symfony/options-resolver/Exception/NoSuchOptionException.php`\g]ȶGvendor/symfony/options-resolver/Exception/UndefinedOptionsException.php`\p ͐Evendor/symfony/options-resolver/Exception/InvalidOptionsException.php`\k =vendor/symfony/options-resolver/Exception/AccessException.php`\5Gvendor/symfony/options-resolver/Exception/OptionDefinitionException.php`\@4Fvendor/symfony/options-resolver/Exception/InvalidArgumentException.php`\GFvendor/symfony/options-resolver/Exception/NoConfigurationException.php`\jEEvendor/symfony/options-resolver/Exception/MissingOptionsException.php`\=(vendor/symfony/filesystem/Filesystem.php9`\9¶)vendor/symfony/filesystem/LockHandler.php#`\#<vendor/symfony/filesystem/Exception/IOExceptionInterface.php`\jwM:vendor/symfony/filesystem/Exception/ExceptionInterface.phpi`\i$ 3vendor/symfony/filesystem/Exception/IOException.php`\#Ѷ=vendor/symfony/filesystem/Exception/FileNotFoundException.php`\p\&vendor/symfony/stopwatch/Stopwatch.php`\#i{+vendor/symfony/stopwatch/StopwatchEvent.php `\ Zz,vendor/symfony/stopwatch/StopwatchPeriod.phpp`\pΧ!$vendor/symfony/stopwatch/Section.php`\#-vendor/symfony/polyfill-mbstring/Mbstring.phpC`\CZ?@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpfA`\fAf߶Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php`\y_@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php@`\@ض.vendor/symfony/polyfill-mbstring/bootstrap.php`\<ˢ'vendor/symfony/polyfill-php72/Php72.php`\,Rʶ+vendor/symfony/polyfill-php72/bootstrap.phpI`\I"<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.phpq`\qMvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php`\ۙ8<vendor/symfony/event-dispatcher/EventSubscriberInterface.php`\o;y3vendor/symfony/event-dispatcher/EventDispatcher.php`\"d0vendor/symfony/event-dispatcher/GenericEvent.phpk`\kt)vendor/symfony/event-dispatcher/Event.php)`\)@B<vendor/symfony/event-dispatcher/EventDispatcherInterface.php`\pX9vendor/symfony/event-dispatcher/Debug/WrappedListener.php `\ ?yͶKvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php5`\5fVBvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.phpe`\ekAvendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php}`\}mv.vendor/symfony/process/PhpExecutableFinder.php`\)Ķ+vendor/symfony/process/ExecutableFinder.php`\^&vendor/symfony/process/InputStream.php$`\$m%vendor/symfony/process/PhpProcess.php`\xp)vendor/symfony/process/ProcessBuilder.php `\ AeѶ"vendor/symfony/process/Process.phpk`\kS慶-vendor/symfony/process/Pipes/WindowsPipes.php `\ mN.vendor/symfony/process/Pipes/AbstractPipes.php `\ *vendor/symfony/process/Pipes/UnixPipes.php`\#̶/vendor/symfony/process/Pipes/PipesInterface.phpm`\mu'vendor/symfony/process/ProcessUtils.php5`\5nV83vendor/symfony/process/Exception/LogicException.php`\ =vendor/symfony/process/Exception/ProcessTimedOutException.php`\7vendor/symfony/process/Exception/ExceptionInterface.phpf`\f]>T=vendor/symfony/process/Exception/InvalidArgumentException.php`\+_;vendor/symfony/process/Exception/ProcessFailedException.phpx`\xzy5vendor/symfony/process/Exception/RuntimeException.php`\:'vendor/symfony/polyfill-php70/Php70.php`\=+7vendor/symfony/polyfill-php70/Resources/stubs/Error.php)`\)[k Avendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php/`\/|mͶ;vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php)`\)O_<vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php*`\*ᤶEvendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php3`\3h;Xvendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php`\~@vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php.`\.&8y+vendor/symfony/polyfill-php70/bootstrap.phpm`\mf'vendor/symfony/polyfill-ctype/Ctype.phpH `\H ݶ+vendor/symfony/polyfill-ctype/bootstrap.phpI`\ID!e/vendor/symfony/console/Logger/ConsoleLogger.php `\ rDvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpb `\b 蒜,vendor/symfony/console/Style/OutputStyle.phpW`\W?-vendor/symfony/console/Style/SymfonyStyle.php2`\2zc/vendor/symfony/console/Style/StyleInterface.php`\&nѶ(vendor/symfony/console/ConsoleEvents.php `\  Nն<vendor/symfony/console/Descriptor/ApplicationDescription.php2 `\2 M4vendor/symfony/console/Descriptor/TextDescriptor.php!`\!uD4vendor/symfony/console/Descriptor/JsonDescriptor.php`\ 9vendor/symfony/console/Descriptor/DescriptorInterface.php`\rQ8vendor/symfony/console/Descriptor/MarkdownDescriptor.php`\>43vendor/symfony/console/Descriptor/XmlDescriptor.php`\&0vendor/symfony/console/Descriptor/Descriptor.phps`\scBM,vendor/symfony/console/Question/Question.php `\ 8vendor/symfony/console/Question/ConfirmationQuestion.php`\d?2vendor/symfony/console/Question/ChoiceQuestion.phpi `\i =/vendor/symfony/console/Tester/CommandTester.php5`\5fy;3vendor/symfony/console/Tester/ApplicationTester.php& `\& /ɶ&vendor/symfony/console/Application.phpe`\eY)9vendor/symfony/console/Formatter/OutputFormatterStyle.php`\(>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php;`\;%4vendor/symfony/console/Formatter/OutputFormatter.php`\|?xBvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php`\G=vendor/symfony/console/Formatter/OutputFormatterInterface.php`\6vendor/symfony/console/Helper/DebugFormatterHelper.phpj`\jOG 2vendor/symfony/console/Helper/DescriptorHelper.phph`\hK\1vendor/symfony/console/Helper/HelperInterface.php`\2vendor/symfony/console/Helper/InputAwareHelper.phpc`\c+vendor/symfony/console/Helper/HelperSet.phpf`\fx/vendor/symfony/console/Helper/ProcessHelper.phpQ `\Q ;B0vendor/symfony/console/Helper/TableSeparator.php`\0vendor/symfony/console/Helper/QuestionHelper.php `\ \n'vendor/symfony/console/Helper/Table.php-`\-š1vendor/symfony/console/Helper/FormatterHelper.php`\)̾7vendor/symfony/console/Helper/SymfonyQuestionHelper.phpV `\V Fo-vendor/symfony/console/Helper/ProgressBar.phpM%`\M%YI(vendor/symfony/console/Helper/Helper.phpk`\k'颶3vendor/symfony/console/Helper/ProgressIndicator.phpE`\E,vendor/symfony/console/Helper/TableStyle.php `\ ?0+vendor/symfony/console/Helper/TableCell.phpw`\wV8vendor/symfony/console/Output/ConsoleOutputInterface.php`\ʶ/vendor/symfony/console/Output/ConsoleOutput.php`\,vendor/symfony/console/Output/NullOutput.php`\Z.vendor/symfony/console/Output/StreamOutput.php`\ĒO(vendor/symfony/console/Output/Output.php `\ 0p0vendor/symfony/console/Output/BufferedOutput.php_`\_>P1vendor/symfony/console/Output/OutputInterface.php`\n 2vendor/symfony/console/Event/ConsoleErrorEvent.phpb`\bMh4vendor/symfony/console/Event/ConsoleCommandEvent.php`\!ȶ6vendor/symfony/console/Event/ConsoleTerminateEvent.phpz`\z,L-vendor/symfony/console/Event/ConsoleEvent.php`\Ӷ6vendor/symfony/console/Event/ConsoleExceptionEvent.php`\*\ȶ#vendor/symfony/console/Terminal.php`\՝Ŷ4vendor/symfony/console/Input/InputAwareInterface.php`\O0vendor/symfony/console/Input/InputDefinition.php`\&vendor/symfony/console/Input/Input.phpE `\E .vendor/symfony/console/Input/InputArgument.php `\ ;+vendor/symfony/console/Input/ArrayInput.php `\ W.,vendor/symfony/console/Input/StringInput.phpV`\V.Ķ,vendor/symfony/console/Input/InputOption.php `\ *vendor/symfony/console/Input/ArgvInput.php`\ɤ9vendor/symfony/console/Input/StreamableInputInterface.php`\B/vendor/symfony/console/Input/InputInterface.php`\rrg=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php`\m?vendor/symfony/console/CommandLoader/ContainerCommandLoader.php?`\? @?vendor/symfony/console/CommandLoader/CommandLoaderInterface.php>`\>eUE.vendor/symfony/console/Command/ListCommand.php`\5-.vendor/symfony/console/Command/HelpCommand.php`\<*vendor/symfony/console/Command/Command.phpw `\w Bv0vendor/symfony/console/Command/LockableTrait.php2`\2N߶3vendor/symfony/console/Exception/LogicException.php`\O\e;vendor/symfony/console/Exception/InvalidOptionException.php`\H7vendor/symfony/console/Exception/ExceptionInterface.phpf`\fAB=vendor/symfony/console/Exception/CommandNotFoundException.php`\K*5=vendor/symfony/console/Exception/InvalidArgumentException.php`\̽Z5vendor/symfony/console/Exception/RuntimeException.php`\,66vendor/symfony/console/EventListener/ErrorListener.php`\HAvendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php `\ ǕPvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.phpO`\OkXhMvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php`\nWSRvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php`\Ivendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.phpQT`\QT~xIvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php`\cI՗Jvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php)`\){5Lvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php\`\\5hVvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php?`\?\bFvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php"`\"Р@ֶHvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php^`\^Uvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php`\~Ķ[vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php`\Svendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.phpm`\mZOvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php`\S]Qvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php0`\0 uTvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php`\fʓ۶Ovendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.phpu`\u# Svendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php`\;Rx{Kvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.phpp`\pg^Hvendor/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php`\kbUvendor/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php`\#1+vendor/php-cs-fixer/diff/src/v3_0/Chunk.php`\&_F*vendor/php-cs-fixer/diff/src/v3_0/Diff.php`\aF,vendor/php-cs-fixer/diff/src/v3_0/Parser.php`\j?,vendor/php-cs-fixer/diff/src/v3_0/Differ.php`\ F*Gvendor/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php`\=hEvendor/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php|`\|oKvendor/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php`\}Gvendor/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php`\R8OBvendor/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php`\7Wvendor/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php `\ _Fvendor/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php`\ksHvendor/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php`\9vendor/php-cs-fixer/diff/src/v3_0/Exception/Exception.phpH`\H{n*vendor/php-cs-fixer/diff/src/v3_0/Line.php`\8Wvendor/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php`\z8Yvendor/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php`\f_vendor/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php`\:]vendor/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php`\ 8Bvendor/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php`\)+vendor/php-cs-fixer/diff/src/v1_4/Chunk.php `\ ۶*vendor/php-cs-fixer/diff/src/v1_4/Diff.php`\0}n,vendor/php-cs-fixer/diff/src/v1_4/Parser.php`\6qc,vendor/php-cs-fixer/diff/src/v1_4/Differ.phpa`\aB*vendor/php-cs-fixer/diff/src/v1_4/Line.php`\!/Hvendor/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php`\5Uvendor/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php`\K+vendor/php-cs-fixer/diff/src/v2_0/Chunk.php`\k˶*vendor/php-cs-fixer/diff/src/v2_0/Diff.php`\,vendor/php-cs-fixer/diff/src/v2_0/Parser.php`\lC,vendor/php-cs-fixer/diff/src/v2_0/Differ.php+`\+N1 ζGvendor/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php`\1%|Evendor/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php `\ .۶Gvendor/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php`\uBvendor/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php`\E{IWvendor/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php `\ h Hvendor/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php`\;$9vendor/php-cs-fixer/diff/src/v2_0/Exception/Exception.phpH`\Hs-*vendor/php-cs-fixer/diff/src/v2_0/Line.php`\AQ1vendor/paragonie/random_compat/lib/random_int.php`\=vendor/paragonie/random_compat/lib/random_bytes_libsodium.php`\$Dvendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php`\}>vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php`\vA?vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php`\1-vendor/paragonie/random_compat/lib/random.php `\ ^·5vendor/paragonie/random_compat/lib/error_polyfill.php`\a4:vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php(`\(/2vendor/paragonie/random_compat/lib/cast_to_int.php`\6d8vendor/paragonie/random_compat/lib/byte_safe_strings.phpp`\p36dvendor/autoload.php`\]0LICENSES`\SivW#!/bin/sh set -eu IFS=' ' CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "${COMMIT_RANGE}") if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php_cs(\\.dist)?|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi vendor/bin/php-cs-fixer fix --config=.php_cs.dist -v --dry-run --stop-on-violation --using-cache=no ${EXTRA_ARGS} #!/usr/bin/env php PhpCsFixer\Console\Application::VERSION, 'vnumber' => 'v'.PhpCsFixer\Console\Application::VERSION, 'codename' => PhpCsFixer\Console\Application::VERSION_CODENAME, ]; echo json_encode([ 'version' => $version, ], JSON_PRETTY_PRINT); isGivenKind(T_OPEN_TAG)) { $openingToken = $token; $openingTokenIndex = $index - $i; $newlineInOpening = false !== strpos($token->getContent(), "\n"); if ($newlineInOpening) { ++$precedingNewlines; } break; } if (false === $token->isGivenKind(T_WHITESPACE)) { break; } $precedingNewlines += substr_count($token->getContent(), "\n"); } } if ($precedingNewlines >= $expectedMin && $precedingNewlines <= $expectedMax) { return; } $previousIndex = $index - 1; $previous = $tokens[$previousIndex]; if (0 === $expectedMax) { if ($previous->isWhitespace()) { $tokens->clearAt($previousIndex); } if ($newlineInOpening) { $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, rtrim($openingToken->getContent()).' ']); } return; } $lineEnding = $this->whitespacesConfig->getLineEnding(); $newlinesForWhitespaceToken = $expectedMax; if (null !== $openingToken) { $content = rtrim($openingToken->getContent()); $newContent = $content.$lineEnding; $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, $newContent]); --$newlinesForWhitespaceToken; } if (0 === $newlinesForWhitespaceToken) { if ($previous->isWhitespace()) { $tokens->clearAt($previousIndex); } return; } if ($previous->isWhitespace()) { $tokens[$previousIndex] = new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken).substr($previous->getContent(), strrpos($previous->getContent(), "\n") + 1)]); } else { $tokens->insertAt($index, new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)])); } } } configure([]); } catch (RequiredFixerConfigurationException $e) { } } if ($this instanceof WhitespacesAwareFixerInterface) { $this->whitespacesConfig = $this->getDefaultWhitespacesFixerConfig(); } } final public function fix(\SplFileInfo $file, Tokens $tokens) { if ($this instanceof ConfigurableFixerInterface && null === $this->configuration) { throw new RequiredFixerConfigurationException($this->getName(), 'Configuration is required.'); } if (0 < $tokens->count() && $this->isCandidate($tokens) && $this->supports($file)) { $this->applyFix($file, $tokens); } } public function isRisky() { return false; } public function getName() { $nameParts = explode('\\', static::class); $name = substr(end($nameParts), 0, -\strlen('Fixer')); return Utils::camelCaseToUnderscore($name); } public function getPriority() { return 0; } public function supports(\SplFileInfo $file) { return true; } public function configure(array $configuration = null) { if (!$this instanceof ConfigurationDefinitionFixerInterface) { throw new \LogicException('Cannot configure using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); } if (null === $configuration) { $message = 'Passing NULL to set default configuration is deprecated and will not be supported in 3.0, use an empty array instead.'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); $configuration = []; } foreach ($this->getConfigurationDefinition()->getOptions() as $option) { if (!$option instanceof DeprecatedFixerOption) { continue; } $name = $option->getName(); if (\array_key_exists($name, $configuration)) { $message = sprintf( 'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s', $name, $this->getName(), (int) Application::VERSION + 1, str_replace('`', '"', $option->getDeprecationMessage()) ); if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); } } try { $this->configuration = $this->getConfigurationDefinition()->resolve($configuration); } catch (MissingOptionsException $exception) { throw new RequiredFixerConfigurationException( $this->getName(), sprintf('Missing required configuration: %s', $exception->getMessage()), $exception ); } catch (InvalidOptionsForEnvException $exception) { throw new InvalidForEnvFixerConfigurationException( $this->getName(), sprintf('Invalid configuration for env: %s', $exception->getMessage()), $exception ); } catch (ExceptionInterface $exception) { throw new InvalidFixerConfigurationException( $this->getName(), sprintf('Invalid configuration: %s', $exception->getMessage()), $exception ); } } public function getConfigurationDefinition() { if (!$this instanceof ConfigurationDefinitionFixerInterface) { throw new \LogicException('Cannot get configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); } if (null === $this->configurationDefinition) { $this->configurationDefinition = $this->createConfigurationDefinition(); } return $this->configurationDefinition; } public function setWhitespacesConfig(WhitespacesFixerConfig $config) { if (!$this instanceof WhitespacesAwareFixerInterface) { throw new \LogicException('Cannot run method for class not implementing "PhpCsFixer\Fixer\WhitespacesAwareFixerInterface".'); } $this->whitespacesConfig = $config; } abstract protected function applyFix(\SplFileInfo $file, Tokens $tokens); protected function createConfigurationDefinition() { if (!$this instanceof ConfigurationDefinitionFixerInterface) { throw new \LogicException('Cannot create configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); } throw new \LogicException('Not implemented.'); } private function getDefaultWhitespacesFixerConfig() { static $defaultWhitespacesFixerConfig = null; if (null === $defaultWhitespacesFixerConfig) { $defaultWhitespacesFixerConfig = new WhitespacesFixerConfig(' ', "\n"); } return $defaultWhitespacesFixerConfig; } } find(false); if (false === $executable) { throw new UnavailableLinterException('Cannot find PHP executable.'); } if ('phpdbg' === \PHP_SAPI) { if (false === strpos($executable, 'phpdbg')) { throw new UnavailableLinterException('Automatically found PHP executable is non-standard phpdbg. Could not find proper PHP executable.'); } $executable = str_replace('phpdbg', 'php', $executable); if (!is_executable($executable)) { throw new UnavailableLinterException('Automatically found PHP executable is phpdbg. Could not find proper PHP executable.'); } } } $this->processBuilder = new ProcessLinterProcessBuilder($executable); $this->fileRemoval = new FileRemoval(); } public function __destruct() { if (null !== $this->temporaryFile) { $this->fileRemoval->delete($this->temporaryFile); } } public function isAsync() { return true; } public function lintFile($path) { return new ProcessLintingResult($this->createProcessForFile($path)); } public function lintSource($source) { return new ProcessLintingResult($this->createProcessForSource($source)); } private function createProcessForFile($path) { if (!is_file($path)) { return $this->createProcessForSource(FileReader::createSingleton()->read($path)); } $process = $this->processBuilder->build($path); $process->setTimeout(10); $process->start(); return $process; } private function createProcessForSource($source) { if (null === $this->temporaryFile) { $this->temporaryFile = tempnam('.', 'cs_fixer_tmp_'); $this->fileRemoval->observe($this->temporaryFile); } if (false === @file_put_contents($this->temporaryFile, $source)) { throw new IOException(sprintf('Failed to write file "%s".', $this->temporaryFile), 0, null, $this->temporaryFile); } return $this->createProcessForFile($this->temporaryFile); } } lintSource(FileReader::createSingleton()->read($path)); } public function lintSource($source) { try { $codeHash = CodeHasher::calculateCodeHash($source); Tokens::clearCache($codeHash); Tokens::fromCode($source); return new TokenizerLintingResult(); } catch (\ParseError $e) { return new TokenizerLintingResult($e); } } } sublinter = $linter; } public function isAsync() { return $this->sublinter->isAsync(); } public function lintFile($path) { $checksum = crc32(file_get_contents($path)); if (!isset($this->cache[$checksum])) { $this->cache[$checksum] = $this->sublinter->lintFile($path); } return $this->cache[$checksum]; } public function lintSource($source) { $checksum = crc32($source); if (!isset($this->cache[$checksum])) { $this->cache[$checksum] = $this->sublinter->lintSource($source); } return $this->cache[$checksum]; } } executable = $executable; } public function build($path) { return new Process([ $this->executable, '-l', $path, ]); } } sublinter = new TokenizerLinter(); } catch (UnavailableLinterException $e) { $this->sublinter = new ProcessLinter($executable); } } public function isAsync() { return $this->sublinter->isAsync(); } public function lintFile($path) { return $this->sublinter->lintFile($path); } public function lintSource($source) { return $this->sublinter->lintSource($source); } } error = $error; } public function check() { if (null !== $this->error) { throw new LintingException( sprintf('PHP Parse error: %s on line %d.', $this->error->getMessage(), $this->error->getLine()), $this->error->getCode(), $this->error ); } } } process = $process; } public function check() { if (!$this->isSuccessful()) { throw new LintingException($this->process->getErrorOutput() ?: $this->process->getOutput(), $this->process->getExitCode()); } } private function isSuccessful() { if (null === $this->isSuccessful) { $this->process->wait(); $this->isSuccessful = $this->process->isSuccessful(); } return $this->isSuccessful; } } clean(); } public function observe($path) { $this->files[$path] = true; } public function delete($path) { if (isset($this->files[$path])) { unset($this->files[$path]); } $this->unlink($path); } public function clean() { foreach ($this->files as $file => $value) { $this->unlink($file); } $this->files = []; } private function unlink($path) { @unlink($path); } } unbind($allowedValue); } } } $this->name = $name; $this->description = $description; $this->isRequired = $isRequired; $this->default = $default; $this->allowedTypes = $allowedTypes; $this->allowedValues = $allowedValues; if (null !== $normalizer) { $this->normalizer = $this->unbind($normalizer); } } public function getName() { return $this->name; } public function getDescription() { return $this->description; } public function hasDefault() { return !$this->isRequired; } public function getDefault() { if (!$this->hasDefault()) { throw new \LogicException('No default value defined.'); } return $this->default; } public function getAllowedTypes() { return $this->allowedTypes; } public function getAllowedValues() { return $this->allowedValues; } public function getNormalizer() { return $this->normalizer; } private function unbind(\Closure $closure) { return $closure->bindTo(null); } } allowedValues = $allowedValues; } public function __invoke($values) { if (!\is_array($values)) { return false; } foreach ($values as $value) { if (!\in_array($value, $this->allowedValues, true)) { return false; } } return true; } public function getAllowedValues() { return $this->allowedValues; } } option = $option; $this->deprecationMessage = $deprecationMessage; } public function getName() { return $this->option->getName(); } public function getDescription() { return $this->option->getDescription(); } public function hasDefault() { return $this->option->hasDefault(); } public function getDefault() { return $this->option->getDefault(); } public function getAllowedTypes() { return $this->option->getAllowedTypes(); } public function getAllowedValues() { return $this->option->getAllowedValues(); } public function getNormalizer() { return $this->option->getNormalizer(); } public function getDeprecationMessage() { return $this->deprecationMessage; } } optionBuilder = $optionBuilder; $this->alias = $alias; } public function setDefault($default) { $this->optionBuilder->setDefault($default); return $this; } public function setAllowedTypes(array $allowedTypes) { $this->optionBuilder->setAllowedTypes($allowedTypes); return $this; } public function setAllowedValues(array $allowedValues) { $this->optionBuilder->setAllowedValues($allowedValues); return $this; } public function setNormalizer(\Closure $normalizer) { $this->optionBuilder->setNormalizer($normalizer); return $this; } public function getOption() { return new AliasedFixerOption( $this->optionBuilder->getOption(), $this->alias ); } } name = $name; $this->description = $description; } public function setDefault($default) { $this->default = $default; $this->isRequired = false; return $this; } public function setAllowedTypes(array $allowedTypes) { $this->allowedTypes = $allowedTypes; return $this; } public function setAllowedValues(array $allowedValues) { $this->allowedValues = $allowedValues; return $this; } public function setNormalizer(\Closure $normalizer) { $this->normalizer = $normalizer; return $this; } public function setDeprecationMessage($deprecationMessage) { $this->deprecationMessage = $deprecationMessage; return $this; } public function getOption() { $option = new FixerOption( $this->name, $this->description, $this->isRequired, $this->default, $this->allowedTypes, $this->allowedValues, $this->normalizer ); if (null !== $this->deprecationMessage) { $option = new DeprecatedFixerOption($option, $this->deprecationMessage); } return $option; } } addOption($option); } if (empty($this->registeredNames)) { throw new \LogicException('Options cannot be empty.'); } } public function getOptions() { return $this->options; } public function resolve(array $options) { $resolver = new OptionsResolver(); foreach ($this->options as $option) { $name = $option->getName(); if ($option instanceof AliasedFixerOption) { $alias = $option->getAlias(); if (\array_key_exists($alias, $options)) { if (\array_key_exists($name, $options)) { throw new InvalidOptionsException(sprintf('Aliased option %s/%s is passed multiple times.', $name, $alias)); } $options[$name] = $options[$alias]; unset($options[$alias]); } } if ($option->hasDefault()) { $resolver->setDefault($name, $option->getDefault()); } else { $resolver->setRequired($name); } $allowedValues = $option->getAllowedValues(); if (null !== $allowedValues) { foreach ($allowedValues as &$allowedValue) { if (\is_object($allowedValue) && \is_callable($allowedValue)) { $allowedValue = function ($values) use ($allowedValue) { return $allowedValue($values); }; } } $resolver->setAllowedValues($name, $allowedValues); } $allowedTypes = $option->getAllowedTypes(); if (null !== $allowedTypes) { $resolver->setAllowedTypes($name, $allowedTypes); } $normalizer = $option->getNormalizer(); if (null !== $normalizer) { $resolver->setNormalizer($name, $normalizer); } } return $resolver->resolve($options); } private function addOption(FixerOptionInterface $option) { $name = $option->getName(); if (\in_array($name, $this->registeredNames, true)) { throw new \LogicException(sprintf('The "%s" option is defined multiple times.', $name)); } $this->options[] = $option; $this->registeredNames[] = $name; return $this; } } resolver = new FixerConfigurationResolver($options); $this->fixerName = $fixerName; $names = array_map( static function (FixerOptionInterface $option) { return $option->getName(); }, $this->resolver->getOptions() ); if (!\in_array($root, $names, true)) { throw new \LogicException(sprintf('The "%s" option is not defined.', $root)); } $this->root = $root; } public function getOptions() { return $this->resolver->getOptions(); } public function resolve(array $options) { if (!empty($options) && !\array_key_exists($this->root, $options)) { $names = array_map( function (FixerOptionInterface $option) { return $option->getName(); }, $this->resolver->getOptions() ); $passedNames = array_keys($options); if (!empty(array_diff($passedNames, $names))) { $message = "Passing \"{$this->root}\" at the root of the configuration for rule \"{$this->fixerName}\" is deprecated and will not be supported in 3.0, use \"{$this->root}\" => array(...) option instead."; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \RuntimeException("{$message}. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); $options = [$this->root => $options]; } } return $this->resolver->resolve($options); } } fixerOption = $fixerOption; $this->alias = $alias; } public function getAlias() { return $this->alias; } public function getName() { return $this->fixerOption->getName(); } public function getDescription() { return $this->fixerOption->getDescription(); } public function hasDefault() { return $this->fixerOption->hasDefault(); } public function getDefault() { return $this->fixerOption->getDefault(); } public function getAllowedTypes() { return $this->fixerOption->getAllowedTypes(); } public function getAllowedValues() { return $this->fixerOption->getAllowedValues(); } public function getNormalizer() { return $this->fixerOption->getNormalizer(); } } isInstalledByComposer()) { throw new \LogicException('Cannot get composer version for tool not installed by composer.'); } if (null === $this->composerInstallationDetails) { $composerInstalled = json_decode(file_get_contents($this->getComposerInstalledFile()), true); foreach ($composerInstalled as $package) { if (\in_array($package['name'], [self::COMPOSER_PACKAGE_NAME, self::COMPOSER_LEGACY_PACKAGE_NAME], true)) { $this->composerInstallationDetails = $package; break; } } } return $this->composerInstallationDetails; } public function getComposerVersion() { $package = $this->getComposerInstallationDetails(); $versionSuffix = ''; if (isset($package['dist'])) { $versionSuffix = '#'.$package['dist']['reference']; } return $package['version'].$versionSuffix; } public function getVersion() { if ($this->isInstalledByComposer()) { return Application::VERSION.':'.$this->getComposerVersion(); } return Application::VERSION; } public function isInstalledAsPhar() { return 'phar://' === substr(__DIR__, 0, 7); } public function isInstalledByComposer() { if (null === $this->isInstalledByComposer) { $this->isInstalledByComposer = !$this->isInstalledAsPhar() && file_exists($this->getComposerInstalledFile()); } return $this->isInstalledByComposer; } public function getPharDownloadUri($version) { return sprintf( 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/%s/php-cs-fixer.phar', $version ); } private function getComposerInstalledFile() { return __DIR__.'/../../../composer/installed.json'; } } eventDispatcher = $eventDispatcher; $this->cacheManager = $cacheManager; } public function accept() { $file = $this->current(); if (!$file instanceof \SplFileInfo) { throw new \RuntimeException( sprintf( 'Expected instance of "\SplFileInfo", got "%s".', \is_object($file) ? \get_class($file) : \gettype($file) ) ); } $path = $file->isLink() ? $file->getPathname() : $file->getRealPath(); if (isset($this->visitedElements[$path])) { return false; } $this->visitedElements[$path] = true; if (!$file->isFile() || $file->isLink()) { return false; } $content = FileReader::createSingleton()->read($path); if ( '' === $content || !$this->cacheManager->needFixing($file->getPathname(), $content) ) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_SKIPPED) ); return false; } return true; } private function dispatchEvent($name, Event $event) { if (null === $this->eventDispatcher) { return; } $this->eventDispatcher->dispatch($name, $event); } } linter = $linter; } public function currentLintingResult() { return $this->currentResult; } public function next() { parent::next(); $this->currentResult = $this->nextResult; if ($this->hasNext()) { $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); } } public function rewind() { parent::rewind(); if ($this->valid()) { $this->currentResult = $this->handleItem($this->current()); } if ($this->hasNext()) { $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); } } private function handleItem(\SplFileInfo $file) { return $this->linter->lintFile($file->getRealPath()); } } finder = $finder; $this->fixers = $fixers; $this->differ = $differ; $this->eventDispatcher = $eventDispatcher; $this->errorsManager = $errorsManager; $this->linter = $linter; $this->isDryRun = $isDryRun; $this->cacheManager = $cacheManager; $this->directory = $directory ?: new Directory(''); $this->stopOnViolation = $stopOnViolation; } public function fix() { $changed = []; $finder = $this->finder; $finderIterator = $finder instanceof \IteratorAggregate ? $finder->getIterator() : $finder; $fileFilteredFileIterator = new FileFilterIterator( $finderIterator, $this->eventDispatcher, $this->cacheManager ); $collection = $this->linter->isAsync() ? new FileCachingLintingIterator($fileFilteredFileIterator, $this->linter) : new FileLintingIterator($fileFilteredFileIterator, $this->linter); foreach ($collection as $file) { $fixInfo = $this->fixFile($file, $collection->currentLintingResult()); Tokens::clearCache(); if ($fixInfo) { $name = $this->directory->getRelativePathTo($file); $changed[$name] = $fixInfo; if ($this->stopOnViolation) { break; } } } return $changed; } private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult) { $name = $file->getPathname(); try { $lintingResult->check(); } catch (LintingException $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_INVALID) ); $this->errorsManager->report(new Error(Error::TYPE_INVALID, $name, $e)); return; } $old = FileReader::createSingleton()->read($file->getRealPath()); Tokens::setLegacyMode(false); $tokens = Tokens::fromCode($old); $oldHash = $tokens->getCodeHash(); $newHash = $oldHash; $new = $old; $appliedFixers = []; try { foreach ($this->fixers as $fixer) { if ( !$fixer instanceof AbstractFixer && (!$fixer->supports($file) || !$fixer->isCandidate($tokens)) ) { continue; } $fixer->fix($file, $tokens); if ($tokens->isChanged()) { $tokens->clearEmptyTokens(); $tokens->clearChanged(); $appliedFixers[] = $fixer->getName(); } } } catch (\Exception $e) { $this->processException($name, $e); return; } catch (\ParseError $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) ); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e)); return; } catch (\Throwable $e) { $this->processException($name, $e); return; } $fixInfo = null; if (!empty($appliedFixers)) { $new = $tokens->generateCode(); $newHash = $tokens->getCodeHash(); } if ($oldHash !== $newHash) { $fixInfo = [ 'appliedFixers' => $appliedFixers, 'diff' => $this->differ->diff($old, $new), ]; try { $this->linter->lintSource($new)->check(); } catch (LintingException $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) ); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e, $fixInfo['appliedFixers'], $fixInfo['diff'])); return; } if (!$this->isDryRun) { if (false === @file_put_contents($file->getRealPath(), $new)) { $error = error_get_last(); throw new IOException( sprintf('Failed to write file "%s", "%s".', $file->getPathname(), $error ? $error['message'] : 'no reason available'), 0, null, $file->getRealPath() ); } } } $this->cacheManager->setFile($name, $new); $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES) ); return $fixInfo; } private function processException($name, $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_EXCEPTION) ); $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $name, $e)); } private function dispatchEvent($name, Event $event) { if (null === $this->eventDispatcher) { return; } $this->eventDispatcher->dispatch($name, $event); } } linter = $linter; } public function currentLintingResult() { return $this->currentResult; } public function next() { parent::next(); if ($this->valid()) { $this->currentResult = $this->handleItem($this->current()); } } public function rewind() { parent::rewind(); if ($this->valid()) { $this->currentResult = $this->handleItem($this->current()); } } private function handleItem(\SplFileInfo $file) { return $this->linter->lintFile($file->getRealPath()); } } status = $status; } public function getStatus() { return $this->status; } } count() : $end; $candidateSequence = [[T_STRING, $functionNameToSearch], '(']; $matches = $tokens->findSequence($candidateSequence, $start, $end, false); if (null === $matches) { return null; } list($functionName, $openParenthesis) = array_keys($matches); $functionsAnalyzer = new FunctionsAnalyzer(); if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $functionName)) { return $this->find($functionNameToSearch, $tokens, $openParenthesis, $end); } return [$functionName, $openParenthesis, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis)]; } } getMessage(); } return null; } } [ 'header' => 'User-Agent: FriendsOfPHP/PHP-CS-Fixer', ], ]) ); if (false === $result) { throw new \RuntimeException(sprintf('Failed to load tags at "%s".', $url)); } $result = json_decode($result, true); if (JSON_ERROR_NONE !== json_last_error()) { throw new \RuntimeException(sprintf( 'Failed to read response from "%s" as JSON: %s.', $url, json_last_error_msg() )); } return $result; } } githubClient = $githubClient; $this->versionParser = new VersionParser(); } public function getLatestVersion() { $this->retrieveAvailableVersions(); return $this->availableVersions[0]; } public function getLatestVersionOfMajor($majorVersion) { $this->retrieveAvailableVersions(); $semverConstraint = '^'.$majorVersion; foreach ($this->availableVersions as $availableVersion) { if (Semver::satisfies($availableVersion, $semverConstraint)) { return $availableVersion; } } return null; } public function compareVersions($versionA, $versionB) { $versionA = $this->versionParser->normalize($versionA); $versionB = $this->versionParser->normalize($versionB); if (Comparator::lessThan($versionA, $versionB)) { return -1; } if (Comparator::greaterThan($versionA, $versionB)) { return 1; } return 0; } private function retrieveAvailableVersions() { if (null !== $this->availableVersions) { return; } foreach ($this->githubClient->getTags() as $tag) { $version = $tag['name']; try { $this->versionParser->normalize($version); if ('stable' === VersionParser::parseStability($version)) { $this->availableVersions[] = $version; } } catch (\UnexpectedValueException $exception) { } } $this->availableVersions = Semver::rsort($this->availableVersions); } } null, 'cache-file' => null, 'config' => null, 'diff' => null, 'diff-format' => null, 'dry-run' => null, 'format' => null, 'path' => [], 'path-mode' => self::PATH_MODE_OVERRIDE, 'rules' => null, 'show-progress' => null, 'stop-on-violation' => null, 'using-cache' => null, 'verbosity' => null, ]; private $cacheFile; private $cacheManager; private $differ; private $directory; private $finder; private $format; private $linter; private $path; private $progress; private $ruleSet; private $usingCache; private $fixerFactory; public function __construct( ConfigInterface $config, array $options, $cwd, ToolInfoInterface $toolInfo ) { $this->cwd = $cwd; $this->defaultConfig = $config; $this->toolInfo = $toolInfo; foreach ($options as $name => $value) { $this->setOption($name, $value); } } public function getCacheFile() { if (!$this->getUsingCache()) { return null; } if (null === $this->cacheFile) { if (null === $this->options['cache-file']) { $this->cacheFile = $this->getConfig()->getCacheFile(); } else { $this->cacheFile = $this->options['cache-file']; } } return $this->cacheFile; } public function getCacheManager() { if (null === $this->cacheManager) { if ($this->getUsingCache() && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer())) { $this->cacheManager = new FileCacheManager( new FileHandler($this->getCacheFile()), new Signature( PHP_VERSION, $this->toolInfo->getVersion(), $this->getRules() ), $this->isDryRun(), $this->getDirectory() ); } else { $this->cacheManager = new NullCacheManager(); } } return $this->cacheManager; } public function getConfig() { if (null === $this->config) { foreach ($this->computeConfigFiles() as $configFile) { if (!file_exists($configFile)) { continue; } $config = self::separatedContextLessInclude($configFile); if (!$config instanceof ConfigInterface) { throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $configFile, \is_object($config) ? \get_class($config) : \gettype($config))); } $this->config = $config; $this->configFile = $configFile; break; } if (null === $this->config) { $this->config = $this->defaultConfig; } } return $this->config; } public function getConfigFile() { if (null === $this->configFile) { $this->getConfig(); } return $this->configFile; } public function getDiffer() { if (null === $this->differ) { $mapper = [ 'null' => static function () { return new NullDiffer(); }, 'sbd' => static function () { return new SebastianBergmannDiffer(); }, 'udiff' => static function () { return new UnifiedDiffer(); }, ]; if ($this->options['diff-format']) { $option = $this->options['diff-format']; if (!isset($mapper[$option])) { throw new InvalidConfigurationException(sprintf( '"diff-format" must be any of "%s", got "%s".', implode('", "', array_keys($mapper)), $option )); } } else { $default = 'sbd'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { $default = 'udiff'; } $option = $this->options['diff'] ? $default : 'null'; } $this->differ = $mapper[$option](); } return $this->differ; } public function getDirectory() { if (null === $this->directory) { $path = $this->getCacheFile(); $filesystem = new Filesystem(); $absolutePath = $filesystem->isAbsolutePath($path) ? $path : $this->cwd.\DIRECTORY_SEPARATOR.$path; $this->directory = new Directory(\dirname($absolutePath)); } return $this->directory; } public function getFixers() { if (null === $this->fixers) { $this->fixers = $this->createFixerFactory() ->useRuleSet($this->getRuleSet()) ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding())) ->getFixers() ; if (false === $this->getRiskyAllowed()) { $riskyFixers = array_map( static function (FixerInterface $fixer) { return $fixer->getName(); }, array_filter( $this->fixers, static function (FixerInterface $fixer) { return $fixer->isRisky(); } ) ); if (\count($riskyFixers)) { throw new InvalidConfigurationException(sprintf('The rules contain risky fixers (%s), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode(', ', $riskyFixers))); } } } return $this->fixers; } public function getLinter() { if (null === $this->linter) { $this->linter = new Linter($this->getConfig()->getPhpExecutable()); } return $this->linter; } public function getPath() { if (null === $this->path) { $filesystem = new Filesystem(); $cwd = $this->cwd; if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) { $this->path = $this->options['path']; } else { $this->path = array_map( static function ($path) use ($cwd, $filesystem) { $absolutePath = $filesystem->isAbsolutePath($path) ? $path : $cwd.\DIRECTORY_SEPARATOR.$path; if (!file_exists($absolutePath)) { throw new InvalidConfigurationException(sprintf( 'The path "%s" is not readable.', $path )); } return $absolutePath; }, $this->options['path'] ); } } return $this->path; } public function getProgress() { if (null === $this->progress) { if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) { $progressType = $this->options['show-progress']; $progressTypes = ['none', 'run-in', 'estimating', 'estimating-max', 'dots']; if (null === $progressType) { $default = 'run-in'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { $default = 'dots'; } $progressType = $this->getConfig()->getHideProgress() ? 'none' : $default; } elseif (!\in_array($progressType, $progressTypes, true)) { throw new InvalidConfigurationException(sprintf( 'The progress type "%s" is not defined, supported are "%s".', $progressType, implode('", "', $progressTypes) )); } elseif (\in_array($progressType, ['estimating', 'estimating-max', 'run-in'], true)) { $message = 'Passing `estimating`, `estimating-max` or `run-in` is deprecated and will not be supported in 3.0, use `none` or `dots` instead.'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); } $this->progress = $progressType; } else { $this->progress = 'none'; } } return $this->progress; } public function getReporter() { if (null === $this->reporter) { $reporterFactory = ReporterFactory::create(); $reporterFactory->registerBuiltInReporters(); $format = $this->getFormat(); try { $this->reporter = $reporterFactory->getReporter($format); } catch (\UnexpectedValueException $e) { $formats = $reporterFactory->getFormats(); sort($formats); throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats))); } } return $this->reporter; } public function getRiskyAllowed() { if (null === $this->allowRisky) { if (null === $this->options['allow-risky']) { $this->allowRisky = $this->getConfig()->getRiskyAllowed(); } else { $this->allowRisky = $this->resolveOptionBooleanValue('allow-risky'); } } return $this->allowRisky; } public function getRules() { return $this->getRuleSet()->getRules(); } public function getUsingCache() { if (null === $this->usingCache) { if (null === $this->options['using-cache']) { $this->usingCache = $this->getConfig()->getUsingCache(); } else { $this->usingCache = $this->resolveOptionBooleanValue('using-cache'); } } return $this->usingCache; } public function getFinder() { if (null === $this->finder) { $this->finder = $this->resolveFinder(); } return $this->finder; } public function isDryRun() { if (null === $this->isDryRun) { if ($this->isStdIn()) { $this->isDryRun = true; } else { $this->isDryRun = $this->options['dry-run']; } } return $this->isDryRun; } public function shouldStopOnViolation() { return $this->options['stop-on-violation']; } public function configFinderIsOverridden() { if (null === $this->configFinderIsOverridden) { $this->resolveFinder(); } return $this->configFinderIsOverridden; } private function computeConfigFiles() { $configFile = $this->options['config']; if (null !== $configFile) { if (false === file_exists($configFile) || false === is_readable($configFile)) { throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile)); } return [$configFile]; } $path = $this->getPath(); if ($this->isStdIn() || 0 === \count($path)) { $configDir = $this->cwd; } elseif (1 < \count($path)) { throw new InvalidConfigurationException('For multiple paths config parameter is required.'); } elseif (is_file($path[0]) && $dirName = pathinfo($path[0], PATHINFO_DIRNAME)) { $configDir = $dirName; } else { $configDir = $path[0]; } $candidates = [ $configDir.\DIRECTORY_SEPARATOR.'.php_cs', $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', ]; if ($configDir !== $this->cwd) { $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; } return $candidates; } private function createFixerFactory() { if (null === $this->fixerFactory) { $fixerFactory = new FixerFactory(); $fixerFactory->registerBuiltInFixers(); $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers()); $this->fixerFactory = $fixerFactory; } return $this->fixerFactory; } private function getFormat() { if (null === $this->format) { $this->format = null === $this->options['format'] ? $this->getConfig()->getFormat() : $this->options['format']; } return $this->format; } private function getRuleSet() { if (null === $this->ruleSet) { $rules = $this->parseRules(); $this->validateRules($rules); $this->ruleSet = new RuleSet($rules); } return $this->ruleSet; } private function isStdIn() { if (null === $this->isStdIn) { $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0]; } return $this->isStdIn; } private function iterableToTraversable($iterable) { return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; } private function parseRules() { if (null === $this->options['rules']) { return $this->getConfig()->getRules(); } $rules = trim($this->options['rules']); if ('' === $rules) { throw new InvalidConfigurationException('Empty rules value is not allowed.'); } if ('{' === $rules[0]) { $rules = json_decode($rules, true); if (JSON_ERROR_NONE !== json_last_error()) { throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: %s.', json_last_error_msg())); } return $rules; } $rules = []; foreach (explode(',', $this->options['rules']) as $rule) { $rule = trim($rule); if ('' === $rule) { throw new InvalidConfigurationException('Empty rule name is not allowed.'); } if ('-' === $rule[0]) { $rules[substr($rule, 1)] = false; } else { $rules[$rule] = true; } } return $rules; } private function validateRules(array $rules) { $ruleSet = []; foreach ($rules as $key => $value) { if (\is_int($key)) { throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value)); } $ruleSet[$key] = true; } $ruleSet = new RuleSet($ruleSet); $configuredFixers = array_keys($ruleSet->getRules()); $fixers = $this->createFixerFactory()->getFixers(); $availableFixers = array_map(static function (FixerInterface $fixer) { return $fixer->getName(); }, $fixers); $unknownFixers = array_diff( $configuredFixers, $availableFixers ); if (\count($unknownFixers)) { $matcher = new WordMatcher($availableFixers); $message = 'The rules contain unknown fixers: '; foreach ($unknownFixers as $unknownFixer) { $alternative = $matcher->match($unknownFixer); $message .= sprintf( '"%s"%s, ', $unknownFixer, null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)' ); } throw new InvalidConfigurationException(substr($message, 0, -2).'.'); } foreach ($fixers as $fixer) { $fixerName = $fixer->getName(); if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $messageEnd = [] === $successors ? sprintf(' and will be removed in version %d.0.', (int) Application::VERSION + 1) : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors))); $message = "Rule \"{$fixerName}\" is deprecated{$messageEnd}"; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \RuntimeException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); } } } private function resolveFinder() { $this->configFinderIsOverridden = false; if ($this->isStdIn()) { return new \ArrayIterator([new StdinFileInfo()]); } $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION]; if (!\in_array( $this->options['path-mode'], $modes, true )) { throw new InvalidConfigurationException(sprintf( 'The path-mode "%s" is not defined, supported are "%s".', $this->options['path-mode'], implode('", "', $modes) )); } $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode']; $paths = array_filter(array_map( static function ($path) { return realpath($path); }, $this->getPath() )); if (!\count($paths)) { if ($isIntersectionPathMode) { return new \ArrayIterator([]); } return $this->iterableToTraversable($this->getConfig()->getFinder()); } $pathsByType = [ 'file' => [], 'dir' => [], ]; foreach ($paths as $path) { if (is_file($path)) { $pathsByType['file'][] = $path; } else { $pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR; } } $nestedFinder = null; $currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder()); try { $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder; } catch (\Exception $e) { } if ($isIntersectionPathMode) { if (null === $nestedFinder) { throw new InvalidConfigurationException( 'Cannot create intersection with not-fully defined Finder in configuration file.' ); } return new \CallbackFilterIterator( $nestedFinder, static function (\SplFileInfo $current) use ($pathsByType) { $currentRealPath = $current->getRealPath(); if (\in_array($currentRealPath, $pathsByType['file'], true)) { return true; } foreach ($pathsByType['dir'] as $path) { if (0 === strpos($currentRealPath, $path)) { return true; } } return false; } ); } if (null !== $this->getConfigFile() && null !== $nestedFinder) { $this->configFinderIsOverridden = true; } if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) { return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']); } return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']); } private function setOption($name, $value) { if (!\array_key_exists($name, $this->options)) { throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name)); } $this->options[$name] = $value; } private function resolveOptionBooleanValue($optionName) { $value = $this->options[$optionName]; if (\is_bool($value)) { return $value; } if (!\is_string($value)) { throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName)); } if ('yes' === $value) { return true; } if ('no' === $value) { return false; } $message = sprintf('Expected "yes" or "no" for option "%s", other values are deprecated and support will be removed in 3.0. Got "%s", this implicitly set the option to "false".', $optionName, $value); if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new InvalidConfigurationException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); return false; } private static function separatedContextLessInclude($path) { return include $path; } } toolInfo = new ToolInfo(); $this->add(new DescribeCommand()); $this->add(new FixCommand($this->toolInfo)); $this->add(new ReadmeCommand()); $this->add(new SelfUpdateCommand( new NewVersionChecker(new GithubClient()), $this->toolInfo, new PharChecker() )); } public function doRun(InputInterface $input, OutputInterface $output) { $stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : ($input->hasParameterOption('--format', true) && 'txt' !== $input->getParameterOption('--format', null, true) ? null : $output) ; if (null !== $stdErr) { $warningsDetector = new WarningsDetector($this->toolInfo); $warningsDetector->detectOldVendor(); $warningsDetector->detectOldMajor(); foreach ($warningsDetector->getWarnings() as $warning) { $stdErr->writeln(sprintf($stdErr->isDecorated() ? '%s' : '%s', $warning)); } } return parent::doRun($input, $output); } public function getLongVersion() { $version = parent::getLongVersion(); if (self::VERSION_CODENAME) { $version .= ' '.self::VERSION_CODENAME.''; } $version .= ' by Fabien Potencier and Dariusz Ruminski'; $commit = '2e82abd1322897eb8aacb861e5ff551af3888349'; if ('@'.'git-commit@' !== $commit) { $version .= ' ('.substr($commit, 0, 7).')'; } return $version; } protected function getDefaultCommands() { return [new HelpCommand(), new ListCommand()]; } } ['symbol' => '?', 'format' => '%s', 'description' => 'unknown'], FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '%s', 'description' => 'invalid file syntax, file ignored'], FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '%s', 'description' => 'Skipped'], FixerFileProcessedEvent::STATUS_NO_CHANGES => ['symbol' => '.', 'format' => '%s', 'description' => 'no changes'], FixerFileProcessedEvent::STATUS_FIXED => ['symbol' => 'F', 'format' => '%s', 'description' => 'fixed'], FixerFileProcessedEvent::STATUS_EXCEPTION => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], ]; private $eventDispatcher; private $output; private $files; private $processedFiles = 0; private $symbolsPerLine; public function __construct(OutputInterface $output, EventDispatcherInterface $dispatcher, $width, $nbFiles) { $this->output = $output; $this->eventDispatcher = $dispatcher; $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); $this->symbolsPerLine = $width; if (null !== $nbFiles) { $this->files = $nbFiles; $this->symbolsPerLine = max(1, ($width ?: 80) - \strlen((string) $this->files) * 2 - 11); } } public function __destruct() { $this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); } public function onFixerFileProcessed(FixerFileProcessedEvent $event) { if ( null === $this->files && null !== $this->symbolsPerLine && 0 === $this->processedFiles % $this->symbolsPerLine && 0 !== $this->processedFiles ) { $this->output->writeln(''); } $status = self::$eventStatusMap[$event->getStatus()]; $this->output->write($this->output->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']); ++$this->processedFiles; if (null !== $this->files) { $symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine; $isLast = $this->processedFiles === $this->files; if (0 === $symbolsOnCurrentLine || $isLast) { $this->output->write(sprintf( '%s %'.\strlen((string) $this->files).'d / %d (%3d%%)', $isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '', $this->processedFiles, $this->files, round($this->processedFiles / $this->files * 100) )); if (!$isLast) { $this->output->writeln(''); } } } } public function printLegend() { $symbols = []; foreach (self::$eventStatusMap as $status) { $symbol = $status['symbol']; if ('' === $symbol || isset($symbols[$symbol])) { continue; } $symbols[$symbol] = sprintf('%s-%s', $this->output->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']); } $this->output->write(sprintf("\nLegend: %s\n", implode(', ', $symbols))); } } output = $output; $this->isDecorated = $output->isDecorated(); } public function listErrors($process, array $errors) { $this->output->writeln(['', sprintf( 'Files that were not fixed due to errors reported during %s:', $process )]); $showDetails = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE; $showTrace = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG; foreach ($errors as $i => $error) { $this->output->writeln(sprintf('%4d) %s', $i + 1, $error->getFilePath())); $e = $error->getSource(); if (!$showDetails || null === $e) { continue; } $class = sprintf('[%s]', \get_class($e)); $message = $e->getMessage(); $code = $e->getCode(); if (0 !== $code) { $message .= " (${code})"; } $length = max(\strlen($class), \strlen($message)); $lines = [ '', $class, $message, '', ]; $this->output->writeln(''); foreach ($lines as $line) { if (\strlen($line) < $length) { $line .= str_repeat(' ', $length - \strlen($line)); } $this->output->writeln(sprintf(' %s ', $this->prepareOutput($line))); } if ($showTrace && !$e instanceof LintingException) { $this->output->writeln(''); $stackTrace = $e->getTrace(); foreach ($stackTrace as $trace) { if (isset($trace['class'], $trace['function']) && \Symfony\Component\Console\Command\Command::class === $trace['class'] && 'run' === $trace['function']) { $this->output->writeln(' [ ... ]'); break; } $this->outputTrace($trace); } } if (Error::TYPE_LINT === $error->getType() && 0 < \count($error->getAppliedFixers())) { $this->output->writeln(''); $this->output->writeln(sprintf(' Applied fixers: %s', implode(', ', $error->getAppliedFixers()))); $diff = $error->getDiff(); if (!empty($diff)) { $diffFormatter = new DiffConsoleFormatter($this->isDecorated, sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL )); $this->output->writeln($diffFormatter->format($diff)); } } } } private function outputTrace(array $trace) { if (isset($trace['class'], $trace['type'], $trace['function'])) { $this->output->writeln(sprintf( ' %s%s%s()', $this->prepareOutput($trace['class']), $this->prepareOutput($trace['type']), $this->prepareOutput($trace['function']) )); } elseif (isset($trace['function'])) { $this->output->writeln(sprintf(' %s()', $this->prepareOutput($trace['function']))); } if (isset($trace['file'])) { $this->output->writeln(sprintf(' in %s at line %d', $this->prepareOutput($trace['file']), $trace['line'])); } } private function prepareOutput($string) { return $this->isDecorated ? OutputFormatter::escape($string) : $string ; } } toolInfo = $toolInfo; } public function detectOldMajor() { } public function detectOldVendor() { if ($this->toolInfo->isInstalledByComposer()) { $details = $this->toolInfo->getComposerInstallationDetails(); if (ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME === $details['name']) { $this->warnings[] = sprintf( 'You are running PHP CS Fixer installed with old vendor `%s`. Please update to `%s`.', ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME, ToolInfo::COMPOSER_PACKAGE_NAME ); } } } public function getWarnings() { if (!\count($this->warnings)) { return []; } return array_unique(array_merge( $this->warnings, ['If you need help while solving warnings, ask at https://gitter.im/PHP-CS-Fixer, we will help you!'] )); } } registerBuiltInFixers(); } $this->fixerFactory = $fixerFactory; } protected function configure() { $this ->setName(self::COMMAND_NAME) ->setDefinition( [ new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'), ] ) ->setDescription('Describe rule / ruleset.') ; } protected function execute(InputInterface $input, OutputInterface $output) { $name = $input->getArgument('name'); try { if ('@' === $name[0]) { $this->describeSet($output, $name); return; } $this->describeRule($output, $name); } catch (DescribeNameNotFoundException $e) { $matcher = new WordMatcher( 'set' === $e->getType() ? $this->getSetNames() : array_keys($this->getFixers()) ); $alternative = $matcher->match($name); $this->describeList($output, $e->getType()); throw new \InvalidArgumentException(sprintf( '%s "%s" not found.%s', ucfirst($e->getType()), $name, null === $alternative ? '' : ' Did you mean "'.$alternative.'"?' )); } } private function describeRule(OutputInterface $output, $name) { $fixers = $this->getFixers(); if (!isset($fixers[$name])) { throw new DescribeNameNotFoundException($name, 'rule'); } $fixer = $fixers[$name]; if ($fixer instanceof DefinedFixerInterface) { $definition = $fixer->getDefinition(); } else { $definition = new FixerDefinition('Description is not available.', []); } $description = $definition->getSummary(); if ($fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $message = [] === $successors ? 'will be removed on next major version' : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); $message = Preg::replace('/(`.+?`)/', '$1', $message); $description .= sprintf(' DEPRECATED: %s.', $message); } $output->writeln(sprintf('Description of %s rule.', $name)); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $output->writeln(sprintf('Fixer class: %s.', \get_class($fixer))); } $output->writeln($description); if ($definition->getDescription()) { $output->writeln($definition->getDescription()); } $output->writeln(''); if ($fixer->isRisky()) { $output->writeln('Fixer applying this rule is risky.'); if ($definition->getRiskyDescription()) { $output->writeln($definition->getRiskyDescription()); } $output->writeln(''); } if ($fixer instanceof ConfigurationDefinitionFixerInterface) { $configurationDefinition = $fixer->getConfigurationDefinition(); $options = $configurationDefinition->getOptions(); $output->writeln(sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's')); foreach ($options as $option) { $line = '* '.OutputFormatter::escape($option->getName()).''; $allowed = HelpCommand::getDisplayableAllowedValues($option); if (null !== $allowed) { foreach ($allowed as &$value) { if ($value instanceof AllowedValueSubset) { $value = 'a subset of '.HelpCommand::toString($value->getAllowedValues()).''; } else { $value = ''.HelpCommand::toString($value).''; } } } else { $allowed = array_map( function ($type) { return ''.$type.''; }, $option->getAllowedTypes() ); } if (null !== $allowed) { $line .= ' ('.implode(', ', $allowed).')'; } $description = Preg::replace('/(`.+?`)/', '$1', OutputFormatter::escape($option->getDescription())); $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; '; if ($option->hasDefault()) { $line .= sprintf( 'defaults to %s', HelpCommand::toString($option->getDefault()) ); } else { $line .= 'required'; } if ($option instanceof DeprecatedFixerOption) { $line .= '. DEPRECATED: '.Preg::replace( '/(`.+?`)/', '$1', OutputFormatter::escape(lcfirst($option->getDeprecationMessage())) ); } if ($option instanceof AliasedFixerOption) { $line .= '; DEPRECATED alias: '.$option->getAlias().''; } $output->writeln($line); } $output->writeln(''); } elseif ($fixer instanceof ConfigurableFixerInterface) { $output->writeln('Fixer is configurable.'); if ($definition->getConfigurationDescription()) { $output->writeln($definition->getConfigurationDescription()); } if ($definition->getDefaultConfiguration()) { $output->writeln(sprintf('Default configuration: %s.', HelpCommand::toString($definition->getDefaultConfiguration()))); } $output->writeln(''); } $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample) { if ($codeSample instanceof VersionSpecificCodeSampleInterface) { return $codeSample->isSuitableFor(\PHP_VERSION_ID); } return true; }); if (!\count($codeSamples)) { $output->writeln([ 'Fixing examples can not be demonstrated on the current PHP version.', '', ]); } else { $output->writeln('Fixing examples:'); $differ = new FullDiffer(); $diffFormatter = new DiffConsoleFormatter($output->isDecorated(), sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL )); foreach ($codeSamples as $index => $codeSample) { $old = $codeSample->getCode(); $tokens = Tokens::fromCode($old); if ($fixer instanceof ConfigurableFixerInterface) { $configuration = $codeSample->getConfiguration(); $fixer->configure(null === $configuration ? [] : $configuration); } $file = $codeSample instanceof FileSpecificCodeSampleInterface ? $codeSample->getSplFileInfo() : new StdinFileInfo(); $fixer->fix($file, $tokens); $diff = $differ->diff($old, $tokens->generateCode()); if ($fixer instanceof ConfigurableFixerInterface) { if (null === $configuration) { $output->writeln(sprintf(' * Example #%d. Fixing with the default configuration.', $index + 1)); } else { $output->writeln(sprintf(' * Example #%d. Fixing with configuration: %s.', $index + 1, HelpCommand::toString($codeSample->getConfiguration()))); } } else { $output->writeln(sprintf(' * Example #%d.', $index + 1)); } $output->writeln($diffFormatter->format($diff, ' %s')); $output->writeln(''); } } } private function describeSet(OutputInterface $output, $name) { if (!\in_array($name, $this->getSetNames(), true)) { throw new DescribeNameNotFoundException($name, 'set'); } $ruleSet = new RuleSet([$name => true]); $rules = $ruleSet->getRules(); ksort($rules); $fixers = $this->getFixers(); $output->writeln(sprintf('Description of %s set.', $name)); $output->writeln(''); $help = ''; foreach ($rules as $rule => $config) { $definition = $fixers[$rule]->getDefinition(); $help .= sprintf( " * %s%s\n | %s\n%s\n", $rule, $fixers[$rule]->isRisky() ? ' risky' : '', $definition->getSummary(), true !== $config ? sprintf(" | Configuration: %s\n", HelpCommand::toString($config)) : '' ); } $output->write($help); } private function getFixers() { if (null !== $this->fixers) { return $this->fixers; } $fixers = []; foreach ($this->fixerFactory->getFixers() as $fixer) { $fixers[$fixer->getName()] = $fixer; } $this->fixers = $fixers; ksort($this->fixers); return $this->fixers; } private function getSetNames() { if (null !== $this->setNames) { return $this->setNames; } $set = new RuleSet(); $this->setNames = $set->getSetDefinitionNames(); sort($this->setNames); return $this->setNames; } private function describeList(OutputInterface $output, $type) { if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { $describe = [ 'set' => $this->getSetNames(), 'rules' => $this->getFixers(), ]; } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $describe = 'set' === $type ? ['set' => $this->getSetNames()] : ['rules' => $this->getFixers()]; } else { return; } foreach ($describe as $list => $items) { $output->writeln(sprintf('Defined %s:', $list)); foreach ($items as $name => $item) { $output->writeln(sprintf('* %s', \is_string($name) ? $name : $item)); } } } } %command.name% command tries to fix as much coding standards problems as possible on a given file or files in a given directory and its subdirectories: $ php %command.full_name% /path/to/dir $ php %command.full_name% /path/to/file By default --path-mode is set to ``override``, which means, that if you specify the path to a file or a directory via command arguments, then the paths provided to a ``Finder`` in config file will be ignored. You can use --path-mode=intersection to merge paths from the config file and from the argument: $ php %command.full_name% --path-mode=intersection /path/to/dir The --format option for the output format. Supported formats are ``txt`` (default one), ``json``, ``xml``, ``checkstyle`` and ``junit``. NOTE: the output for the following formats are generated in accordance with XML schemas * ``junit`` follows the `JUnit xml schema from Jenkins `_ * ``checkstyle`` follows the common `"checkstyle" xml schema `_ The --verbose option will show the applied rules. When using the ``txt`` format it will also displays progress notifications. The --rules option limits the rules to apply on the project: $ php %command.full_name% /path/to/project --rules=@PSR2 By default the PSR1 and PSR2 rules are used. The --rules option lets you choose the exact rules to apply (the rule names must be separated by a comma): $ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type You can also blacklist the rules you don't want by placing a dash in front of the rule name, if this is more convenient, using -name_of_fixer: $ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type When using combinations of exact and blacklist rules, applying exact rules along with above blacklisted results: $ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison Complete configuration for rules can be supplied using a ``json`` formatted string. $ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}' The --dry-run flag will run the fixer without making changes to your files. The --diff flag can be used to let the fixer output all the changes it makes. The --diff-format option allows to specify in which format the fixer should output the changes it makes: * udiff: unified diff format; * sbd: Sebastianbergmann/diff format (default when using `--diff` without specifying `diff-format`). The --allow-risky option (pass ``yes`` or ``no``) allows you to set whether risky rules may run. Default value is taken from config file. Risky rule is a rule, which could change code behaviour. By default no risky rules are run. The --stop-on-violation flag stops the execution upon first file that needs to be fixed. The --show-progress option allows you to choose the way process progress is rendered: * none: disables progress output; * run-in: [deprecated] simple single-line progress output; * estimating: [deprecated] multiline progress output with number of files and percentage on each line. Note that with this option, the files list is evaluated before processing to get the total number of files and then kept in memory to avoid using the file iterator twice. This has an impact on memory usage so using this option is not recommended on very large projects; * estimating-max: [deprecated] same as dots; * dots: same as estimating but using all terminal columns instead of default 80. If the option is not provided, it defaults to run-in unless a config file that disables output is used, in which case it defaults to none. This option has no effect if the verbosity of the command is less than verbose. $ php %command.full_name% --verbose --show-progress=estimating The command can also read from standard input, in which case it won't automatically fix anything: $ cat foo.php | php %command.full_name% --diff - Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that would be default in next MAJOR release (unified differ, estimating, full-width progress indicator): $ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff Choose from the list of available rules: %%%FIXERS_DETAILS%%% The --dry-run option displays the files that need to be fixed but without actually modifying them: $ php %command.full_name% /path/to/code --dry-run Config file ----------- Instead of using command line options to customize the rule, you can save the project configuration in a .php_cs.dist file in the root directory of your project. The file must return an instance of `PhpCsFixer\ConfigInterface` (%%%CONFIG_INTERFACE_URL%%%) which lets you configure the rules, the files and directories that need to be analyzed. You may also create .php_cs file, which is the local configuration that will be used instead of the project configuration. It is a good practice to add that file into your .gitignore file. With the --config option you can specify the path to the .php_cs file. The example below will add two rules to the default list of PSR2 set rules: exclude('somedir') ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php') ->in(__DIR__) ; return PhpCsFixer\Config::create() ->setRules([ '@PSR2' => true, 'strict_param' => true, 'array_syntax' => ['syntax' => 'short'], ]) ->setFinder($finder) ; ?> **NOTE**: ``exclude`` will work only for directories, so if you need to exclude file, try ``notPath``. See `Symfony\Finder` (https://symfony.com/doc/current/components/finder.html) online documentation for other `Finder` methods. You may also use a blacklist for the rules instead of the above shown whitelist approach. The following example shows how to use all ``Symfony`` rules but the ``full_opening_tag`` rule. exclude('somedir') ->in(__DIR__) ; return PhpCsFixer\Config::create() ->setRules([ '@Symfony' => true, 'full_opening_tag' => false, ]) ->setFinder($finder) ; ?> You may want to use non-linux whitespaces in your project. Then you need to configure them in your config file. setIndent("\t") ->setLineEnding("\r\n") ; ?> By using ``--using-cache`` option with ``yes`` or ``no`` you can set if the caching mechanism should be used. Caching ------- The caching mechanism is enabled by default. This will speed up further runs by fixing only files that were modified since the last run. The tool will fix all files if the tool version has changed or the list of rules has changed. Cache is supported only for tool downloaded as phar file or installed via composer. Cache can be disabled via ``--using-cache`` option or config file: setUsingCache(false) ; ?> Cache file can be specified via ``--cache-file`` option or config file: setCacheFile(__DIR__.'/.php_cs.cache') ; ?> Using PHP CS Fixer on CI ------------------------ Require ``friendsofphp/php-cs-fixer`` as a ``dev`` dependency: $ ./composer.phar require --dev friendsofphp/php-cs-fixer Then, add the following command to your CI: %%%CI_INTEGRATION%%% Where ``$COMMIT_RANGE`` is your range of commits, eg ``$TRAVIS_COMMIT_RANGE`` or ``HEAD~..HEAD``. Exit codes ---------- Exit code is built using following bit flags: * 0 OK. * 1 General error (or PHP minimal requirement not matched). * 4 Some files have invalid syntax (only in dry-run mode). * 8 Some files need fixing (only in dry-run mode). * 16 Configuration error of the application. * 32 Configuration error of a Fixer. * 64 Exception raised within the application. (applies to exit codes of the `fix` command only) EOF ; return strtr($template, [ '%%%CONFIG_INTERFACE_URL%%%' => sprintf( 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/v%s/src/ConfigInterface.php', self::getLatestReleaseVersionFromChangeLog() ), '%%%CI_INTEGRATION%%%' => implode("\n", array_map( static function ($line) { return ' $ '.$line; }, \array_slice(file(__DIR__.'/../../../dev-tools/ci-integration.sh', FILE_IGNORE_NEW_LINES), 3) )), '%%%FIXERS_DETAILS%%%' => self::getFixersHelp(), ]); } public static function toString($value) { if (\is_array($value)) { static $replaces = [ ['#\r|\n#', '#\s{1,}#', '#array\s*\((.*)\)#s', '#\[\s+#', '#,\s*\]#', '#\d+\s*=>\s*#'], ['', ' ', '[$1]', '[', ']', ''], ]; $str = var_export($value, true); do { $strNew = Preg::replace( $replaces[0], $replaces[1], $str ); if ($strNew === $str) { break; } $str = $strNew; } while (true); } else { $str = var_export($value, true); } return Preg::replace('/\bNULL\b/', 'null', $str); } public static function getDisplayableAllowedValues(FixerOptionInterface $option) { $allowed = $option->getAllowedValues(); if (null !== $allowed) { $allowed = array_filter($allowed, static function ($value) { return !($value instanceof \Closure); }); usort($allowed, static function ($valueA, $valueB) { if ($valueA instanceof AllowedValueSubset) { return -1; } if ($valueB instanceof AllowedValueSubset) { return 1; } return strcasecmp( self::toString($valueA), self::toString($valueB) ); }); if (0 === \count($allowed)) { $allowed = null; } } return $allowed; } public static function getLatestReleaseVersionFromChangeLog() { static $version = null; if (null !== $version) { return $version; } $changelogFile = self::getChangeLogFile(); if (null === $changelogFile) { $version = Application::VERSION; return $version; } $changelog = @file_get_contents($changelogFile); if (false === $changelog) { $error = error_get_last(); throw new \RuntimeException(sprintf( 'Failed to read content of the changelog file "%s".%s', $changelogFile, $error ? ' '.$error['message'] : '' )); } for ($i = (int) Application::VERSION; $i > 0; --$i) { if (1 === Preg::match('/Changelog for v('.$i.'.\d+.\d+)/', $changelog, $matches)) { $version = $matches[1]; break; } } if (null === $version) { throw new \RuntimeException(sprintf('Failed to parse changelog data of "%s".', $changelogFile)); } return $version; } protected function initialize(InputInterface $input, OutputInterface $output) { $output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue')); } private static function getChangeLogFile() { $changelogFile = __DIR__.'/../../../CHANGELOG.md'; return is_file($changelogFile) ? $changelogFile : null; } private static function getFixersHelp() { $help = ''; $fixerFactory = new FixerFactory(); $fixers = $fixerFactory->registerBuiltInFixers()->getFixers(); usort( $fixers, static function (FixerInterface $a, FixerInterface $b) { return strcmp($a->getName(), $b->getName()); } ); $ruleSets = []; foreach (RuleSet::create()->getSetDefinitionNames() as $setName) { $ruleSets[$setName] = new RuleSet([$setName => true]); } $getSetsWithRule = static function ($rule) use ($ruleSets) { $sets = []; foreach ($ruleSets as $setName => $ruleSet) { if ($ruleSet->hasRule($rule)) { $sets[] = $setName; } } return $sets; }; $count = \count($fixers) - 1; foreach ($fixers as $i => $fixer) { $sets = $getSetsWithRule($fixer->getName()); if ($fixer instanceof DefinedFixerInterface) { $description = $fixer->getDefinition()->getSummary(); } else { $description = '[n/a]'; } if ($fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $message = [] === $successors ? 'will be removed on next major version' : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); $description .= sprintf(' DEPRECATED: %s.', $message); } $description = implode("\n | ", self::wordwrap( Preg::replace('/(`.+?`)/', '$1', $description), 72 )); if (!empty($sets)) { $help .= sprintf(" * %s [%s]\n | %s\n", $fixer->getName(), implode(', ', $sets), $description); } else { $help .= sprintf(" * %s\n | %s\n", $fixer->getName(), $description); } if ($fixer->isRisky()) { $help .= sprintf( " | *Risky rule: %s.*\n", Preg::replace( '/(`.+?`)/', '$1', lcfirst(Preg::replace('/\.$/', '', $fixer->getDefinition()->getRiskyDescription())) ) ); } if ($fixer instanceof ConfigurationDefinitionFixerInterface) { $configurationDefinition = $fixer->getConfigurationDefinition(); $configurationDefinitionOptions = $configurationDefinition->getOptions(); if (\count($configurationDefinitionOptions)) { $help .= " |\n | Configuration options:\n"; usort( $configurationDefinitionOptions, static function (FixerOptionInterface $optionA, FixerOptionInterface $optionB) { return strcmp($optionA->getName(), $optionB->getName()); } ); foreach ($configurationDefinitionOptions as $option) { $line = ''.OutputFormatter::escape($option->getName()).''; $allowed = self::getDisplayableAllowedValues($option); if (null !== $allowed) { foreach ($allowed as &$value) { if ($value instanceof AllowedValueSubset) { $value = 'a subset of '.self::toString($value->getAllowedValues()).''; } else { $value = ''.self::toString($value).''; } } } else { $allowed = array_map( function ($type) { return ''.$type.''; }, $option->getAllowedTypes() ); } if (null !== $allowed) { $line .= ' ('.implode(', ', $allowed).')'; } $line .= ': '.Preg::replace( '/(`.+?`)/', '$1', lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDescription()))) ).'; '; if ($option->hasDefault()) { $line .= 'defaults to '.self::toString($option->getDefault()).''; } else { $line .= 'required'; } if ($option instanceof DeprecatedFixerOption) { $line .= '. DEPRECATED: '.Preg::replace( '/(`.+?`)/', '$1', lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDeprecationMessage()))) ); } if ($option instanceof AliasedFixerOption) { $line .= '; DEPRECATED alias: '.$option->getAlias().''; } foreach (self::wordwrap($line, 72) as $index => $line) { $help .= (0 === $index ? ' | - ' : ' | ').$line."\n"; } } } } elseif ($fixer instanceof ConfigurableFixerInterface) { $help .= " | *Configurable rule.*\n"; } if ($count !== $i) { $help .= "\n"; } } return Preg::replace('#\\\\()#', '<<$1', $help); } private static function wordwrap($string, $width) { $result = []; $currentLine = 0; $lineLength = 0; foreach (explode(' ', $string) as $word) { $wordLength = \strlen(Preg::replace('~~', '', $word)); if (0 !== $lineLength) { ++$wordLength; } if ($lineLength + $wordLength > $width) { ++$currentLine; $lineLength = 0; } $result[$currentLine][] = $word; $lineLength += $wordLength; } return array_map(static function ($line) { return implode(' ', $line); }, $result); } } defaultConfig = new Config(); $this->errorsManager = new ErrorsManager(); $this->eventDispatcher = new EventDispatcher(); $this->stopwatch = new Stopwatch(); $this->toolInfo = $toolInfo; } public function getHelp() { return HelpCommand::getHelpCopy(); } protected function configure() { $this ->setName(self::COMMAND_NAME) ->setDefinition( [ new InputArgument('path', InputArgument::IS_ARRAY, 'The path.'), new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be override or intersection).', 'override'), new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be yes or no).'), new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php_cs file.'), new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'The rules.'), new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Does cache should be used (can be yes or no).'), new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'), new InputOption('diff', '', InputOption::VALUE_NONE, 'Also produce diff for each file.'), new InputOption('diff-format', '', InputOption::VALUE_REQUIRED, 'Specify diff format.'), new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'), new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'), new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, run-in, estimating, estimating-max or dots).'), ] ) ->setDescription('Fixes a directory or a file.') ; } protected function execute(InputInterface $input, OutputInterface $output) { $verbosity = $output->getVerbosity(); $passedConfig = $input->getOption('config'); $passedRules = $input->getOption('rules'); $resolver = new ConfigurationResolver( $this->defaultConfig, [ 'allow-risky' => $input->getOption('allow-risky'), 'config' => $passedConfig, 'dry-run' => $input->getOption('dry-run'), 'rules' => $passedRules, 'path' => $input->getArgument('path'), 'path-mode' => $input->getOption('path-mode'), 'using-cache' => $input->getOption('using-cache'), 'cache-file' => $input->getOption('cache-file'), 'format' => $input->getOption('format'), 'diff' => $input->getOption('diff'), 'diff-format' => $input->getOption('diff-format'), 'stop-on-violation' => $input->getOption('stop-on-violation'), 'verbosity' => $verbosity, 'show-progress' => $input->getOption('show-progress'), ], getcwd(), $this->toolInfo ); $reporter = $resolver->getReporter(); $stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : ('txt' === $reporter->getFormat() ? $output : null) ; if (null !== $stdErr) { if (null !== $passedConfig && null !== $passedRules) { if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \RuntimeException('Passing both `config` and `rules` options is not possible. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.'); } $stdErr->writeln([ sprintf($stdErr->isDecorated() ? '%s' : '%s', 'When passing both "--config" and "--rules" the rules within the configuration file are not used.'), sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Passing both options is deprecated; version v3.0 PHP-CS-Fixer will exit with a configuration error code.'), ]); } $configFile = $resolver->getConfigFile(); $stdErr->writeln(sprintf('Loaded config %s%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"')); if ($resolver->getUsingCache()) { $cacheFile = $resolver->getCacheFile(); if (is_file($cacheFile)) { $stdErr->writeln(sprintf('Using cache file "%s".', $cacheFile)); } } } $progressType = $resolver->getProgress(); $finder = $resolver->getFinder(); if (null !== $stdErr && $resolver->configFinderIsOverridden()) { $stdErr->writeln( sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Paths from configuration file have been overridden by paths provided as command arguments.') ); } if ('none' === $progressType || null === $stdErr) { $progressOutput = new NullOutput(); } elseif ('run-in' === $progressType) { $progressOutput = new ProcessOutput($stdErr, $this->eventDispatcher, null, null); } else { $finder = new \ArrayIterator(iterator_to_array($finder)); $progressOutput = new ProcessOutput( $stdErr, $this->eventDispatcher, 'estimating' !== $progressType ? (new Terminal())->getWidth() : null, \count($finder) ); } $runner = new Runner( $finder, $resolver->getFixers(), $resolver->getDiffer(), 'none' !== $progressType ? $this->eventDispatcher : null, $this->errorsManager, $resolver->getLinter(), $resolver->isDryRun(), $resolver->getCacheManager(), $resolver->getDirectory(), $resolver->shouldStopOnViolation() ); $this->stopwatch->start('fixFiles'); $changed = $runner->fix(); $this->stopwatch->stop('fixFiles'); $progressOutput->printLegend(); $fixEvent = $this->stopwatch->getEvent('fixFiles'); $reportSummary = new ReportSummary( $changed, $fixEvent->getDuration(), $fixEvent->getMemory(), OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity(), $resolver->isDryRun(), $output->isDecorated() ); $output->isDecorated() ? $output->write($reporter->generate($reportSummary)) : $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW) ; $invalidErrors = $this->errorsManager->getInvalidErrors(); $exceptionErrors = $this->errorsManager->getExceptionErrors(); $lintErrors = $this->errorsManager->getLintErrors(); if (null !== $stdErr) { $errorOutput = new ErrorOutput($stdErr); if (\count($invalidErrors) > 0) { $errorOutput->listErrors('linting before fixing', $invalidErrors); } if (\count($exceptionErrors) > 0) { $errorOutput->listErrors('fixing', $exceptionErrors); } if (\count($lintErrors) > 0) { $errorOutput->listErrors('linting after fixing', $lintErrors); } } $exitStatusCalculator = new FixCommandExitStatusCalculator(); return $exitStatusCalculator->calculate( $resolver->isDryRun(), \count($changed) > 0, \count($invalidErrors) > 0, \count($exceptionErrors) > 0 ); } } name = $name; $this->type = $type; } public function getName() { return $this->name; } public function getType() { return $this->type; } } versionChecker = $versionChecker; $this->toolInfo = $toolInfo; $this->pharChecker = $pharChecker; } protected function configure() { $this ->setName(self::COMMAND_NAME) ->setAliases(['selfupdate']) ->setDefinition( [ new InputOption('--force', '-f', InputOption::VALUE_NONE, 'Force update to next major version if available.'), ] ) ->setDescription('Update php-cs-fixer.phar to the latest stable version.') ->setHelp( <<<'EOT' The %command.name% command replace your php-cs-fixer.phar by the latest version released on: https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases $ php php-cs-fixer.phar %command.name% EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->toolInfo->isInstalledAsPhar()) { $output->writeln('Self-update is available only for PHAR version.'); return 1; } $currentVersion = $this->getApplication()->getVersion(); Preg::match('/^v?(?\d+)\./', $currentVersion, $matches); $currentMajor = (int) $matches['major']; try { $latestVersion = $this->versionChecker->getLatestVersion(); $latestVersionOfCurrentMajor = $this->versionChecker->getLatestVersionOfMajor($currentMajor); } catch (\Exception $exception) { $output->writeln(sprintf( 'Unable to determine newest version: %s', $exception->getMessage() )); return 1; } if (1 !== $this->versionChecker->compareVersions($latestVersion, $currentVersion)) { $output->writeln('php-cs-fixer is already up to date.'); return 0; } $remoteTag = $latestVersion; if ( 0 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $latestVersion) && true !== $input->getOption('force') ) { $output->writeln(sprintf('A new major version of php-cs-fixer is available (%s)', $latestVersion)); $output->writeln(sprintf('Before upgrading please read https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/%s/UPGRADE.md', $latestVersion)); $output->writeln('If you are ready to upgrade run this command with -f'); $output->writeln('Checking for new minor/patch version...'); if (1 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $currentVersion)) { $output->writeln('No minor update for php-cs-fixer.'); return 0; } $remoteTag = $latestVersionOfCurrentMajor; } $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; if (!is_writable($localFilename)) { $output->writeln(sprintf('No permission to update %s file.', $localFilename)); return 1; } $tempFilename = \dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar'; $remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag); if (false === @copy($remoteFilename, $tempFilename)) { $output->writeln(sprintf('Unable to download new version %s from the server.', $remoteTag)); return 1; } chmod($tempFilename, 0777 & ~umask()); $pharInvalidityReason = $this->pharChecker->checkFileValidity($tempFilename); if (null !== $pharInvalidityReason) { unlink($tempFilename); $output->writeln(sprintf('The download of %s is corrupt (%s).', $remoteTag, $pharInvalidityReason)); $output->writeln('Please re-run the self-update command to try again.'); return 1; } rename($tempFilename, $localFilename); $output->writeln(sprintf('php-cs-fixer updated (%s)', $remoteTag)); } } setName(self::COMMAND_NAME) ->setDescription('Generates the README content, based on the fix command help.') ; } protected function execute(InputInterface $input, OutputInterface $output) { $header = <<<'EOF' PHP Coding Standards Fixer ========================== The PHP Coding Standards Fixer (PHP CS Fixer) tool fixes your code to follow standards; whether you want to follow PHP coding standards as defined in the PSR-1, PSR-2, etc., or other community driven ones like the Symfony one. You can **also** define your (teams) style through configuration. It can modernize your code (like converting the ``pow`` function to the ``**`` operator on PHP 5.6) and (micro) optimize it. If you are already using a linter to identify coding standards problems in your code, you know that fixing them by hand is tedious, especially on large projects. This tool does not only detect them, but also fixes them for you. The PHP CS Fixer is maintained on GitHub at https://github.com/FriendsOfPHP/PHP-CS-Fixer bug reports and ideas about new features are welcome there. You can talk to us at https://gitter.im/PHP-CS-Fixer/Lobby about the project, configuration, possible improvements, ideas and questions, please visit us! Requirements ------------ PHP needs to be a minimum version of PHP 5.6.0. Installation ------------ Locally ~~~~~~~ Download the `php-cs-fixer.phar`_ file and store it somewhere on your computer. Globally (manual) ~~~~~~~~~~~~~~~~~ You can run these commands to easily access latest ``php-cs-fixer`` from anywhere on your system: .. code-block:: bash $ wget %download.url% -O php-cs-fixer or with specified version: .. code-block:: bash $ wget %download.version_url% -O php-cs-fixer or with curl: .. code-block:: bash $ curl -L %download.url% -o php-cs-fixer then: .. code-block:: bash $ sudo chmod a+x php-cs-fixer $ sudo mv php-cs-fixer /usr/local/bin/php-cs-fixer Then, just run ``php-cs-fixer``. Globally (Composer) ~~~~~~~~~~~~~~~~~~~ To install PHP CS Fixer, `install Composer `_ and issue the following command: .. code-block:: bash $ composer global require friendsofphp/php-cs-fixer Then make sure you have the global Composer binaries directory in your ``PATH``. This directory is platform-dependent, see `Composer documentation `_ for details. Example for some Unix systems: .. code-block:: bash $ export PATH="$PATH:$HOME/.composer/vendor/bin" Globally (homebrew) ~~~~~~~~~~~~~~~~~~~ .. code-block:: bash $ brew install php-cs-fixer Locally (PHIVE) ~~~~~~~~~~~~~~~ Install `PHIVE `_ and issue the following command: .. code-block:: bash $ phive install php-cs-fixer # use `--global` for global install Update ------ Locally ~~~~~~~ The ``self-update`` command tries to update ``php-cs-fixer`` itself: .. code-block:: bash $ php php-cs-fixer.phar self-update Globally (manual) ~~~~~~~~~~~~~~~~~ You can update ``php-cs-fixer`` through this command: .. code-block:: bash $ sudo php-cs-fixer self-update Globally (Composer) ~~~~~~~~~~~~~~~~~~~ You can update ``php-cs-fixer`` through this command: .. code-block:: bash $ ./composer.phar global update friendsofphp/php-cs-fixer Globally (homebrew) ~~~~~~~~~~~~~~~~~~~ You can update ``php-cs-fixer`` through this command: .. code-block:: bash $ brew upgrade php-cs-fixer Locally (PHIVE) ~~~~~~~~~~~~~~~~~~~ .. code-block:: bash $ phive update php-cs-fixer Usage ----- EOF; $footer = <<<'EOF' Helpers ------- Dedicated plugins exist for: * `Atom`_ * `NetBeans`_ * `PhpStorm`_ * `Sublime Text`_ * `Vim`_ * `VS Code`_ Contribute ---------- The tool comes with quite a few built-in fixers, but everyone is more than welcome to `contribute`_ more of them. Fixers ~~~~~~ A *fixer* is a class that tries to fix one CS issue (a ``Fixer`` class must implement ``FixerInterface``). Configs ~~~~~~~ A *config* knows about the CS rules and the files and directories that must be scanned by the tool when run in the directory of your project. It is useful for projects that follow a well-known directory structures (like for Symfony projects for instance). .. _php-cs-fixer.phar: %download.url% .. _Atom: https://github.com/Glavin001/atom-beautify .. _NetBeans: http://plugins.netbeans.org/plugin/49042/php-cs-fixer .. _PhpStorm: https://medium.com/@valeryan/how-to-configure-phpstorm-to-use-php-cs-fixer-1844991e521f .. _Sublime Text: https://github.com/benmatselby/sublime-phpcs .. _Vim: https://github.com/stephpy/vim-php-cs-fixer .. _VS Code: https://github.com/junstyle/vscode-php-cs-fixer .. _contribute: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/CONTRIBUTING.md EOF; $command = $this->getApplication()->get('fix'); $help = $command->getHelp(); $help = str_replace('%command.full_name%', 'php-cs-fixer.phar '.$command->getName(), $help); $help = str_replace('%command.name%', $command->getName(), $help); $help = Preg::replace('##', '``', $help); $help = Preg::replace('#`(``.+?``)`#', '$1', $help); $help = Preg::replace('#^(\s+)``(.+)``$#m', '$1$2', $help); $help = Preg::replace('#^ \* ``(.+)``(.*?\n)#m', "* **$1**$2\n", $help); $help = Preg::replace('#^ \\| #m', ' ', $help); $help = Preg::replace('#^ \\|#m', '', $help); $help = Preg::replace('#^(?= \\*Risky rule: )#m', "\n", $help); $help = Preg::replace("#^( Configuration options:\n)( - )#m", "$1\n$2", $help); $help = Preg::replace("#^\n( +\\$ )#m", "\n.. code-block:: bash\n\n$1", $help); $help = Preg::replace("#^\n( +<\\?php)#m", "\n.. code-block:: php\n\n$1", $help); $help = Preg::replaceCallback( '#^\s*<\?(\w+).*?\?>#ms', static function ($matches) { $result = Preg::replace("#^\\.\\. code-block:: bash\n\n#m", '', $matches[0]); if ('php' !== $matches[1]) { $result = Preg::replace("#<\\?{$matches[1]}\\s*#", '', $result); } return Preg::replace("#\n\n +\\?>#", '', $result); }, $help ); $help = Preg::replaceCallback( '#`(.+)`\s?\((.+)<\/url>\)#', static function (array $matches) { return sprintf('`%s <%s>`_', str_replace('\\', '\\\\', $matches[1]), $matches[2]); }, $help ); $help = Preg::replace('#^ #m', ' ', $help); $help = Preg::replace('#\*\* +\[#', '** [', $help); $downloadLatestUrl = sprintf('https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v%s/php-cs-fixer.phar', HelpCommand::getLatestReleaseVersionFromChangeLog()); $downloadUrl = 'https://cs.symfony.com/download/php-cs-fixer-v2.phar'; $header = str_replace('%download.version_url%', $downloadLatestUrl, $header); $header = str_replace('%download.url%', $downloadUrl, $header); $footer = str_replace('%download.version_url%', $downloadLatestUrl, $footer); $footer = str_replace('%download.url%', $downloadUrl, $footer); $output->write($header."\n".$help."\n".$footer); } } candidates = $candidates; } public function match($needle) { $word = null; $distance = ceil(\strlen($needle) * 0.35); foreach ($this->candidates as $candidate) { $candidateDistance = levenshtein($needle, $candidate); if ($candidateDistance < $distance) { $word = $candidate; $distance = $candidateDistance; } } return $word; } } files() ->name('*.php') ->name('*.phpt') ->ignoreDotFiles(true) ->ignoreVCS(true) ->exclude('vendor') ; } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, PhpTokens $phpTokens) { $analyzer = new TokensAnalyzer($phpTokens); $this->classyElements = $analyzer->getClassyElements(); foreach ($phpTokens->findGivenKind(T_DOC_COMMENT) as $index => $docCommentToken) { if (!$this->nextElementAcceptsDoctrineAnnotations($phpTokens, $index)) { continue; } $tokens = DoctrineAnnotationTokens::createFromDocComment( $docCommentToken, $this->configuration['ignored_tags'] ); $this->fixAnnotations($tokens); $phpTokens[$index] = new PhpToken([T_DOC_COMMENT, $tokens->getCode()]); } } abstract protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens); protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('ignored_tags', 'List of tags that must not be treated as Doctrine Annotations.')) ->setAllowedTypes(['array']) ->setAllowedValues([static function ($values) { foreach ($values as $value) { if (!\is_string($value)) { return false; } } return true; }]) ->setDefault([ 'abstract', 'access', 'code', 'deprec', 'encode', 'exception', 'final', 'ingroup', 'inheritdoc', 'inheritDoc', 'magic', 'name', 'toc', 'tutorial', 'private', 'static', 'staticvar', 'staticVar', 'throw', 'api', 'author', 'category', 'copyright', 'deprecated', 'example', 'filesource', 'global', 'ignore', 'internal', 'license', 'link', 'method', 'package', 'param', 'property', 'property-read', 'property-write', 'return', 'see', 'since', 'source', 'subpackage', 'throws', 'todo', 'TODO', 'usedBy', 'uses', 'var', 'version', 'after', 'afterClass', 'backupGlobals', 'backupStaticAttributes', 'before', 'beforeClass', 'codeCoverageIgnore', 'codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd', 'covers', 'coversDefaultClass', 'coversNothing', 'dataProvider', 'depends', 'expectedException', 'expectedExceptionCode', 'expectedExceptionMessage', 'expectedExceptionMessageRegExp', 'group', 'large', 'medium', 'preserveGlobalState', 'requires', 'runTestsInSeparateProcesses', 'runInSeparateProcess', 'small', 'test', 'testdox', 'ticket', 'uses', 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', 'startuml', 'fix', 'FIXME', 'fixme', 'override', ]) ->getOption(), ]); } private function nextElementAcceptsDoctrineAnnotations(PhpTokens $tokens, $index) { do { $index = $tokens->getNextMeaningfulToken($index); if (null === $index) { return false; } } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL])); if ($tokens[$index]->isClassy()) { return true; } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT])) { $index = $tokens->getNextMeaningfulToken($index); } return isset($this->classyElements[$index]); } } getPreviousBlock($tokens, $previousBlockStart); $previous = $previousBlockEnd; if ($tokens[$previous]->equals('}')) { $previous = $tokens->getPrevMeaningfulToken($previous); } if ( !$tokens[$previous]->equals(';') || $tokens[$tokens->getPrevMeaningfulToken($previous)]->equals('{') ) { return false; } $candidateIndex = $tokens->getPrevTokenOfKind( $previous, [ ';', [T_BREAK], [T_CLOSE_TAG], [T_CONTINUE], [T_EXIT], [T_GOTO], [T_IF], [T_RETURN], [T_THROW], ] ); if ( null === $candidateIndex || $tokens[$candidateIndex]->equalsAny([';', [T_CLOSE_TAG], [T_IF]]) || $this->isInConditional($tokens, $candidateIndex, $previousBlockStart) || $this->isInConditionWithoutBraces($tokens, $candidateIndex, $previousBlockStart) ) { return false; } } while (!$tokens[$previousBlockStart]->isGivenKind(T_IF)); return true; } private function getPreviousBlock(Tokens $tokens, $index) { $close = $previous = $tokens->getPrevMeaningfulToken($index); if ($tokens[$close]->equals('}')) { $previous = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $close); } $open = $tokens->getPrevTokenOfKind($previous, [[T_IF], [T_ELSE], [T_ELSEIF]]); if ($tokens[$open]->isGivenKind(T_IF)) { $elseCandidate = $tokens->getPrevMeaningfulToken($open); if ($tokens[$elseCandidate]->isGivenKind(T_ELSE)) { $open = $elseCandidate; } } return [$open, $close]; } private function isInConditional(Tokens $tokens, $index, $lowerLimitIndex) { $candidateIndex = $tokens->getPrevTokenOfKind($index, [')', ';', ':']); if ($tokens[$candidateIndex]->equals(':')) { return true; } if (!$tokens[$candidateIndex]->equals(')')) { return false; } $open = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $candidateIndex); return $tokens->getPrevMeaningfulToken($open) > $lowerLimitIndex; } private function isInConditionWithoutBraces(Tokens $tokens, $index, $lowerLimitIndex) { do { if ($tokens[$index]->isComment() || $tokens[$index]->isWhitespace()) { $index = $tokens->getPrevMeaningfulToken($index); } $token = $tokens[$index]; if ($token->isGivenKind([T_IF, T_ELSEIF, T_ELSE])) { return true; } if ($token->equals(';', '}')) { return false; } if ($token->equals('{')) { $index = $tokens->getPrevMeaningfulToken($index); if ($tokens[$index]->isGivenKind(T_DO)) { --$index; continue; } if (!$tokens[$index]->equals(')')) { return false; } $index = $tokens->findBlockStart( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index ); $index = $tokens->getPrevMeaningfulToken($index); if ($tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) { return false; } } elseif ($token->equals(')')) { $type = Tokens::detectBlockType($token); $index = $tokens->findBlockStart( $type['type'], $index ); $index = $tokens->getPrevMeaningfulToken($index); } else { --$index; } } while ($index > $lowerLimitIndex); return false; } } differ = new Differ(new StrictUnifiedDiffOutputBuilder([ 'fromFile' => 'Original', 'toFile' => 'New', ])); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } differ = new Differ("--- Original\n+++ New\n", false); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } isDecoratedOutput = $isDecoratedOutput; $this->template = $template; } public function format($diff, $lineTemplate = '%s') { $isDecorated = $this->isDecoratedOutput; $template = $isDecorated ? $this->template : Preg::replace('/<[^<>]+>/', '', $this->template) ; return sprintf( $template, implode( PHP_EOL, array_map( function ($string) use ($isDecorated, $lineTemplate) { if ($isDecorated) { $string = Preg::replaceCallback( [ '/^(\+.*)/', '/^(\-.*)/', '/^(@.*)/', ], function ($matches) { if ('+' === $matches[0][0]) { $colour = 'green'; } elseif ('-' === $matches[0][0]) { $colour = 'red'; } else { $colour = 'cyan'; } return sprintf('%s', $colour, OutputFormatter::escape($matches[0]), $colour); }, $string ); } return sprintf($lineTemplate, $string); }, Preg::split('#\R#u', $diff) ) ) ); } } differ = new Differ(new StrictUnifiedDiffOutputBuilder([ 'collapseRanges' => false, 'commonLineThreshold' => 100, 'contextLines' => 100, 'fromFile' => 'Original', 'toFile' => 'New', ])); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } differ = new Differ(); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } configuration['around_argument_assignments']) { foreach ([ 'before_argument_assignments', 'after_argument_assignments', ] as $newOption) { if (!\array_key_exists($newOption, $configuration)) { $this->configuration[$newOption] = null; } } } if (!$this->configuration['around_array_assignments']) { foreach ([ 'before_array_assignments_equals', 'after_array_assignments_equals', 'before_array_assignments_colon', 'after_array_assignments_colon', ] as $newOption) { if (!\array_key_exists($newOption, $configuration)) { $this->configuration[$newOption] = null; } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver(array_merge( parent::createConfigurationDefinition()->getOptions(), [ (new FixerOptionBuilder('around_parentheses', 'Whether to fix spaces around parentheses.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('around_commas', 'Whether to fix spaces around commas.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('around_argument_assignments', 'Whether to fix spaces around argument assignment operator.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->setDeprecationMessage('Use options `before_argument_assignments` and `after_argument_assignments` instead.') ->getOption(), (new FixerOptionBuilder('before_argument_assignments', 'Whether to add, remove or ignore spaces before argument assignment operator.')) ->setAllowedTypes(['null', 'bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder('after_argument_assignments', 'Whether to add, remove or ignore spaces after argument assignment operator.')) ->setAllowedTypes(['null', 'bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder('around_array_assignments', 'Whether to fix spaces around array assignment operators.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->setDeprecationMessage('Use options `before_array_assignments_equals`, `after_array_assignments_equals`, `before_array_assignments_colon` and `after_array_assignments_colon` instead.') ->getOption(), (new FixerOptionBuilder('before_array_assignments_equals', 'Whether to add, remove or ignore spaces before array `=` assignment operator.')) ->setAllowedTypes(['null', 'bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('after_array_assignments_equals', 'Whether to add, remove or ignore spaces after array assignment `=` operator.')) ->setAllowedTypes(['null', 'bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('before_array_assignments_colon', 'Whether to add, remove or ignore spaces before array `:` assignment operator.')) ->setAllowedTypes(['null', 'bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('after_array_assignments_colon', 'Whether to add, remove or ignore spaces after array assignment `:` operator.')) ->setAllowedTypes(['null', 'bool']) ->setDefault(true) ->getOption(), ] )); } protected function fixAnnotations(Tokens $tokens) { if ($this->configuration['around_parentheses']) { $this->fixSpacesAroundParentheses($tokens); } if ($this->configuration['around_commas']) { $this->fixSpacesAroundCommas($tokens); } if ( null !== $this->configuration['before_argument_assignments'] || null !== $this->configuration['after_argument_assignments'] || null !== $this->configuration['before_array_assignments_equals'] || null !== $this->configuration['after_array_assignments_equals'] || null !== $this->configuration['before_array_assignments_colon'] || null !== $this->configuration['after_array_assignments_colon'] ) { $this->fixAroundAssignments($tokens); } } private function fixSpacesAroundParentheses(Tokens $tokens) { $inAnnotationUntilIndex = null; foreach ($tokens as $index => $token) { if (null !== $inAnnotationUntilIndex) { if ($index === $inAnnotationUntilIndex) { $inAnnotationUntilIndex = null; continue; } } elseif ($tokens[$index]->isType(DocLexer::T_AT)) { $endIndex = $tokens->getAnnotationEnd($index); if (null !== $endIndex) { $inAnnotationUntilIndex = $endIndex + 1; } continue; } if (null === $inAnnotationUntilIndex) { continue; } if (!$token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_CLOSE_PARENTHESIS])) { continue; } if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) { $token = $tokens[$index - 1]; if ($token->isType(DocLexer::T_NONE)) { $token->clear(); } $token = $tokens[$index + 1]; } else { $token = $tokens[$index - 1]; } if ($token->isType(DocLexer::T_NONE)) { if (false !== strpos($token->getContent(), "\n")) { continue; } $token->clear(); } } } private function fixSpacesAroundCommas(Tokens $tokens) { $inAnnotationUntilIndex = null; foreach ($tokens as $index => $token) { if (null !== $inAnnotationUntilIndex) { if ($index === $inAnnotationUntilIndex) { $inAnnotationUntilIndex = null; continue; } } elseif ($tokens[$index]->isType(DocLexer::T_AT)) { $endIndex = $tokens->getAnnotationEnd($index); if (null !== $endIndex) { $inAnnotationUntilIndex = $endIndex; } continue; } if (null === $inAnnotationUntilIndex) { continue; } if (!$token->isType(DocLexer::T_COMMA)) { continue; } $token = $tokens[$index - 1]; if ($token->isType(DocLexer::T_NONE)) { $token->clear(); } if ($index < \count($tokens) - 1 && !Preg::match('/^\s/', $tokens[$index + 1]->getContent())) { $tokens->insertAt($index + 1, new Token(DocLexer::T_NONE, ' ')); } } } private function fixAroundAssignments(Tokens $tokens) { $beforeArguments = $this->configuration['before_argument_assignments']; $afterArguments = $this->configuration['after_argument_assignments']; $beforeArraysEquals = $this->configuration['before_array_assignments_equals']; $afterArraysEquals = $this->configuration['after_array_assignments_equals']; $beforeArraysColon = $this->configuration['before_array_assignments_colon']; $afterArraysColon = $this->configuration['after_array_assignments_colon']; $scopes = []; foreach ($tokens as $index => $token) { $endScopeType = end($scopes); if (false !== $endScopeType && $token->isType($endScopeType)) { array_pop($scopes); continue; } if ($tokens[$index]->isType(DocLexer::T_AT)) { $scopes[] = DocLexer::T_CLOSE_PARENTHESIS; continue; } if ($tokens[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) { $scopes[] = DocLexer::T_CLOSE_CURLY_BRACES; continue; } if (DocLexer::T_CLOSE_PARENTHESIS === $endScopeType && $token->isType(DocLexer::T_EQUALS)) { $this->updateSpacesAfter($tokens, $index, $afterArguments); $this->updateSpacesBefore($tokens, $index, $beforeArguments); continue; } if (DocLexer::T_CLOSE_CURLY_BRACES === $endScopeType) { if ($token->isType(DocLexer::T_EQUALS)) { $this->updateSpacesAfter($tokens, $index, $afterArraysEquals); $this->updateSpacesBefore($tokens, $index, $beforeArraysEquals); continue; } if ($token->isType(DocLexer::T_COLON)) { $this->updateSpacesAfter($tokens, $index, $afterArraysColon); $this->updateSpacesBefore($tokens, $index, $beforeArraysColon); } } } } private function updateSpacesAfter(Tokens $tokens, $index, $insert) { $this->updateSpacesAt($tokens, $index + 1, $index + 1, $insert); } private function updateSpacesBefore(Tokens $tokens, $index, $insert) { $this->updateSpacesAt($tokens, $index - 1, $index, $insert); } private function updateSpacesAt(Tokens $tokens, $index, $insertIndex, $insert) { if (null === $insert) { return; } $token = $tokens[$index]; if ($insert) { if (!$token->isType(DocLexer::T_NONE)) { $tokens->insertAt($insertIndex, $token = new Token()); } $token->setContent(' '); } elseif ($token->isType(DocLexer::T_NONE)) { $token->clear(); } } } 'with_braces'] ), ] ); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver(array_merge( parent::createConfigurationDefinition()->getOptions(), [ (new FixerOptionBuilder('syntax', 'Whether to add or remove braces.')) ->setAllowedValues(['with_braces', 'without_braces']) ->setDefault('without_braces') ->getOption(), ] )); } protected function fixAnnotations(Tokens $tokens) { if ('without_braces' === $this->configuration['syntax']) { $this->removesBracesFromAnnotations($tokens); } else { $this->addBracesToAnnotations($tokens); } } private function addBracesToAnnotations(Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$tokens[$index]->isType(DocLexer::T_AT)) { continue; } $braceIndex = $tokens->getNextMeaningfulToken($index + 1); if (null !== $braceIndex && $tokens[$braceIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { continue; } $tokens->insertAt($index + 2, new Token(DocLexer::T_OPEN_PARENTHESIS, '(')); $tokens->insertAt($index + 3, new Token(DocLexer::T_CLOSE_PARENTHESIS, ')')); } } private function removesBracesFromAnnotations(Tokens $tokens) { for ($index = 0, $max = \count($tokens); $index < $max; ++$index) { if (!$tokens[$index]->isType(DocLexer::T_AT)) { continue; } $openBraceIndex = $tokens->getNextMeaningfulToken($index + 1); if (null === $openBraceIndex) { continue; } if (!$tokens[$openBraceIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { continue; } $closeBraceIndex = $tokens->getNextMeaningfulToken($openBraceIndex); if (null === $closeBraceIndex) { continue; } if (!$tokens[$closeBraceIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) { continue; } for ($currentIndex = $index + 2; $currentIndex <= $closeBraceIndex; ++$currentIndex) { $tokens[$currentIndex]->clear(); } } } } true] ), ] ); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver(array_merge( parent::createConfigurationDefinition()->getOptions(), [ (new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ] )); } protected function fixAnnotations(Tokens $tokens) { $annotationPositions = []; for ($index = 0, $max = \count($tokens); $index < $max; ++$index) { if (!$tokens[$index]->isType(DocLexer::T_AT)) { continue; } $annotationEndIndex = $tokens->getAnnotationEnd($index); if (null === $annotationEndIndex) { return; } $annotationPositions[] = [$index, $annotationEndIndex]; $index = $annotationEndIndex; } $indentLevel = 0; foreach ($tokens as $index => $token) { if (!$token->isType(DocLexer::T_NONE) || false === strpos($token->getContent(), "\n")) { continue; } if (!$this->indentationCanBeFixed($tokens, $index, $annotationPositions)) { continue; } $braces = $this->getLineBracesCount($tokens, $index); $delta = $braces[0] - $braces[1]; $mixedBraces = 0 === $delta && $braces[0] > 0; $extraIndentLevel = 0; if ($indentLevel > 0 && ($delta < 0 || $mixedBraces)) { --$indentLevel; if ($this->configuration['indent_mixed_lines'] && $this->isClosingLineWithMeaningfulContent($tokens, $index)) { $extraIndentLevel = 1; } } $token->setContent(Preg::replace( '/(\n( +\*)?) *$/', '$1'.str_repeat(' ', 4 * ($indentLevel + $extraIndentLevel) + 1), $token->getContent() )); if ($delta > 0 || $mixedBraces) { ++$indentLevel; } } } private function getLineBracesCount(Tokens $tokens, $index) { $opening = 0; $closing = 0; while (isset($tokens[++$index])) { $token = $tokens[$index]; if ($token->isType(DocLexer::T_NONE) && false !== strpos($token->getContent(), "\n")) { break; } if ($token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_OPEN_CURLY_BRACES])) { ++$opening; continue; } if (!$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) { continue; } if ($opening > 0) { --$opening; } else { ++$closing; } } return [$opening, $closing]; } private function isClosingLineWithMeaningfulContent(Tokens $tokens, $index) { while (isset($tokens[++$index])) { $token = $tokens[$index]; if ($token->isType(DocLexer::T_NONE)) { if (false !== strpos($token->getContent(), "\n")) { return false; } continue; } return !$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES]); } return false; } private function indentationCanBeFixed(Tokens $tokens, $newLineTokenIndex, array $annotationPositions) { foreach ($annotationPositions as $position) { if ($newLineTokenIndex >= $position[0] && $newLineTokenIndex <= $position[1]) { return true; } } for ($index = $newLineTokenIndex + 1, $max = \count($tokens); $index < $max; ++$index) { $token = $tokens[$index]; if (false !== strpos($token->getContent(), "\n")) { return false; } return $tokens[$index]->isType(DocLexer::T_AT); } return false; } } ':'] ), ] ); } public function getPriority() { return 1; } protected function createConfigurationDefinition() { $options = parent::createConfigurationDefinition()->getOptions(); $operator = new FixerOptionBuilder('operator', 'The operator to use.'); $options[] = $operator ->setAllowedValues(['=', ':']) ->setDefault('=') ->getOption() ; return new FixerConfigurationResolver($options); } protected function fixAnnotations(Tokens $tokens) { $scopes = []; foreach ($tokens as $token) { if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) { $scopes[] = 'annotation'; continue; } if ($token->isType(DocLexer::T_OPEN_CURLY_BRACES)) { $scopes[] = 'array'; continue; } if ($token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) { array_pop($scopes); continue; } if ('array' === end($scopes) && $token->isType([DocLexer::T_EQUALS, DocLexer::T_COLON])) { $token->setContent($this->configuration['operator']); } } } } [\n 'baz' => true,\n ],\n];\n"), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } public function getPriority() { return -30; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($this->findArrays($tokens) as $array) { $indentLevel = 1; $scopes = [[ 'opening_braces' => $array['start_braces']['opening'], 'unindented' => false, ]]; $currentScope = 0; $arrayIndent = $this->getLineIndentation($tokens, $array['start']); $previousLineInitialIndent = $arrayIndent; $previousLineNewIndent = $arrayIndent; foreach ($array['braces'] as $index => $braces) { $currentIndentLevel = $indentLevel; if ( $braces['starts_with_closing'] && !$scopes[$currentScope]['unindented'] && !$this->isClosingLineWithMeaningfulContent($tokens, $index) ) { --$currentIndentLevel; } $token = $tokens[$index]; if ($this->newlineIsInArrayScope($tokens, $index, $array)) { $content = Preg::replace( '/(\R+)[\t ]*$/', '$1'.$arrayIndent.str_repeat($this->whitespacesConfig->getIndent(), $currentIndentLevel), $token->getContent() ); $previousLineInitialIndent = $this->extractIndent($token->getContent()); $previousLineNewIndent = $this->extractIndent($content); } else { $content = Preg::replace( '/(\R)'.preg_quote($previousLineInitialIndent, '/').'([\t ]*)$/', '$1'.$previousLineNewIndent.'$2', $token->getContent() ); } $closingBraces = $braces['closing']; while ($closingBraces-- > 0) { if (!$scopes[$currentScope]['unindented']) { --$indentLevel; $scopes[$currentScope]['unindented'] = true; } if (0 === --$scopes[$currentScope]['opening_braces']) { array_pop($scopes); --$currentScope; } } if ($braces['opening'] > 0) { $scopes[] = [ 'opening_braces' => $braces['opening'], 'unindented' => false, ]; ++$indentLevel; ++$currentScope; } $tokens[$index] = new Token([T_WHITESPACE, $content]); } } } private function findArrays(Tokens $tokens) { $arrays = []; foreach ($this->findArrayTokenRanges($tokens, 0, \count($tokens) - 1) as $arrayTokenRanges) { $array = [ 'start' => $arrayTokenRanges[0][0], 'end' => $arrayTokenRanges[\count($arrayTokenRanges) - 1][1], 'token_ranges' => $arrayTokenRanges, ]; $array['start_braces'] = $this->getLineSignificantBraces($tokens, $array['start'] - 1, $array); $array['braces'] = $this->computeArrayLineSignificantBraces($tokens, $array); $arrays[] = $array; } return $arrays; } private function findArrayTokenRanges(Tokens $tokens, $from, $to) { $arrayTokenRanges = []; $currentArray = null; for ($index = $from; $index <= $to; ++$index) { $token = $tokens[$index]; if (null !== $currentArray && $currentArray['end'] === $index) { $rangeIndexes = [$currentArray['start']]; foreach ($currentArray['ignored_tokens_ranges'] as list($start, $end)) { $rangeIndexes[] = $start - 1; $rangeIndexes[] = $end + 1; } $rangeIndexes[] = $currentArray['end']; $arrayTokenRanges[] = array_chunk($rangeIndexes, 2); foreach ($currentArray['ignored_tokens_ranges'] as list($start, $end)) { foreach ($this->findArrayTokenRanges($tokens, $start, $end) as $nestedArray) { $arrayTokenRanges[] = $nestedArray; } } $currentArray = null; continue; } if (null === $currentArray && $token->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { if ($token->isGivenKind(T_ARRAY)) { $index = $tokens->getNextTokenOfKind($index, ['(']); } $currentArray = [ 'start' => $index, 'end' => $tokens->findBlockEnd( $tokens[$index]->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index ), 'ignored_tokens_ranges' => [], ]; continue; } if ( null !== $currentArray && ( ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) || $token->equals('{') ) ) { $endIndex = $tokens->findBlockEnd( $token->equals('{') ? Tokens::BLOCK_TYPE_CURLY_BRACE : Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index ); $currentArray['ignored_tokens_ranges'][] = [$index, $endIndex]; $index = $endIndex; continue; } } return $arrayTokenRanges; } private function computeArrayLineSignificantBraces(Tokens $tokens, array $array) { $braces = []; for ($index = $array['start']; $index <= $array['end']; ++$index) { if (!$this->isNewLineToken($tokens, $index)) { continue; } $braces[$index] = $this->getLineSignificantBraces($tokens, $index, $array); } return $braces; } private function getLineSignificantBraces(Tokens $tokens, $index, array $array) { $deltas = []; for (++$index; $index <= $array['end']; ++$index) { if ($this->isNewLineToken($tokens, $index)) { break; } if (!$this->indexIsInArrayTokenRanges($index, $array)) { continue; } $token = $tokens[$index]; if ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) { continue; } if ($token->equals(')')) { $openBraceIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); if (!$tokens[$tokens->getPrevMeaningfulToken($openBraceIndex)]->isGivenKind(T_ARRAY)) { continue; } } if ($token->equalsAny(['(', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) { $deltas[] = 1; continue; } if ($token->equalsAny([')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) { $deltas[] = -1; } } $braces = [ 'opening' => 0, 'closing' => 0, 'starts_with_closing' => -1 === reset($deltas), ]; foreach ($deltas as $delta) { if (1 === $delta) { ++$braces['opening']; } elseif ($braces['opening'] > 0) { --$braces['opening']; } else { ++$braces['closing']; } } return $braces; } private function isClosingLineWithMeaningfulContent(Tokens $tokens, $newLineIndex) { $nextMeaningfulIndex = $tokens->getNextMeaningfulToken($newLineIndex); return !$tokens[$nextMeaningfulIndex]->equalsAny([')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]]); } private function getLineIndentation(Tokens $tokens, $index) { $newlineTokenIndex = $this->getPreviousNewlineTokenIndex($tokens, $index); if (null === $newlineTokenIndex) { return ''; } return $this->extractIndent($this->computeNewLineContent($tokens, $newlineTokenIndex)); } private function extractIndent($content) { if (Preg::match('/\R([\t ]*)[^\r\n]*$/D', $content, $matches)) { return $matches[1]; } return ''; } private function getPreviousNewlineTokenIndex(Tokens $tokens, $index) { while ($index > 0) { $index = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE], [T_INLINE_HTML]]); if (null === $index) { break; } if ($this->isNewLineToken($tokens, $index)) { return $index; } } return null; } private function newlineIsInArrayScope(Tokens $tokens, $index, array $array) { if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny(['.', '?', ':'])) { return false; } $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if ($nextToken->isGivenKind(T_OBJECT_OPERATOR) || $nextToken->equalsAny(['.', '?', ':'])) { return false; } return $this->indexIsInArrayTokenRanges($index, $array); } private function indexIsInArrayTokenRanges($index, array $array) { foreach ($array['token_ranges'] as list($start, $end)) { if ($index < $start) { return false; } if ($index <= $end) { return true; } } return false; } private function isNewLineToken(Tokens $tokens, $index) { if (!$tokens[$index]->equalsAny([[T_WHITESPACE], [T_INLINE_HTML]])) { return false; } return (bool) Preg::match('/\R/', $this->computeNewLineContent($tokens, $index)); } private function computeNewLineContent(Tokens $tokens, $index) { $content = $tokens[$index]->getContent(); if (0 !== $index && $tokens[$index - 1]->equalsAny([[T_OPEN_TAG], [T_CLOSE_TAG]])) { $content = Preg::replace('/\S/', '', $tokens[$index - 1]->getContent()).$content; } return $content; } } ['inside']]), new CodeSample(" ['outside']]), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]])) { continue; } if (\in_array('inside', $this->configuration['positions'], true)) { if ($token->equals('[')) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); } else { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $index); } if ($tokens[$index + 1]->isWhitespace(" \t")) { $tokens->clearAt($index + 1); } if ($tokens[$endIndex - 1]->isWhitespace(" \t")) { $tokens->clearAt($endIndex - 1); } } if (\in_array('outside', $this->configuration['positions'], true)) { $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($index); if ($tokens[$prevNonWhitespaceIndex]->isComment()) { continue; } $tokens->removeLeadingWhitespace($index); } } } protected function createConfigurationDefinition() { $values = ['inside', 'outside']; return new FixerConfigurationResolverRootless('positions', [ (new FixerOptionBuilder('positions', 'Whether spacing should be fixed inside and/or outside the offset braces.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset($values)]) ->setDefault($values) ->getOption(), ], $this->getName()); } } = 7.3.', [ new VersionSpecificCodeSample( <<<'SAMPLE' = 70300 && $tokens->isTokenKindFound(T_START_HEREDOC); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { if (!$tokens[$index]->isGivenKind(T_END_HEREDOC)) { continue; } $end = $index; $index = $tokens->getPrevTokenOfKind($index, [[T_START_HEREDOC]]); $this->fixIndentation($tokens, $index, $end); } } private function fixIndentation(Tokens $tokens, $start, $end) { $indent = $this->getIndentAt($tokens, $start).$this->whitespacesConfig->getIndent(); Preg::match('/^[ \t]*/', $tokens[$end]->getContent(), $matches); $currentIndent = $matches[0]; $content = $indent.substr($tokens[$end]->getContent(), \strlen($currentIndent)); $tokens[$end] = new Token([T_END_HEREDOC, $content]); if ($end === $start + 1) { return; } for ($index = $end - 1, $last = true; $index > $start; --$index, $last = false) { if (!$tokens[$index]->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_WHITESPACE])) { continue; } $regexEnd = $last && !$currentIndent ? '(?!$)' : ''; $content = Preg::replace('/(?<=\v)'.$currentIndent.$regexEnd.'/', $indent, $tokens[$index]->getContent()); $tokens[$index] = new Token([$tokens[$index]->getId(), $content]); } ++$index; if ($tokens[$index]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { $content = $indent.substr($tokens[$index]->getContent(), \strlen($currentIndent)); $tokens[$index] = new Token([T_ENCAPSED_AND_WHITESPACE, $content]); } else { $tokens->insertAt($index, new Token([T_ENCAPSED_AND_WHITESPACE, $indent])); } } private function getIndentAt(Tokens $tokens, $index) { for (; $index >= 0; --$index) { if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML, T_OPEN_TAG])) { continue; } $content = $tokens[$index]->getContent(); if ($tokens[$index]->isWhitespace() && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { $content = $tokens[$index - 1]->getContent().$content; } if (1 === Preg::match('/\R([ \t]*)$/', $content, $matches)) { return $matches[1]; } } return ''; } } = 0; --$index) { $token = $tokens[$index]; if ( $token->isGivenKind(T_OPEN_TAG) && $tokens->offsetExists($index + 1) && $tokens[$index + 1]->isWhitespace() && 1 === Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches) && 1 === Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches) ) { $tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]); if ('' === $whitespaceMatches[2]) { $tokens->clearAt($index + 1); } else { $tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaceMatches[2]]); } continue; } if (!$token->isWhitespace()) { continue; } $lines = Preg::split('/(\\R+)/', $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); $linesSize = \count($lines); if ($linesSize > 1 || !isset($tokens[$index + 1])) { if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || 1 !== Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) { $lines[0] = rtrim($lines[0], " \t"); } for ($i = 1; $i < $linesSize; ++$i) { $trimmedLine = rtrim($lines[$i], " \t"); if ('' !== $trimmedLine) { $lines[$i] = $trimmedLine; } } $content = implode('', $lines); if ('' !== $content) { $tokens[$index] = new Token([$token->getId(), $content]); } else { $tokens->clearAt($index); } } } } } setEmail('voff.web@gmail.com')\n ->setPassword('233434');\n")] ); } public function getPriority() { return -29; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { continue; } if ($this->canBeMovedToNextLine($index, $tokens)) { $newline = new Token([T_WHITESPACE, $lineEnding]); if ($tokens[$index - 1]->isWhitespace()) { $tokens[$index - 1] = $newline; } else { $tokens->insertAt($index, $newline); ++$index; } } $currentIndent = $this->getIndentAt($tokens, $index - 1); if (null === $currentIndent) { continue; } $expectedIndent = $this->getExpectedIndentAt($tokens, $index); if ($currentIndent !== $expectedIndent) { $tokens[$index - 1] = new Token([T_WHITESPACE, $lineEnding.$expectedIndent]); } } } private function getExpectedIndentAt(Tokens $tokens, $index) { $index = $tokens->getPrevMeaningfulToken($index); $indent = $this->whitespacesConfig->getIndent(); for ($i = $index; $i >= 0; --$i) { if ($tokens[$i]->equals(')')) { $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i); } $currentIndent = $this->getIndentAt($tokens, $i); if (null === $currentIndent) { continue; } if ($this->currentLineRequiresExtraIndentLevel($tokens, $i, $index)) { return $currentIndent.$indent; } return $currentIndent; } return $indent; } private function canBeMovedToNextLine($index, Tokens $tokens) { $prevMeaningful = $tokens->getPrevMeaningfulToken($index); $hasCommentBefore = false; for ($i = $index - 1; $i > $prevMeaningful; --$i) { if ($tokens[$i]->isComment()) { $hasCommentBefore = true; continue; } if ($tokens[$i]->isWhitespace() && 1 === Preg::match('/\R/', $tokens[$i]->getContent())) { return $hasCommentBefore; } } return false; } private function getIndentAt(Tokens $tokens, $index) { if (1 === Preg::match('/\R{1}([ \t]*)$/', $this->getIndentContentAt($tokens, $index), $matches)) { return $matches[1]; } return null; } private function getIndentContentAt(Tokens $tokens, $index) { for ($i = $index; $i >= 0; --$i) { if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML])) { continue; } $content = $tokens[$index]->getContent(); if ($tokens[$index]->isWhitespace() && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { $content = $tokens[$index - 1]->getContent().$content; } if (Preg::match('/\R/', $content)) { return $content; } } return ''; } private function currentLineRequiresExtraIndentLevel(Tokens $tokens, $start, $end) { if ($tokens[$start + 1]->isGivenKind(T_OBJECT_OPERATOR)) { return false; } if ($tokens[$end]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_CLOSE)) { return true; } return !$tokens[$end]->equals(')') || $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end) >= $start ; } } whitespacesConfig->getLineEnding(); for ($index = 0, $count = \count($tokens); $index < $count; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_END_HEREDOC)) { $tokens[$index] = new Token([ $token->getId(), Preg::replace( "#\r\n|\n#", $ending, $token->getContent() ), ]); } continue; } if ($token->isGivenKind([T_OPEN_TAG, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_START_HEREDOC])) { $tokens[$index] = new Token([ $token->getId(), Preg::replace( "#\r\n|\n#", $ending, $token->getContent() ), ]); } } } } = 70100 && $tokens->isTokenKindFound(CT::T_NULLABLE_TYPE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $typehintKinds = [ CT::T_ARRAY_TYPEHINT, T_CALLABLE, T_NS_SEPARATOR, T_STRING, ]; for ($index = $tokens->count() - 1; $index >= 0; --$index) { if (!$tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { continue; } if ( $tokens[$index + 1]->isWhitespace() && $tokens[$index + 2]->isGivenKind($typehintKinds) ) { $tokens->removeTrailingWhitespace($index); } } } } isWhitespace()) { $this->fixWhitespaceToken($tokens, $i); } } } private function fixWhitespaceToken(Tokens $tokens, $index) { $content = $tokens[$index]->getContent(); $lines = Preg::split("/(\r\n|\n)/", $content); $lineCount = \count($lines); if ( $lineCount > 2 || ($lineCount > 0 && (!isset($tokens[$index + 1]) || $tokens[$index - 1]->isGivenKind(T_OPEN_TAG))) ) { $lMax = isset($tokens[$index + 1]) ? $lineCount - 1 : $lineCount; $lStart = 1; if ($tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && "\n" === substr($tokens[$index - 1]->getContent(), -1)) { $lStart = 0; } for ($l = $lStart; $l < $lMax; ++$l) { $lines[$l] = Preg::replace('/^\h+$/', '', $lines[$l]); } $content = implode($this->whitespacesConfig->getLineEnding(), $lines); if ('' !== $content) { $tokens[$index] = new Token([T_WHITESPACE, $content]); } else { $tokens->clearAt($index); } } } } T_BREAK, 'case' => T_CASE, 'continue' => T_CONTINUE, 'curly_brace_block' => '{', 'default' => T_DEFAULT, 'extra' => T_WHITESPACE, 'parenthesis_brace_block' => '(', 'return' => T_RETURN, 'square_brace_block' => CT::T_ARRAY_SQUARE_BRACE_OPEN, 'switch' => T_SWITCH, 'throw' => T_THROW, 'use' => T_USE, 'use_trait' => CT::T_USE_TRAIT, ]; static $tokenKindCallbackMap = [ T_BREAK => 'fixAfterToken', T_CASE => 'fixAfterToken', T_CONTINUE => 'fixAfterToken', T_DEFAULT => 'fixAfterToken', T_RETURN => 'fixAfterToken', T_SWITCH => 'fixAfterToken', T_THROW => 'fixAfterToken', T_USE => 'removeBetweenUse', T_WHITESPACE => 'removeMultipleBlankLines', CT::T_USE_TRAIT => 'removeBetweenUse', CT::T_ARRAY_SQUARE_BRACE_OPEN => 'fixStructureOpenCloseIfMultiLine', ]; static $tokenEqualsMap = [ '{' => 'fixStructureOpenCloseIfMultiLine', '(' => 'fixStructureOpenCloseIfMultiLine', ]; $tokensAssoc = array_flip(array_intersect_key($reprToTokenMap, array_flip($this->configuration['tokens']))); $this->tokenKindCallbackMap = array_intersect_key($tokenKindCallbackMap, $tokensAssoc); $this->tokenEqualsMap = array_intersect_key($tokenEqualsMap, $tokensAssoc); } public function getDefinition() { return new FixerDefinition( 'Removes extra blank lines and/or blank lines following configuration.', [ new CodeSample( ' ['break']] ), new CodeSample( ' ['continue']] ), new CodeSample( ' ['curly_brace_block']] ), new CodeSample( ' ['extra']] ), new CodeSample( ' ['parenthesis_brace_block']] ), new CodeSample( ' ['return']] ), new CodeSample( ' ['square_brace_block']] ), new CodeSample( ' ['throw']] ), new CodeSample( ' ['use']] ), new CodeSample( ' ['use_trait']] ), new CodeSample( ' ['switch', 'case', 'default']] ), ] ); } public function getPriority() { return -20; } public function isCandidate(Tokens $tokens) { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->tokens = $tokens; $this->tokensAnalyzer = new TokensAnalyzer($this->tokens); for ($index = $tokens->getSize() - 1; $index > 0; --$index) { $this->fixByToken($tokens[$index], $index); } } protected function createConfigurationDefinition() { $that = $this; return new FixerConfigurationResolverRootless('tokens', [ (new FixerOptionBuilder('tokens', 'List of tokens to fix.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(self::$availableTokens)]) ->setNormalizer(static function (Options $options, $tokens) use ($that) { foreach ($tokens as &$token) { if ('useTrait' === $token) { $message = "Token \"useTrait\" in option \"tokens\" for rule \"{$that->getName()}\" is deprecated and will be removed in 3.0, use \"use_trait\" instead."; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new InvalidConfigurationException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); $token = 'use_trait'; break; } } return $tokens; }) ->setDefault(['extra']) ->getOption(), ], $this->getName()); } private function fixByToken(Token $token, $index) { foreach ($this->tokenKindCallbackMap as $kind => $callback) { if (!$token->isGivenKind($kind)) { continue; } $this->{$callback}($index); return; } foreach ($this->tokenEqualsMap as $equals => $callback) { if (!$token->equals($equals)) { continue; } $this->{$callback}($index); return; } } private function removeBetweenUse($index) { $next = $this->tokens->getNextTokenOfKind($index, [';', T_CLOSE_TAG]); if (null === $next || $this->tokens[$next]->isGivenKind(T_CLOSE_TAG)) { return; } $nextUseCandidate = $this->tokens->getNextMeaningfulToken($next); if (null === $nextUseCandidate || 1 === $nextUseCandidate - $next || !$this->tokens[$nextUseCandidate]->isGivenKind($this->tokens[$index]->getId())) { return; } return $this->removeEmptyLinesAfterLineWithTokenAt($next); } private function removeMultipleBlankLines($index) { $expected = $this->tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && 1 === Preg::match('/\R$/', $this->tokens[$index - 1]->getContent()) ? 1 : 2; $parts = Preg::split('/(.*\R)/', $this->tokens[$index]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $count = \count($parts); if ($count > $expected) { $this->tokens[$index] = new Token([T_WHITESPACE, implode('', \array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]); } } private function fixAfterToken($index) { for ($i = $index - 1; $i > 0; --$i) { if ($this->tokens[$i]->isGivenKind(T_FUNCTION) && $this->tokensAnalyzer->isLambda($i)) { return; } if ($this->tokens[$i]->isGivenKind(T_CLASS) && $this->tokensAnalyzer->isAnonymousClass($i)) { return; } if ($this->tokens[$i]->isWhitespace() && false !== strpos($this->tokens[$i]->getContent(), "\n")) { break; } } $this->removeEmptyLinesAfterLineWithTokenAt($index); } private function fixStructureOpenCloseIfMultiLine($index) { $blockTypeInfo = Tokens::detectBlockType($this->tokens[$index]); $bodyEnd = $this->tokens->findBlockEnd($blockTypeInfo['type'], $index); for ($i = $bodyEnd - 1; $i >= $index; --$i) { if (false !== strpos($this->tokens[$i]->getContent(), "\n")) { $this->removeEmptyLinesAfterLineWithTokenAt($i); $this->removeEmptyLinesAfterLineWithTokenAt($index); break; } } } private function removeEmptyLinesAfterLineWithTokenAt($index) { $tokenCount = \count($this->tokens); for ($end = $index; $end < $tokenCount; ++$end) { if ( $this->tokens[$end]->equals('}') || false !== strpos($this->tokens[$end]->getContent(), "\n") ) { break; } } if ($end === $tokenCount) { return; } $ending = $this->whitespacesConfig->getLineEnding(); for ($i = $end; $i < $tokenCount && $this->tokens[$i]->isWhitespace(); ++$i) { $content = $this->tokens[$i]->getContent(); if (substr_count($content, "\n") < 1) { continue; } $pos = strrpos($content, "\n"); if ($pos + 2 <= \strlen($content)) { $newContent = $ending.substr($content, $pos + 1); } else { $newContent = $ending; } $this->tokens[$i] = new Token([T_WHITESPACE, $newContent]); } } } T_BREAK, 'case' => T_CASE, 'continue' => T_CONTINUE, 'declare' => T_DECLARE, 'default' => T_DEFAULT, 'die' => T_EXIT, 'do' => T_DO, 'exit' => T_EXIT, 'for' => T_FOR, 'foreach' => T_FOREACH, 'goto' => T_GOTO, 'if' => T_IF, 'include' => T_INCLUDE, 'include_once' => T_INCLUDE_ONCE, 'require' => T_REQUIRE, 'require_once' => T_REQUIRE_ONCE, 'return' => T_RETURN, 'switch' => T_SWITCH, 'throw' => T_THROW, 'try' => T_TRY, 'while' => T_WHILE, 'yield' => T_YIELD, ]; private $fixTokenMap = []; public function configure(array $configuration = null) { parent::configure($configuration); $this->fixTokenMap = []; foreach ($this->configuration['statements'] as $key) { $this->fixTokenMap[$key] = self::$tokenMap[$key]; } } public function getDefinition() { return new FixerDefinition( 'An empty line feed must precede any configured statement.', [ new CodeSample( 'process(); break; case 44: break; } ', [ 'statements' => ['break'], ] ), new CodeSample( 'isTired()) { $bar->sleep(); continue; } } ', [ 'statements' => ['continue'], ] ), new CodeSample( ' ['die'], ] ), new CodeSample( ' 0); ', [ 'statements' => ['do'], ] ), new CodeSample( ' ['exit'], ] ), new CodeSample( ' ['goto'], ] ), new CodeSample( ' ['if'], ] ), new CodeSample( ' ['return'], ] ), new CodeSample( ' ['switch'], ] ), new CodeSample( 'bar(); throw new \UnexpectedValueException("A cannot be null"); } ', [ 'statements' => ['throw'], ] ), new CodeSample( 'bar(); } catch (\Exception $exception) { $a = -1; } ', [ 'statements' => ['try'], ] ), new CodeSample( ' ['yield'], ] ), ] ); } public function getPriority() { return -21; } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(array_values($this->fixTokenMap)); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); $tokenKinds = array_values($this->fixTokenMap); $analyzer = new TokensAnalyzer($tokens); for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind($tokenKinds) || ($token->isGivenKind(T_WHILE) && $analyzer->isWhilePartOfDoWhile($index))) { continue; } $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; if (!$prevNonWhitespaceToken->equalsAny([';', '}'])) { continue; } $prevIndex = $index - 1; $prevToken = $tokens[$prevIndex]; if ($prevToken->isWhitespace()) { $countParts = substr_count($prevToken->getContent(), "\n"); if (0 === $countParts) { $tokens[$prevIndex] = new Token([T_WHITESPACE, rtrim($prevToken->getContent(), " \t").$lineEnding.$lineEnding]); } elseif (1 === $countParts) { $tokens[$prevIndex] = new Token([T_WHITESPACE, $lineEnding.$prevToken->getContent()]); } } else { $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding.$lineEnding])); ++$index; ++$limit; } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('statements', 'List of statements which must be preceded by an empty line.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(array_keys(self::$tokenMap))]) ->setDefault([ 'break', 'continue', 'declare', 'return', 'throw', 'try', ]) ->getOption(), ]); } } count(); if ($count && !$tokens[$count - 1]->isGivenKind([T_INLINE_HTML, T_CLOSE_TAG, T_OPEN_TAG])) { $tokens->ensureWhitespaceAtIndex($count - 1, 1, $this->whitespacesConfig->getLineEnding()); } } } isTokenKindFound('('); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->equals('(')) { continue; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ARRAY)) { continue; } $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); if (!$tokens[$tokens->getNextNonWhitespace($index)]->isComment()) { $this->removeSpaceAroundToken($tokens, $index + 1); } if (!$tokens[$tokens->getPrevMeaningfulToken($endIndex)]->equals(',')) { $this->removeSpaceAroundToken($tokens, $endIndex - 1); } } } private function removeSpaceAroundToken(Tokens $tokens, $index) { $token = $tokens[$index]; if ($token->isWhitespace() && false === strpos($token->getContent(), "\n")) { $tokens->clearAt($index); } } } isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT, T_WHITESPACE]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->indent = $this->whitespacesConfig->getIndent(); foreach ($tokens as $index => $token) { if ($token->isComment()) { $tokens[$index] = $this->fixIndentInComment($tokens, $index); continue; } if ($token->isWhitespace()) { $tokens[$index] = $this->fixIndentToken($tokens, $index); continue; } } } private function fixIndentInComment(Tokens $tokens, $index) { $content = Preg::replace('/^(?:(?getContent(), -1, $count); while (0 !== $count) { $content = Preg::replace('/^(\ +)?\t/m', '\1 ', $content, -1, $count); } $indent = $this->indent; $content = Preg::replaceCallback('/^(?: )+/m', static function ($matches) use ($indent) { return str_replace(' ', $indent, $matches[0]); }, $content); return new Token([$tokens[$index]->getId(), $content]); } private function fixIndentToken(Tokens $tokens, $index) { $content = $tokens[$index]->getContent(); $previousTokenHasTrailingLinebreak = false; if (false !== strpos($tokens[$index - 1]->getContent(), "\n")) { $content = "\n".$content; $previousTokenHasTrailingLinebreak = true; } $indent = $this->indent; $newContent = Preg::replaceCallback( '/(\R)(\h+)/', static function (array $matches) use ($indent) { $content = Preg::replace('/(?:(?getFixer()->getDefinition(); } public function configure(array $configuration = null) { $this->getFixer()->configure($configuration); $this->configuration = $configuration; } public function getConfigurationDefinition() { return $this->getFixer()->getConfigurationDefinition(); } public function getSuccessorsNames() { return array_keys($this->proxyFixers); } protected function createProxyFixers() { return [$this->getFixer()]; } private function getFixer() { if (null === $this->fixer) { $this->fixer = new NoExtraBlankLinesFixer(); } return $this->fixer; } } generateCode(); $newContent = Preg::replace('/<\?(?:phP|pHp|pHP|Php|PhP|PHp|PHP)?(\s|$)/', ' $token) { if ($token->isGivenKind(T_OPEN_TAG)) { $tokenContent = $token->getContent(); if ('isGivenKind([T_COMMENT, T_DOC_COMMENT, T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_STRING])) { $tokenContent = ''; $tokenContentLength = 0; $parts = explode('getContent()); $iLast = \count($parts) - 1; foreach ($parts as $i => $part) { $tokenContent .= $part; $tokenContentLength += \strlen($part); if ($i !== $iLast) { $originalTokenContent = substr($content, $tokensOldContentLength + $tokenContentLength, 5); if ('getId(), $tokenContent]); $token = $tokens[$index]; } $tokensOldContent .= $token->getContent(); $tokensOldContentLength += \strlen($token->getContent()); } $tokensOrg->overrideRange(0, $tokensOrg->count() - 1, $tokens); } } isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $i = \count($tokens); while ($i--) { $token = $tokens[$i]; if (!$token->isGivenKind(T_OPEN_TAG_WITH_ECHO)) { continue; } $nextIndex = $i + 1; $tokens[$i] = new Token([T_OPEN_TAG, 'isWhitespace()) { $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, ' '])); } $tokens->insertAt($nextIndex, new Token([T_ECHO, 'echo'])); } } } isTokenKindFound(T_OPEN_TAG); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) { return; } $newlineFound = false; foreach ($tokens as $token) { if ($token->isWhitespace() && false !== strpos($token->getContent(), "\n")) { $newlineFound = true; break; } } if (!$newlineFound) { return; } $token = $tokens[0]; $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$this->whitespacesConfig->getLineEnding()]); } } ` tag MUST be omitted from files containing only PHP.', [new CodeSample("\n")] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_CLOSE_TAG); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if (\count($tokens) < 2 || !$tokens->isMonolithicPhp()) { return; } if (!$tokens->isTokenKindFound(T_CLOSE_TAG)) { return; } $closeTags = $tokens->findGivenKind(T_CLOSE_TAG); $index = key($closeTags); if (isset($tokens[$index - 1]) && $tokens[$index - 1]->isWhitespace()) { $tokens->clearAt($index - 1); } $tokens->clearAt($index); $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG]])) { $tokens->insertAt($prevIndex + 1, new Token(';')); } } } isTokenKindFound(T_OPEN_TAG); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) { return; } $newlineFound = false; foreach ($tokens as $token) { if ($token->isWhitespace() && false !== strpos($token->getContent(), "\n")) { $newlineFound = true; break; } } if (!$newlineFound) { return; } $token = $tokens[0]; if (false === strpos($token->getContent(), "\n")) { $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]); } if (!$tokens[1]->isWhitespace() && false === strpos($tokens[1]->getContent(), "\n")) { $tokens->insertAt(1, new Token([T_WHITESPACE, $lineEnding])); } } } proxyFixers); } protected function createProxyFixers() { $fixer = new BlankLineBeforeStatementFixer(); $fixer->configure(['statements' => ['return']]); return [$fixer]; } } isAllTokenKindsFound([T_FUNCTION, T_RETURN, T_VARIABLE]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokenCount = \count($tokens); for ($index = 1; $index < $tokenCount; ++$index) { if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { continue; } $functionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); if ($tokens[$functionOpenIndex]->equals(';')) { $index = $functionOpenIndex - 1; continue; } $functionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $functionOpenIndex); $tokensAdded = $this->fixFunction( $tokens, $index, $functionOpenIndex, $functionCloseIndex ); $index = $functionCloseIndex + $tokensAdded; $tokenCount += $tokensAdded; } } private function fixFunction(Tokens $tokens, $functionIndex, $functionOpenIndex, $functionCloseIndex) { static $riskyKinds = [ CT::T_DYNAMIC_VAR_BRACE_OPEN, T_EVAL, T_GLOBAL, T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, T_REQUIRE_ONCE, T_STATIC, ]; $inserted = 0; $candidates = []; $isRisky = false; for ($index = $functionIndex + 1; $index < $functionOpenIndex; ++$index) { if ($tokens[$index]->equals('&')) { $isRisky = true; break; } } for ($index = $functionOpenIndex + 1; $index < $functionCloseIndex; ++$index) { if ($tokens[$index]->isGivenKind(T_FUNCTION)) { $nestedFunctionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); if ($tokens[$nestedFunctionOpenIndex]->equals(';')) { $index = $nestedFunctionOpenIndex - 1; continue; } $nestedFunctionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nestedFunctionOpenIndex); $tokensAdded = $this->fixFunction( $tokens, $index, $nestedFunctionOpenIndex, $nestedFunctionCloseIndex ); $index = $nestedFunctionCloseIndex + $tokensAdded; $functionCloseIndex += $tokensAdded; $inserted += $tokensAdded; } if ($isRisky) { continue; } if ($tokens[$index]->equals('&')) { $isRisky = true; continue; } if ($tokens[$index]->isGivenKind(T_RETURN)) { $candidates[] = $index; continue; } if ($tokens[$index]->isGivenKind($riskyKinds)) { $isRisky = true; continue; } if ($tokens[$index]->equals('$')) { $nextIndex = $tokens->getNextMeaningfulToken($index); if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { $isRisky = true; continue; } } if ($this->isSuperGlobal($tokens[$index])) { $isRisky = true; continue; } } if ($isRisky) { return $inserted; } for ($i = \count($candidates) - 1; $i >= 0; --$i) { $index = $candidates[$i]; $returnVarIndex = $tokens->getNextMeaningfulToken($index); if (!$tokens[$returnVarIndex]->isGivenKind(T_VARIABLE)) { continue; } $endReturnVarIndex = $tokens->getNextMeaningfulToken($returnVarIndex); if (!$tokens[$endReturnVarIndex]->equalsAny([';', [T_CLOSE_TAG]])) { continue; } $assignVarEndIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$assignVarEndIndex]->equals(';')) { continue; } $assignVarOperatorIndex = $tokens->getPrevTokenOfKind( $assignVarEndIndex, ['=', ';', '{', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]] ); if (null === $assignVarOperatorIndex || !$tokens[$assignVarOperatorIndex]->equals('=')) { continue; } $assignVarIndex = $tokens->getPrevMeaningfulToken($assignVarOperatorIndex); if (!$tokens[$assignVarIndex]->equals($tokens[$returnVarIndex], false)) { continue; } $beforeAssignVarIndex = $tokens->getPrevMeaningfulToken($assignVarIndex); if (!$tokens[$beforeAssignVarIndex]->equalsAny([';', '{', '}'])) { continue; } $inserted += $this->simplifyReturnStatement( $tokens, $assignVarIndex, $assignVarOperatorIndex, $index, $endReturnVarIndex ); } return $inserted; } private function simplifyReturnStatement( Tokens $tokens, $assignVarIndex, $assignVarOperatorIndex, $returnIndex, $returnVarEndIndex ) { $inserted = 0; $originalIndent = $tokens[$assignVarIndex - 1]->isWhitespace() ? $tokens[$assignVarIndex - 1]->getContent() : null ; if ($tokens[$returnVarEndIndex]->equals(';')) { $tokens->clearTokenAndMergeSurroundingWhitespace($returnVarEndIndex); } for ($i = $returnIndex; $i <= $returnVarEndIndex - 1; ++$i) { $this->clearIfSave($tokens, $i); } if ($tokens[$returnIndex - 1]->isWhitespace()) { $content = $tokens[$returnIndex - 1]->getContent(); $fistLinebreakPos = strrpos($content, "\n"); $content = false === $fistLinebreakPos ? ' ' : substr($content, $fistLinebreakPos) ; $tokens[$returnIndex - 1] = new Token([T_WHITESPACE, $content]); } for ($i = $assignVarIndex; $i <= $assignVarOperatorIndex; ++$i) { $this->clearIfSave($tokens, $i); } $tokens->insertAt($assignVarIndex, new Token([T_RETURN, 'return'])); ++$inserted; if ( null !== $originalIndent && $tokens[$assignVarIndex - 1]->isWhitespace() && $originalIndent !== $tokens[$assignVarIndex - 1]->getContent() ) { $tokens[$assignVarIndex - 1] = new Token([T_WHITESPACE, $originalIndent]); } $nextIndex = $tokens->getNonEmptySibling($assignVarIndex, 1); if (!$tokens[$nextIndex]->isWhitespace()) { $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, ' '])); ++$inserted; } return $inserted; } private function clearIfSave(Tokens $tokens, $index) { if ($tokens[$index]->isComment()) { return; } if ($tokens[$index]->isWhitespace() && $tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { return; } $tokens->clearTokenAndMergeSurroundingWhitespace($index); } private function isSuperGlobal(Token $token) { static $superNames = [ '$_COOKIE' => true, '$_ENV' => true, '$_FILES' => true, '$_GET' => true, '$_POST' => true, '$_REQUEST' => true, '$_SERVER' => true, '$_SESSION' => true, '$GLOBALS' => true, ]; if (!$token->isGivenKind(T_VARIABLE)) { return false; } return isset($superNames[strtoupper($token->getContent())]); } } isAllTokenKindsFound([T_FUNCTION, T_RETURN]); } public function getDefinition() { return new FixerDefinition( 'There should not be an empty `return` statement at the end of a function.', [ new CodeSample( ' $token) { if (!$token->isGivenKind(T_FUNCTION)) { continue; } $index = $tokens->getNextTokenOfKind($index, [';', '{']); if ($tokens[$index]->equals('{')) { $this->fixFunction($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); } } } private function fixFunction(Tokens $tokens, $start, $end) { for ($index = $end; $index > $start; --$index) { if (!$tokens[$index]->isGivenKind(T_RETURN)) { continue; } $nextAt = $tokens->getNextMeaningfulToken($index); if (!$tokens[$nextAt]->equals(';')) { continue; } if ($tokens->getNextMeaningfulToken($nextAt) !== $end) { continue; } $previous = $tokens->getPrevMeaningfulToken($index); if ($tokens[$previous]->equalsAny([[T_ELSE], ')'])) { continue; } $tokens->clearTokenAndMergeSurroundingWhitespace($index); $tokens->clearTokenAndMergeSurroundingWhitespace($nextAt); } } } isTokenKindFound(T_RETURN); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_RETURN)) { continue; } if ($this->needFixing($tokens, $index)) { $this->clear($tokens, $index); } } } private function clear(Tokens $tokens, $index) { while (!$tokens[++$index]->equals(';')) { if ($this->shouldClearToken($tokens, $index)) { $tokens->clearAt($index); } } } private function needFixing(Tokens $tokens, $index) { if ($this->isStrictOrNullableReturnTypeFunction($tokens, $index)) { return false; } $content = ''; while (!$tokens[$index]->equals(';')) { $index = $tokens->getNextMeaningfulToken($index); $content .= $tokens[$index]->getContent(); } $content = ltrim($content, '('); $content = rtrim($content, ');'); return 'null' === strtolower($content); } private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, $returnIndex) { $functionIndex = $returnIndex; do { $functionIndex = $tokens->getPrevTokenOfKind($functionIndex, [[T_FUNCTION]]); if (null === $functionIndex) { return false; } $openingCurlyBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['{']); $closingCurlyBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingCurlyBraceIndex); } while ($closingCurlyBraceIndex < $returnIndex); $possibleVoidIndex = $tokens->getPrevMeaningfulToken($openingCurlyBraceIndex); $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind(T_STRING) && 'void' !== $tokens[$possibleVoidIndex]->getContent(); $nullableTypeIndex = $tokens->getNextTokenOfKind($functionIndex, [[CT::T_NULLABLE_TYPE]]); $isNullableReturnType = null !== $nullableTypeIndex && $nullableTypeIndex < $openingCurlyBraceIndex; return $isStrictReturnType || $isNullableReturnType; } private function shouldClearToken(Tokens $tokens, $index) { $token = $tokens[$index]; return !$token->isComment() && !($token->isWhitespace() && $tokens[$index + 1]->isComment()); } } isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $isInNamespace = false; $isImported = false; for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_NAMESPACE)) { $isInNamespace = true; continue; } if ($token->isGivenKind(T_USE) && $isInNamespace) { $nextIndex = $tokens->getNextMeaningfulToken($index); if ('datetime' !== strtolower($tokens[$nextIndex]->getContent())) { continue; } $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); if ($tokens[$nextNextIndex]->equals(';')) { $isImported = true; } $index = $nextNextIndex; continue; } if (!$token->isGivenKind(T_STRING)) { continue; } $lowercaseContent = strtolower($token->getContent()); if ('datetime' === $lowercaseContent) { $this->fixClassUsage($tokens, $index, $isInNamespace, $isImported); $limit = $tokens->count(); } elseif ('date_create' === $lowercaseContent) { $this->fixFunctionUsage($tokens, $index, 'date_create_immutable'); } elseif ('date_create_from_format' === $lowercaseContent) { $this->fixFunctionUsage($tokens, $index, 'date_create_immutable_from_format'); } } } private function fixClassUsage(Tokens $tokens, $index, $isInNamespace, $isImported) { $nextIndex = $tokens->getNextMeaningfulToken($index); if ($tokens[$nextIndex]->isGivenKind(T_DOUBLE_COLON)) { $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); if ($tokens[$nextNextIndex]->isGivenKind(T_STRING)) { $nextNextNextIndex = $tokens->getNextMeaningfulToken($nextNextIndex); if (!$tokens[$nextNextNextIndex]->equals('(')) { return; } } } $isUsedAlone = false; $isUsedWithLeadingBackslash = false; $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if (!$tokens[$prevPrevIndex]->isGivenKind(T_STRING)) { $isUsedWithLeadingBackslash = true; } } elseif (!$tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_OBJECT_OPERATOR])) { $isUsedAlone = true; } if ($isUsedWithLeadingBackslash || $isUsedAlone && ($isInNamespace && $isImported || !$isInNamespace)) { $tokens[$index] = new Token([T_STRING, \DateTimeImmutable::class]); if ($isInNamespace && $isUsedAlone) { $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); } } } private function fixFunctionUsage(Tokens $tokens, $index, $replacement) { $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR])) { return; } if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if ($tokens[$prevPrevIndex]->isGivenKind([T_NEW, T_STRING])) { return; } } $tokens[$index] = new Token([T_STRING, $replacement]); } } proxyFixers); } protected function createProxyFixers() { return [new ErrorSuppressionFixer()]; } } isTokenKindFound(T_UNSET); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { if (!$tokens[$index]->isGivenKind(T_UNSET)) { continue; } $previousUnsetCall = $this->getPreviousUnsetCall($tokens, $index); if (\is_int($previousUnsetCall)) { $index = $previousUnsetCall; continue; } list($previousUnset, , $previousUnsetBraceEnd) = $previousUnsetCall; $tokensAddCount = $this->moveTokens( $tokens, $nextUnsetContentStart = $tokens->getNextTokenOfKind($index, ['(']), $nextUnsetContentEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextUnsetContentStart), $previousUnsetBraceEnd - 1 ); if (!$tokens[$previousUnsetBraceEnd]->isWhitespace()) { $tokens->insertAt($previousUnsetBraceEnd, new Token([T_WHITESPACE, ' '])); ++$tokensAddCount; } $tokens->insertAt($previousUnsetBraceEnd, new Token(',')); ++$tokensAddCount; $this->clearOffsetTokens($tokens, $tokensAddCount, [$index, $nextUnsetContentStart, $nextUnsetContentEnd]); $nextUnsetSemicolon = $tokens->getNextMeaningfulToken($nextUnsetContentEnd); if (null !== $nextUnsetSemicolon && $tokens[$nextUnsetSemicolon]->equals(';')) { $tokens->clearTokenAndMergeSurroundingWhitespace($nextUnsetSemicolon); } $index = $previousUnset + 1; } } private function clearOffsetTokens(Tokens $tokens, $offset, array $indices) { foreach ($indices as $index) { $tokens->clearTokenAndMergeSurroundingWhitespace($index + $offset); } } private function getPreviousUnsetCall(Tokens $tokens, $index) { $previousUnsetSemicolon = $tokens->getPrevMeaningfulToken($index); if (null === $previousUnsetSemicolon) { return $index; } if (!$tokens[$previousUnsetSemicolon]->equals(';')) { return $previousUnsetSemicolon; } $previousUnsetBraceEnd = $tokens->getPrevMeaningfulToken($previousUnsetSemicolon); if (null === $previousUnsetBraceEnd) { return $index; } if (!$tokens[$previousUnsetBraceEnd]->equals(')')) { return $previousUnsetBraceEnd; } $previousUnsetBraceStart = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $previousUnsetBraceEnd); $previousUnset = $tokens->getPrevMeaningfulToken($previousUnsetBraceStart); if (null === $previousUnset) { return $index; } if (!$tokens[$previousUnset]->isGivenKind(T_UNSET)) { return $previousUnset; } return [ $previousUnset, $previousUnsetBraceStart, $previousUnsetBraceEnd, $previousUnsetSemicolon, ]; } private function moveTokens(Tokens $tokens, $start, $end, $to) { $added = 0; for ($i = $start + 1; $i < $end; $i += 2) { if ($tokens[$i]->isWhitespace() && $tokens[$to + 1]->isWhitespace()) { $tokens[$to + 1] = new Token([T_WHITESPACE, $tokens[$to + 1]->getContent().$tokens[$i]->getContent()]); } else { $tokens->insertAt(++$to, clone $tokens[$i]); ++$end; ++$added; } $tokens->clearAt($i + 1); } return $added; } } isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $sequenceNeeded = [[T_STRING, 'is_null'], '(']; $functionsAnalyzer = new FunctionsAnalyzer(); $currIndex = 0; while (null !== $currIndex) { $matches = $tokens->findSequence($sequenceNeeded, $currIndex, $tokens->count() - 1, false); if (null === $matches) { break; } $matches = array_keys($matches); list($isNullIndex, $currIndex) = $matches; if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $matches[0])) { continue; } $next = $tokens->getNextMeaningfulToken($currIndex); if ($tokens[$next]->equals(')')) { continue; } $prevTokenIndex = $tokens->getPrevMeaningfulToken($matches[0]); if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { $tokens->removeTrailingWhitespace($prevTokenIndex); $tokens->clearAt($prevTokenIndex); $prevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex); } $isInvertedNullCheck = false; if ($tokens[$prevTokenIndex]->equals('!')) { $isInvertedNullCheck = true; $tokens->removeTrailingWhitespace($prevTokenIndex); $tokens->clearAt($prevTokenIndex); } $referenceEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $matches[1]); $isContainingDangerousConstructs = false; for ($paramTokenIndex = $matches[1]; $paramTokenIndex <= $referenceEnd; ++$paramTokenIndex) { if (\in_array($tokens[$paramTokenIndex]->getContent(), ['?', '?:', '='], true)) { $isContainingDangerousConstructs = true; break; } } $parentLeftToken = $tokens[$tokens->getPrevMeaningfulToken($isNullIndex)]; $parentRightToken = $tokens[$tokens->getNextMeaningfulToken($referenceEnd)]; $parentOperations = [T_IS_EQUAL, T_IS_NOT_EQUAL, T_IS_IDENTICAL, T_IS_NOT_IDENTICAL]; $wrapIntoParentheses = $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations); $prevIndex = $tokens->getPrevMeaningfulToken($referenceEnd); if ($tokens[$prevIndex]->equals(',')) { $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); } if (!$isContainingDangerousConstructs) { $tokens->removeLeadingWhitespace($referenceEnd); $tokens->clearAt($referenceEnd); $tokens->removeLeadingWhitespace($matches[1]); $tokens->removeTrailingWhitespace($matches[1]); $tokens->clearAt($matches[1]); } $replacement = [ new Token([T_STRING, 'null']), new Token([T_WHITESPACE, ' ']), new Token($isInvertedNullCheck ? [T_IS_NOT_IDENTICAL, '!=='] : [T_IS_IDENTICAL, '===']), new Token([T_WHITESPACE, ' ']), ]; if (true === $this->configuration['use_yoda_style']) { if ($wrapIntoParentheses) { array_unshift($replacement, new Token('(')); $tokens->insertAt($referenceEnd + 1, new Token(')')); } $tokens->overrideRange($isNullIndex, $isNullIndex, $replacement); } else { $replacement = array_reverse($replacement); if ($wrapIntoParentheses) { $replacement[] = new Token(')'); $tokens[$isNullIndex] = new Token('('); } else { $tokens->clearAt($isNullIndex); } $tokens->insertAt($referenceEnd + 1, $replacement); } $currIndex = $isNullIndex; } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('use_yoda_style', 'Whether Yoda style conditions should be used.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->setDeprecationMessage('Use `yoda_style` fixer instead.') ->getOption(), ]); } } true] ), new CodeSample( " true, self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE => ['unlink'], ] ), ], null, 'Risky because adding/removing `@` might cause changes to code behaviour or if `trigger_error` function is overridden.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(['@', T_STRING]); } public function isRisky() { return true; } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder(self::OPTION_MUTE_DEPRECATION_ERROR, 'Whether to add `@` in deprecation notices.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder(self::OPTION_NOISE_REMAINING_USAGES, 'Whether to remove `@` in remaining usages.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder(self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE, 'List of global functions to exclude from removing `@`')) ->setAllowedTypes(['array']) ->setDefault([]) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $functionsAnalyzer = new FunctionsAnalyzer(); $excludedFunctions = array_map(function ($function) { return strtolower($function); }, $this->configuration[self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE]); for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if ($this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && $token->equals('@')) { $tokens->clearAt($index); continue; } if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { continue; } $functionIndex = $index; $startIndex = $index; $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $startIndex = $prevIndex; $prevIndex = $tokens->getPrevMeaningfulToken($startIndex); } $index = $prevIndex; if ($this->isDeprecationErrorCall($tokens, $functionIndex)) { if (!$this->configuration[self::OPTION_MUTE_DEPRECATION_ERROR]) { continue; } if ($tokens[$prevIndex]->equals('@')) { continue; } $tokens->insertAt($startIndex, new Token('@')); continue; } if (!$tokens[$prevIndex]->equals('@')) { continue; } if ($this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && !\in_array($tokens[$functionIndex]->getContent(), $excludedFunctions, true)) { $tokens->clearAt($index); } } } private function isDeprecationErrorCall(Tokens $tokens, $index) { if ('trigger_error' !== strtolower($tokens[$index]->getContent())) { return false; } $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($index, [T_STRING, '('])); $prevIndex = $tokens->getPrevMeaningfulToken($endBraceIndex); if ($tokens[$prevIndex]->equals(',')) { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } return $tokens[$prevIndex]->equals([T_STRING, 'E_USER_DEPRECATED']); } } isTokenKindFound(T_FILE); } public function getPriority() { return 4; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $currIndex = 0; while (null !== $currIndex) { $boundaries = $this->find('dirname', $tokens, $currIndex, $tokens->count() - 1); if (null === $boundaries) { return; } list($functionNameIndex, $openParenthesis, $closeParenthesis) = $boundaries; $currIndex = $openParenthesis; $fileCandidateRightIndex = $tokens->getPrevMeaningfulToken($closeParenthesis); $trailingCommaIndex = null; if ($tokens[$fileCandidateRightIndex]->equals(',')) { $trailingCommaIndex = $fileCandidateRightIndex; $fileCandidateRightIndex = $tokens->getPrevMeaningfulToken($fileCandidateRightIndex); } $fileCandidateRight = $tokens[$fileCandidateRightIndex]; if (!$fileCandidateRight->isGivenKind(T_FILE)) { continue; } $fileCandidateLeftIndex = $tokens->getNextMeaningfulToken($openParenthesis); $fileCandidateLeft = $tokens[$fileCandidateLeftIndex]; if (!$fileCandidateLeft->isGivenKind(T_FILE)) { continue; } $namespaceCandidateIndex = $tokens->getPrevMeaningfulToken($functionNameIndex); $namespaceCandidate = $tokens[$namespaceCandidateIndex]; if ($namespaceCandidate->isGivenKind(T_NS_SEPARATOR)) { $tokens->removeTrailingWhitespace($namespaceCandidateIndex); $tokens->clearAt($namespaceCandidateIndex); } if (null !== $trailingCommaIndex) { if (!$tokens[$tokens->getNextNonWhitespace($trailingCommaIndex)]->isComment()) { $tokens->removeTrailingWhitespace($trailingCommaIndex); } $tokens->clearTokenAndMergeSurroundingWhitespace($trailingCommaIndex); } if (!$tokens[$tokens->getNextNonWhitespace($closeParenthesis)]->isComment()) { $tokens->removeLeadingWhitespace($closeParenthesis); } $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesis); if (!$tokens[$tokens->getNextNonWhitespace($openParenthesis)]->isComment()) { $tokens->removeLeadingWhitespace($openParenthesis); } $tokens->removeTrailingWhitespace($openParenthesis); $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesis); $tokens[$fileCandidateLeftIndex] = new Token([T_DIR, '__DIR__']); $tokens->clearTokenAndMergeSurroundingWhitespace($functionNameIndex); } } } isAllTokenKindsFound([T_ISSET, T_BOOLEAN_AND]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokenCount = $tokens->count(); for ($index = 1; $index < $tokenCount; ++$index) { if (!$tokens[$index]->isGivenKind(T_ISSET) || $tokens[$tokens->getPrevMeaningfulToken($index)]->equals('!')) { continue; } $issetInfo = $this->getIssetInfo($tokens, $index); $issetCloseBraceIndex = end($issetInfo); $insertLocation = prev($issetInfo) + 1; $booleanAndTokenIndex = $tokens->getNextMeaningfulToken($issetCloseBraceIndex); while ($tokens[$booleanAndTokenIndex]->isGivenKind(T_BOOLEAN_AND)) { $issetIndex = $tokens->getNextMeaningfulToken($booleanAndTokenIndex); if (!$tokens[$issetIndex]->isGivenKind(T_ISSET)) { $index = $issetIndex; break; } $nextIssetInfo = $this->getIssetInfo($tokens, $issetIndex); $clones = $this->getTokenClones($tokens, \array_slice($nextIssetInfo, 1, -1)); $this->clearTokens($tokens, array_merge($nextIssetInfo, [$issetIndex, $booleanAndTokenIndex])); array_unshift($clones, new Token(','), new Token([T_WHITESPACE, ' '])); $tokens->insertAt($insertLocation, $clones); $numberOfTokensInserted = \count($clones); $tokenCount += $numberOfTokensInserted; $issetCloseBraceIndex += $numberOfTokensInserted; $insertLocation += $numberOfTokensInserted; $booleanAndTokenIndex = $tokens->getNextMeaningfulToken($issetCloseBraceIndex); } } } private function clearTokens(Tokens $tokens, array $indexes) { foreach ($indexes as $index) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } private function getIssetInfo(Tokens $tokens, $index) { $openIndex = $tokens->getNextMeaningfulToken($index); $braceOpenCount = 1; $meaningfulTokenIndexes = [$openIndex]; for ($i = $openIndex + 1;; ++$i) { if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { continue; } $meaningfulTokenIndexes[] = $i; if ($tokens[$i]->equals(')')) { --$braceOpenCount; if (0 === $braceOpenCount) { break; } } elseif ($tokens[$i]->equals('(')) { ++$braceOpenCount; } } return $meaningfulTokenIndexes; } private function getTokenClones(Tokens $tokens, array $indexes) { $clones = []; foreach ($indexes as $i) { $clones[] = clone $tokens[$i]; } return $clones; } } [ new Token([T_STATIC, 'static']), new Token([T_DOUBLE_COLON, '::']), new Token([CT::T_CLASS_CONSTANT, 'class']), ], 'get_class' => [new Token([T_CLASS_C, '__CLASS__'])], 'php_sapi_name' => [new Token([T_STRING, 'PHP_SAPI'])], 'phpversion' => [new Token([T_STRING, 'PHP_VERSION'])], 'pi' => [new Token([T_STRING, 'M_PI'])], ]; } parent::__construct(); } public function configure(array $configuration = null) { parent::configure($configuration); $this->functionsFixMap = []; foreach ($this->configuration['functions'] as $key) { $this->functionsFixMap[$key] = self::$availableFunctions[$key]; } } public function getDefinition() { return new FixerDefinition( 'Replace core functions calls returning constants with the constants.', [ new CodeSample(" ['phpversion']]), ], null, 'Risky when any of the configured functions to replace are overridden.' ); } public function getPriority() { return 1; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 4; $index > 0; --$index) { $candidate = $this->getReplaceCandidate($tokens, $index); if (null === $candidate) { continue; } $this->fixFunctionCallToConstant( $tokens, $index, $candidate[0], $candidate[1], $candidate[2] ); } } protected function createConfigurationDefinition() { $functionNames = array_keys(self::$availableFunctions); return new FixerConfigurationResolver([ (new FixerOptionBuilder('functions', 'List of function names to fix.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset($functionNames)]) ->setDefault([ 'get_class', 'php_sapi_name', 'phpversion', 'pi', ]) ->getOption(), ]); } private function fixFunctionCallToConstant(Tokens $tokens, $index, $braceOpenIndex, $braceCloseIndex, array $replacements) { $tokens->clearTokenAndMergeSurroundingWhitespace($braceCloseIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($braceOpenIndex); if ($replacements[0]->isGivenKind([T_CLASS_C, T_STATIC])) { $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { $tokens->clearAt($prevIndex); } } $tokens->clearAt($index); $tokens->insertAt($index, $replacements); } private function getReplaceCandidate(Tokens $tokens, $index) { if (!$tokens[$index]->isGivenKind(T_STRING)) { return null; } $braceOpenIndex = $tokens->getNextMeaningfulToken($index); if (!$tokens[$braceOpenIndex]->equals('(')) { return null; } $braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex); if (!$tokens[$braceCloseIndex]->equals(')')) { return null; } $functionNamePrefix = $tokens->getPrevMeaningfulToken($index); if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) { return null; } if ($tokens[$functionNamePrefix]->isGivenKind(T_NS_SEPARATOR)) { $prevIndex = $tokens->getPrevMeaningfulToken($functionNamePrefix); if ($tokens[$prevIndex]->isGivenKind([T_STRING, T_NEW])) { return null; } } $lowerContent = strtolower($tokens[$index]->getContent()); if (!\array_key_exists($lowerContent, $this->functionsFixMap)) { return null; } $clones = []; foreach ($this->functionsFixMap[$lowerContent] as $token) { $clones[] = clone $token; } return [ $braceOpenIndex, $braceCloseIndex, $clones, ]; } } a);\n")], null, 'Changing variables to `null` instead of unsetting them will mean they still show up '. 'when looping over class variables.' ); } public function isRisky() { return true; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_UNSET) && $tokens->isAnyTokenKindsFound([T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM]); } public function getPriority() { return 25; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { if (!$tokens[$index]->isGivenKind(T_UNSET)) { continue; } $unsetsInfo = $this->getUnsetsInfo($tokens, $index); if (!$this->isAnyUnsetToTransform($unsetsInfo)) { continue; } $isLastUnset = true; foreach (array_reverse($unsetsInfo) as $unsetInfo) { $this->updateTokens($tokens, $unsetInfo, $isLastUnset); $isLastUnset = false; } } } private function getUnsetsInfo(Tokens $tokens, $index) { $argumentsAnalyzer = new ArgumentsAnalyzer(); $unsetStart = $tokens->getNextTokenOfKind($index, ['(']); $unsetEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $unsetStart); $isFirst = true; $unsets = []; foreach ($argumentsAnalyzer->getArguments($tokens, $unsetStart, $unsetEnd) as $startIndex => $endIndex) { $startIndex = $tokens->getNextMeaningfulToken($startIndex - 1); $endIndex = $tokens->getPrevMeaningfulToken($endIndex + 1); $unsets[] = [ 'startIndex' => $startIndex, 'endIndex' => $endIndex, 'isToTransform' => $this->isProperty($tokens, $startIndex, $endIndex), 'isFirst' => $isFirst, ]; $isFirst = false; } return $unsets; } private function isProperty(Tokens $tokens, $index, $endIndex) { if ($tokens[$index]->isGivenKind(T_VARIABLE)) { $nextIndex = $tokens->getNextMeaningfulToken($index); if (null === $nextIndex || !$tokens[$nextIndex]->isGivenKind(T_OBJECT_OPERATOR)) { return false; } $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); if (null !== $nextNextIndex && $nextNextIndex < $endIndex) { return false; } return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_STRING); } if ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STRING])) { $nextIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[T_DOUBLE_COLON], [T_NS_SEPARATOR], [T_STRING]]); $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); if (null !== $nextNextIndex && $nextNextIndex < $endIndex) { return false; } return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_VARIABLE); } return false; } private function isAnyUnsetToTransform(array $unsetsInfo) { foreach ($unsetsInfo as $unsetInfo) { if ($unsetInfo['isToTransform']) { return true; } } return false; } private function updateTokens(Tokens $tokens, array $unsetInfo, $isLastUnset) { if ($unsetInfo['isFirst'] && $unsetInfo['isToTransform']) { $braceIndex = $tokens->getPrevTokenOfKind($unsetInfo['startIndex'], ['(']); $unsetIndex = $tokens->getPrevTokenOfKind($braceIndex, [[T_UNSET]]); $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($unsetIndex); } if ($isLastUnset && $unsetInfo['isToTransform']) { $braceIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [')']); $previousIndex = $tokens->getPrevMeaningfulToken($braceIndex); if ($tokens[$previousIndex]->equals(',')) { $tokens->clearTokenAndMergeSurroundingWhitespace($previousIndex); } $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex); } if (!$isLastUnset) { $commaIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [',']); $tokens[$commaIndex] = new Token(';'); } if (!$unsetInfo['isToTransform'] && !$isLastUnset) { $tokens->insertAt($unsetInfo['endIndex'] + 1, new Token(')')); } if (!$unsetInfo['isToTransform'] && !$unsetInfo['isFirst']) { $tokens->insertAt( $unsetInfo['startIndex'], [ new Token([T_UNSET, 'unset']), new Token('('), ] ); } if ($unsetInfo['isToTransform']) { $tokens->insertAt( $unsetInfo['endIndex'] + 1, [ new Token([T_WHITESPACE, ' ']), new Token('='), new Token([T_WHITESPACE, ' ']), new Token([T_STRING, 'null']), ] ); } } } = 7.0.', [ new VersionSpecificCodeSample( <<<'EOT' $bar['baz']; echo $foo->$callback($baz); EOT , new VersionSpecification(70000) ), ] ); } public function isCandidate(Tokens $tokens) { return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(T_VARIABLE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index > 1; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_VARIABLE)) { continue; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if (!$prevToken->equals('$') && !$prevToken->isGivenKind(T_OBJECT_OPERATOR)) { continue; } $openingBrace = CT::T_DYNAMIC_VAR_BRACE_OPEN; $closingBrace = CT::T_DYNAMIC_VAR_BRACE_CLOSE; if ($prevToken->isGivenKind(T_OBJECT_OPERATOR)) { $openingBrace = CT::T_DYNAMIC_PROP_BRACE_OPEN; $closingBrace = CT::T_DYNAMIC_PROP_BRACE_CLOSE; } $tokens->overrideRange($index, $index, [ new Token([$openingBrace, '{']), new Token([T_VARIABLE, $token->getContent()]), new Token([$closingBrace, '}']), ]); } } } callback = 'none' === $this->configuration['space'] ? 'removeWhitespaceAroundToken' : 'ensureWhitespaceAroundToken'; } public function getDefinition() { return new FixerDefinition( 'Equal sign in declare statement should be surrounded by spaces or not following configuration.', [ new CodeSample(" 'single']), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DECLARE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $callback = $this->callback; for ($index = 0, $count = $tokens->count(); $index < $count - 6; ++$index) { if (!$tokens[$index]->isGivenKind(T_DECLARE)) { continue; } while (!$tokens[++$index]->equals('=')); $this->{$callback}($tokens, $index); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('space', 'Spacing to apply around the equal sign.')) ->setAllowedValues(['single', 'none']) ->setDefault('none') ->getOption(), ]); } private function ensureWhitespaceAroundToken(Tokens $tokens, $index) { if ($tokens[$index + 1]->isWhitespace()) { if (' ' !== $tokens[$index + 1]->getContent()) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } } else { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } if ($tokens[$index - 1]->isWhitespace()) { if (' ' !== $tokens[$index - 1]->getContent() && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); } } else { $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); } } private function removeWhitespaceAroundToken(Tokens $tokens, $index) { if (!$tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { $tokens->removeLeadingWhitespace($index); } $tokens->removeTrailingWhitespace($index); } } isTokenKindFound(CT::T_CLASS_CONSTANT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->replaceClassKeywords($tokens); } private function replaceClassKeywords(Tokens $tokens, $namespaceNumber = -1) { $namespaceIndexes = array_keys($tokens->findGivenKind(T_NAMESPACE)); if (\count($namespaceIndexes) && isset($namespaceIndexes[$namespaceNumber])) { $startIndex = $namespaceIndexes[$namespaceNumber]; $namespaceBlockStartIndex = $tokens->getNextTokenOfKind($startIndex, [';', '{']); $endIndex = $tokens[$namespaceBlockStartIndex]->equals('{') ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $namespaceBlockStartIndex) : $tokens->getNextTokenOfKind($namespaceBlockStartIndex, [T_NAMESPACE]); $endIndex = $endIndex ?: $tokens->count() - 1; } elseif (-1 === $namespaceNumber) { $startIndex = 0; $endIndex = \count($namespaceIndexes) ? $namespaceIndexes[0] : $tokens->count() - 1; } else { return; } $this->storeImports($tokens, $startIndex, $endIndex); $tokens->rewind(); $this->replaceClassKeywordsSection($tokens, $startIndex, $endIndex); $this->replaceClassKeywords($tokens, $namespaceNumber + 1); } private function storeImports(Tokens $tokens, $startIndex, $endIndex) { $tokensAnalyzer = new TokensAnalyzer($tokens); $this->imports = []; foreach ($tokensAnalyzer->getImportUseIndexes() as $index) { if ($index < $startIndex || $index > $endIndex) { continue; } $import = ''; while ($index = $tokens->getNextMeaningfulToken($index)) { if ($tokens[$index]->equalsAny([';', [CT::T_GROUP_IMPORT_BRACE_OPEN]]) || $tokens[$index]->isGivenKind(T_AS)) { break; } $import .= $tokens[$index]->getContent(); } if ($tokens[$index]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { $groupEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $index); $groupImports = array_map( static function ($import) { return trim($import); }, explode(',', $tokens->generatePartialCode($index + 1, $groupEndIndex - 1)) ); foreach ($groupImports as $groupImport) { $groupImportParts = array_map(static function ($import) { return trim($import); }, explode(' as ', $groupImport)); if (2 === \count($groupImportParts)) { $this->imports[$groupImportParts[1]] = $import.$groupImportParts[0]; } else { $this->imports[] = $import.$groupImport; } } } elseif ($tokens[$index]->isGivenKind(T_AS)) { $aliasIndex = $tokens->getNextMeaningfulToken($index); $alias = $tokens[$aliasIndex]->getContent(); $this->imports[$alias] = $import; } else { $this->imports[] = $import; } } } private function replaceClassKeywordsSection(Tokens $tokens, $startIndex, $endIndex) { $ctClassTokens = $tokens->findGivenKind(CT::T_CLASS_CONSTANT, $startIndex, $endIndex); if (!empty($ctClassTokens)) { $this->replaceClassKeyword($tokens, current(array_keys($ctClassTokens))); $this->replaceClassKeywordsSection($tokens, $startIndex, $endIndex); } } private function replaceClassKeyword(Tokens $tokens, $classIndex) { $classEndIndex = $tokens->getPrevMeaningfulToken($classIndex); $classEndIndex = $tokens->getPrevMeaningfulToken($classEndIndex); $classBeginIndex = $classEndIndex; while (true) { $prev = $tokens->getPrevMeaningfulToken($classBeginIndex); if (!$tokens[$prev]->isGivenKind([T_NS_SEPARATOR, T_STRING])) { break; } $classBeginIndex = $prev; } $classString = $tokens->generatePartialCode( $tokens[$classBeginIndex]->isGivenKind(T_NS_SEPARATOR) ? $tokens->getNextMeaningfulToken($classBeginIndex) : $classBeginIndex, $classEndIndex ); $classImport = false; foreach ($this->imports as $alias => $import) { if ($classString === $alias) { $classImport = $import; break; } $classStringArray = explode('\\', $classString); $namespaceToTest = $classStringArray[0]; if (0 === strcmp($namespaceToTest, substr($import, -\strlen($namespaceToTest)))) { $classImport = $import; break; } } for ($i = $classBeginIndex; $i <= $classIndex; ++$i) { if (!$tokens[$i]->isComment() && !($tokens[$i]->isWhitespace() && false !== strpos($tokens[$i]->getContent(), "\n"))) { $tokens->clearAt($i); } } $tokens->insertAt($classBeginIndex, new Token([ T_CONSTANT_ENCAPSED_STRING, "'".$this->makeClassFQN($classImport, $classString)."'", ])); } private function makeClassFQN($classImport, $classString) { if (false === $classImport) { return $classString; } $classStringArray = explode('\\', $classString); $classStringLength = \count($classStringArray); $classImportArray = explode('\\', $classImport); $classImportLength = \count($classImportArray); if (1 === $classStringLength) { return $classImport; } return implode('\\', array_merge( \array_slice($classImportArray, 0, $classImportLength - $classStringLength + 1), $classStringArray )); } } 'always_last'] ), new CodeSample( ' 'alpha'] ), new CodeSample( ' 'alpha', 'null_adjustment' => 'always_last', ] ), new CodeSample( ' 'alpha', 'null_adjustment' => 'none', ] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOC_COMMENT); } protected function createConfigurationDefinition() { 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) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); if (!\count($annotations)) { continue; } foreach ($annotations as $annotation) { $types = $annotation->getTypes(); $annotation->setTypes($this->sortTypes($types)); $line = $doc->getLine($annotation->getStart()); $line->setContent(Preg::replaceCallback('/(@method\s+.+?\s+\w+\()(.*)\)/', function (array $matches) { $sorted = Preg::replaceCallback('/((?:^|,)\s*)([^\s]+)/', function (array $matches) { return $matches[1].$this->sortJoinedTypes($matches[2]); }, $matches[2]); return $matches[1].$sorted.')'; }, $line->getContent())); } $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } private function sortTypes(array $types) { foreach ($types as $index => $type) { $types[$index] = Preg::replaceCallback('/^([^<]+)<(?:([\w\|]+?)(,\s*))?(.*)>$/', function (array $matches) { return $matches[1].'<'.$this->sortJoinedTypes($matches[2]).$matches[3].$this->sortJoinedTypes($matches[4]).'>'; }, $type); } if ('alpha' === $this->configuration['sort_algorithm']) { $types = Utils::stableSort( $types, static function ($type) { return $type; }, static function ($typeA, $typeB) { $regexp = '/^\\??\\\?/'; return strcasecmp( Preg::replace($regexp, '', $typeA), Preg::replace($regexp, '', $typeB) ); } ); } if ('none' !== $this->configuration['null_adjustment']) { $nulls = []; foreach ($types as $index => $type) { if (Preg::match('/^\\\?null$/i', $type)) { $nulls[$index] = $type; unset($types[$index]); } } if (\count($nulls)) { if ('always_last' === $this->configuration['null_adjustment']) { array_push($types, ...$nulls); } else { array_unshift($types, ...$nulls); } } } return $types; } private function sortJoinedTypes($types) { $types = array_filter( Preg::split('/([^|<]+(?:<.*>)?)/', $types, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY), static function ($value) { return '|' !== $value; } ); return implode('|', $this->sortTypes($types)); } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $nextIndex = $tokens->getNextMeaningfulToken($index); if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { continue; } $prevIndex = $index - 1; $prevToken = $tokens[$prevIndex]; if ( $prevToken->isGivenKind(T_OPEN_TAG) || ($prevToken->isWhitespace(" \t") && !$tokens[$index - 2]->isGivenKind(T_OPEN_TAG)) || $prevToken->equalsAny([';', ',', '{', '(']) ) { continue; } $indent = ''; if ($tokens[$nextIndex - 1]->isWhitespace()) { $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$nextIndex - 1]); } $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)]); } } private function fixDocBlock($content, $indent) { return ltrim(Preg::replace('/^[ \t]*\*/m', $indent.' *', $content)); } private function fixWhitespaceBeforeDocblock($content, $indent) { return rtrim($content, " \t").$indent; } } isTokenKindFound(T_DOC_COMMENT); } public function getDefinition() { return new FixerDefinition( 'Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations.', [ new CodeSample( ' $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $content = $token->getContent(); $content = $this->moveParamAnnotations($content); $content = $this->moveReturnAnnotations($content); $tokens[$index] = new Token([T_DOC_COMMENT, $content]); } } private function moveParamAnnotations($content) { $doc = new DocBlock($content); $params = $doc->getAnnotationsOfType('param'); if (empty($params)) { return $content; } $others = $doc->getAnnotationsOfType(['throws', 'return']); if (empty($others)) { return $content; } $end = end($params)->getEnd(); $line = $doc->getLine($end); foreach ($others as $other) { if ($other->getStart() < $end) { $line->setContent($line->getContent().$other->getContent()); $other->remove(); } } return $doc->getContent(); } private function moveReturnAnnotations($content) { $doc = new DocBlock($content); $returns = $doc->getAnnotationsOfType('return'); if (empty($returns)) { return $content; } $others = $doc->getAnnotationsOfType(['param', 'throws']); if (empty($others)) { return $content; } $start = $returns[0]->getStart(); $line = $doc->getLine($start); foreach (array_reverse($others) as $other) { if ($other->getEnd() > $start) { $line->setContent($other->getContent().$line->getContent()); $other->remove(); } } return $doc->getContent(); } } isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_DOC_COMMENT)) { $tokens[$index] = new Token([T_DOC_COMMENT, $this->fixTokenContent($token->getContent())]); continue; } if (!$token->isGivenKind(T_COMMENT)) { continue; } $content = $token->getContent(); $fixedContent = $this->fixTokenContent($content); if ($content !== $fixedContent) { $tokens[$index] = new Token([T_DOC_COMMENT, $fixedContent]); } } } private function fixTokenContent($content) { return Preg::replaceCallback( '#^/\*\*[ \t]*@var[ \t]+(\S+)[ \t]*(\$\S+)?[ \t]*([^\n]*)\*/$#', static function (array $matches) { $content = '/** @var'; for ($i = 1, $m = \count($matches); $i < $m; ++$i) { if ('' !== $matches[$i]) { $content .= ' '.$matches[$i]; } } $content = rtrim($content); return $content.' */'; }, $content ); } } true] ), new CodeSample( ' false] ), ] ); } public function getPriority() { return 10; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOC_COMMENT); } public function isRisky() { return false; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $argumentsAnalyzer = new ArgumentsAnalyzer(); for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $mainIndex = $index; $token = $tokens[$index]; if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $tokenContent = $token->getContent(); if (false !== stripos($tokenContent, 'inheritdoc')) { continue; } if (false === strpos($tokenContent, "\n")) { continue; } $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 (!$this->configuration['only_untyped'] || '' === $argumentInfo['type']) { $arguments[$argumentInfo['name']] = $argumentInfo; } } if (!\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 (!\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 ('?' !== $type[0] && '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)]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('only_untyped', 'Whether to add missing `@param` annotations for untyped parameters only.')) ->setDefault(true) ->setAllowedTypes(['bool']) ->getOption(), ]); } private function prepareArgumentInformation(Tokens $tokens, $start, $end) { $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->getContent()) { if ($token->isGivenKind(T_ELLIPSIS)) { if ('' === $info['type']) { $info['type'] = 'array'; } else { $info['type'] .= '[]'; } } else { $info['type'] .= $token->getContent(); } } } return $info; } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $content = $token->getContent(); $content = Preg::replaceCallback( '#(?:@{+|{+[ \t]*@)[ \t]*(example|id|internal|inheritdoc|link|source|toc|tutorial)s?([^}]*)(?:}+)#i', static function (array $matches) { $doc = trim($matches[2]); if ('' === $doc) { return '{@'.strtolower($matches[1]).'}'; } return '{@'.strtolower($matches[1]).' '.$doc.'}'; }, $content ); $content = Preg::replace( '#(?isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } if (Preg::match('#^/\*\*[\s\*]*\*/$#', $token->getContent())) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } } } [ '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 $typesToFix = []; public function configure(array $configuration = null) { parent::configure($configuration); $this->typesToFix = array_merge(...array_map(function ($group) { return self::$possibleTypes[$group]; }, $this->configuration['groups'])); } public function getDefinition() { return new FixerDefinition( 'The correct case must be used for standard PHP types in PHPDoc.', [ new CodeSample( 'typesToFix, true)) { return $lower; } return $type; } protected function createConfigurationDefinition() { $possibleGroups = array_keys(self::$possibleTypes); return new FixerConfigurationResolver([ (new FixerOptionBuilder('groups', 'Type groups to fix.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset($possibleGroups)]) ->setDefault($possibleGroups) ->getOption(), ]); } } isTokenKindFound(T_DOC_COMMENT); } public function getDefinition() { return new FixerDefinition( '`@return void` and `@return null` annotations should be omitted from PHPDoc.', [ new CodeSample( ' $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType('return'); if (empty($annotations)) { continue; } foreach ($annotations as $annotation) { $this->fixAnnotation($doc, $annotation); } $newContent = $doc->getContent(); if ($newContent === $token->getContent()) { continue; } if ('' === $newContent) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); continue; } $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } private function fixAnnotation(DocBlock $doc, Annotation $annotation) { $types = $annotation->getNormalizedTypes(); if (1 === \count($types) && ('null' === $types[0] || 'void' === $types[0])) { $annotation->remove(); } } } isTokenKindFound(T_DOC_COMMENT); } public function getPriority() { return 25; } public function getDefinition() { return new FixerDefinition( 'Docblocks should only be used on structural elements.', [ new CodeSample( ' $sqlite) { $sqlite->open($path); } ' ), ] ); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $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; } $tokens[$index] = new Token([T_COMMENT, '/*'.ltrim($token->getContent(), '/*')]); } } } 'bool', 'callback' => 'callable', 'double' => 'float', 'integer' => 'int', 'real' => 'float', 'str' => 'string', ]; public function getDefinition() { 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('setAllowedValues([new AllowedValueSubset(array_keys(self::$types))]) ->setDefault(['boolean', 'double', 'integer', 'real', 'str']) ->getOption(), ]); } protected function normalize($type) { if (\in_array($type, $this->configuration['types'], true)) { return self::$types[$type]; } return $type; } } ['author']] ), ] ); } public function getPriority() { return 10; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if (!\count($this->configuration['annotations'])) { return; } foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType($this->configuration['annotations']); if (empty($annotations)) { continue; } foreach ($annotations as $annotation) { $annotation->remove(); } if ('' === $doc->getContent()) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } else { $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('annotations', [ (new FixerOptionBuilder('annotations', 'List of annotations to remove, e.g. `["author"]`.')) ->setAllowedTypes(['array']) ->setDefault([]) ->getOption(), ], $this->getName()); } } configure(['annotations' => ['package', 'subpackage']]); return [$fixer]; } } isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { 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, $index) { $classOpenIndex = $tokens->getNextTokenOfKind($index, ['{']); $classEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenIndex); $extendingOrImplementing = $this->isExtendingOrImplementing($tokens, $index, $classOpenIndex); if (!$extendingOrImplementing) { $this->fixClassyOutside($tokens, $index); } if (!$extendingOrImplementing && $this->isUsingTrait($tokens, $index, $classOpenIndex, $classEndIndex)) { $extendingOrImplementing = true; } $this->fixClassyInside($tokens, $classOpenIndex, $classEndIndex, !$extendingOrImplementing); return $classEndIndex; } private function fixClassyInside(Tokens $tokens, $classOpenIndex, $classEndIndex, $fixThisLevel) { 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, $classIndex) { $previousIndex = $tokens->getPrevNonWhitespace($classIndex); if ($tokens[$previousIndex]->isGivenKind(T_DOC_COMMENT)) { $this->fixToken($tokens, $previousIndex); } } private function fixToken(Tokens $tokens, $tokenIndex) { $count = 0; $content = Preg::replaceCallback( '#(\h*(?:@{*|{*\h*@)\h*inheritdoc\h*)([^}]*)((?:}*)\h*)#i', static function ($matches) { return ' '.$matches[2]; }, $tokens[$tokenIndex]->getContent(), -1, $count ); if ($count) { $tokens[$tokenIndex] = new Token([T_DOC_COMMENT, $content]); } } private function isExtendingOrImplementing(Tokens $tokens, $classIndex, $classOpenIndex) { for ($index = $classIndex; $index < $classOpenIndex; ++$index) { if ($tokens[$index]->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { return true; } } return false; } private function isUsingTrait(Tokens $tokens, $classIndex, $classOpenIndex, $classCloseIndex) { if ($tokens[$classIndex]->isGivenKind(T_INTERFACE)) { return false; } $useIndex = $tokens->getNextTokenOfKind($classOpenIndex, [[CT::T_USE_TRAIT]]); return null !== $useIndex && $useIndex < $classCloseIndex; } } ['this' => 'self']] ), ] ); } public function isCandidate(Tokens $tokens) { return \count($tokens) > 10 && $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); } public function getPriority() { return 10; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); foreach ($tokensAnalyzer->getClassyElements() as $index => $element) { if ('method' === $element['type']) { $this->fixMethod($tokens, $index); } } } protected function createConfigurationDefinition() { $default = [ 'this' => '$this', '@this' => '$this', '$self' => 'self', '@self' => 'self', '$static' => 'static', '@static' => 'static', ]; return new FixerConfigurationResolverRootless('replacements', [ (new FixerOptionBuilder('replacements', 'Mapping between replaced return types with new ones.')) ->setAllowedTypes(['array']) ->setNormalizer(static function (Options $options, $value) use ($default) { $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".', \is_object($from) ? \get_class($from) : \gettype($from).(\is_resource($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(), ], $this->getName()); } private function fixMethod(Tokens $tokens, $index) { static $methodModifiers = [T_STATIC, T_FINAL, T_ABSTRACT, T_PRIVATE, T_PROTECTED, T_PUBLIC]; do { $tokenIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$tokenIndex]->isGivenKind($methodModifiers)) { break; } $index = $tokenIndex; } while (true); $docIndex = $tokens->getPrevNonWhitespace($index); if (!$tokens[$docIndex]->isGivenKind(T_DOC_COMMENT)) { return; } $docBlock = new DocBlock($tokens[$docIndex]->getContent()); $returnsBlock = $docBlock->getAnnotationsOfType('return'); if (!\count($returnsBlock)) { return; } $returnsBlock = $returnsBlock[0]; $types = $returnsBlock->getTypes(); if (!\count($types)) { return; } $newTypes = []; foreach ($types as $type) { $lower = strtolower($type); $newTypes[] = isset($this->configuration['replacements'][$lower]) ? $this->configuration['replacements'][$lower] : $type; } if ($types === $newTypes) { return; } $returnsBlock->setTypes($newTypes); $tokens[$docIndex] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { 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()]); } } } } private function isCorrectlyFormatted($content) { if (false !== stripos($content, '{@inheritdoc}')) { return true; } return $content !== rtrim($content, '.。!?¡¿!?'); } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); if (1 === \count($doc->getLines())) { continue; } $annotations = $doc->getAnnotationsOfType(['param', 'return', 'type', 'var']); if (!isset($annotations[0]) || !\in_array($annotations[0]->getTag()->getName(), ['type', 'var'], true)) { continue; } $this->fixLine($doc->getLine($annotations[0]->getStart())); $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } private function fixLine(Line $line) { $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)); } } } isTokenKindFound(T_DOC_COMMENT); } public function getDefinition() { return new FixerDefinition( 'There should not be blank lines between docblock and the documented element.', [ new CodeSample( ' $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $next = $tokens->getNextNonWhitespace($index); if ($index + 2 === $next && false === $tokens[$next]->isGivenKind($forbiddenSuccessors)) { $this->fixWhitespace($tokens, $index + 1); } } } private function fixWhitespace(Tokens $tokens, $index) { $content = $tokens[$index]->getContent(); if (substr_count($content, "\n") > 1) { $tokens[$index] = new Token([T_WHITESPACE, substr($content, strrpos($content, "\n"))]); } } } configuration['tags'], self::$tagsWithName); $tagsWithMethodSignatureToAlign = array_intersect($this->configuration['tags'], self::$tagsWithMethodSignature); $tagsWithoutNameToAlign = array_diff($this->configuration['tags'], $tagsWithNameToAlign, $tagsWithMethodSignatureToAlign); $types = []; $indent = '(?P(?: {2}|\t)*)'; if (!empty($tagsWithNameToAlign)) { $types[] = '(?P'.implode('|', $tagsWithNameToAlign).')\s+(?P[^$]+?)\s+(?P(?:&|\.{3})?\$[^\s]+)'; } if (!empty($tagsWithoutNameToAlign)) { $types[] = '(?P'.implode('|', $tagsWithoutNameToAlign).')\s+(?P[^\s]+?)'; } if (!empty($tagsWithMethodSignatureToAlign)) { $types[] = '(?P'.implode('|', $tagsWithMethodSignatureToAlign).')(\s+(?P[^\s(]+)|)\s+(?P.+\))'; } $desc = '(?:\s+(?P\V*))'; $this->regex = '/^'.$indent.' \* @(?:'.implode('|', $types).')'.$desc.'\s*$/u'; $this->regexCommentLine = '/^'.$indent.' \*(?! @)(?:\s+(?P\V+))(?align = $this->configuration['align']; } public function getDefinition() { $code = <<<'EOF' self::ALIGN_VERTICAL]), new CodeSample($code, ['align' => self::ALIGN_LEFT]), ] ); } public function getPriority() { return -21; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { 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]); } } } protected function createConfigurationDefinition() { $tags = new FixerOptionBuilder('tags', 'The tags that should be aligned.'); $tags ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(self::$alignableTags)]) ->setDefault([ 'param', '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) { $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($i = 0, $l = \count($docBlock->getLines()); $i < $l; ++$i) { $items = []; $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; } $tagMax = 0; $hintMax = 0; $varMax = 0; foreach ($items as $item) { if (null === $item['tag']) { continue; } $tagMax = max($tagMax, \strlen($item['tag'])); $hintMax = max($hintMax, \strlen($item['hint'])); $varMax = max($varMax, \strlen($item['var'])); } $currTag = null; 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::$tagsWithName, true) || \in_array($currTag, self::$tagsWithMethodSignature, true)) { $extraIndent = 3; } $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'] .$this->getIndent( $tagMax - \strlen($item['tag']) + 1, $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); } } } private function getMatches($line, $matchCommentOnly = false) { 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']; } if (isset($matches['hint'])) { $matches['hint'] = trim($matches['hint']); } return $matches; } if ($matchCommentOnly && Preg::match($this->regexCommentLine, $line, $matches)) { $matches['tag'] = null; $matches['var'] = ''; $matches['hint'] = ''; return $matches; } } private function getIndent($verticalAlignIndent, $leftAlignIndent = 1) { $indent = self::ALIGN_VERTICAL === $this->align ? $verticalAlignIndent : $leftAlignIndent; return str_repeat(' ', $indent); } private function getLeftAlignedDescriptionIndent(array $items, $index) { if (self::ALIGN_LEFT !== $this->align) { return 0; } $item = null; for (; $index >= 0; --$index) { $item = $items[$index]; if (null !== $item['tag']) { break; } } if (null === $item) { return 0; } return $this->getSentenceIndent($item['tag']) + $this->getSentenceIndent($item['hint']) + $this->getSentenceIndent($item['var']); } private function getSentenceIndent($sentence) { if (null === $sentence) { return 0; } $length = \strlen($sentence); return 0 === $length ? 0 : $length + 1; } } tokenKinds = [T_DOC_COMMENT]; if ('phpdocs_only' !== $this->configuration['comment_type']) { $this->tokenKinds[] = T_COMMENT; } } public function getDefinition() { 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( ' 'phpdocs_like'] ), new CodeSample( ' 'all_multiline'] ), ] ); } public function getPriority() { return -40; } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound($this->tokenKinds); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $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([ \t]*)$/', $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)]); } } protected function createConfigurationDefinition() { 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(), ]); } } configure(['annotations' => ['access']]); return [$fixer]; } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { 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+)(\s+)([^\$](?:[^<\s]|<[^>]*>)*)(\s|\*)/i', '$1$4$3$2$5', $token->getContent() ); if ($newContent === $token->getContent()) { continue; } $tokens[$index] = new Token([$token->getId(), $newContent]); } } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotations(); if (empty($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 || '*' !== $lineAfterAnnotationTrimmed[0]) { 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('/(?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) { return $matches[1].strtolower($matches[2]).$matches[3]; }, $startLine->getContent(), 1 ); $startLine->setContent($content); } $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $content = $token->getContent(); $content = $this->fixStart($content); $content = $this->fixEnd($content); $tokens[$index] = new Token([T_DOC_COMMENT, $content]); } } private function fixStart($content) { return Preg::replace( '~ (^/\*\*) # DocComment begin (?: \R[ \t]*(?:\*[ \t]*)? # lines without useful content (?!\R[ \t]*\*/) # not followed by a DocComment end )+ (\R[ \t]*(?:\*[ \t]*)?\S) # first line with useful content ~x', '$1$2', $content ); } private function fixEnd($content) { return Preg::replace( '~ (\R[ \t]*(?:\*[ \t]*)?\S.*?) # last line with useful content (?: (?isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $namespaceUseAnalyzer = new NamespaceUsesAnalyzer(); $shortNames = []; foreach ($namespaceUseAnalyzer->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) { $shortNames[strtolower($namespaceUseAnalysis->getShortName())] = '\\'.strtolower($namespaceUseAnalysis->getFullName()); } foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $functionIndex = $this->findDocumentedFunction($tokens, $index); if (null === $functionIndex) { continue; } $docBlock = new DocBlock($token->getContent()); $openingParenthesisIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); $closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex); $argumentsInfo = $this->getArgumentsInfo( $tokens, $openingParenthesisIndex + 1, $closingParenthesisIndex - 1 ); foreach ($docBlock->getAnnotationsOfType('param') as $annotation) { if (0 === Preg::match('/@param(?:\s+[^\$]\S+)?\s+(\$\S+)/', $annotation->getContent(), $matches)) { continue; } $argumentName = $matches[1]; if ( !isset($argumentsInfo[$argumentName]) || $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $shortNames) ) { $annotation->remove(); } } $returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex); foreach ($docBlock->getAnnotationsOfType('return') as $annotation) { if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $shortNames)) { $annotation->remove(); } } $tokens[$index] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('allow_mixed', 'Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`)')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } private function findDocumentedFunction(Tokens $tokens, $index) { do { $index = $tokens->getNextMeaningfulToken($index); if (null === $index || $tokens[$index]->isGivenKind(T_FUNCTION)) { return $index; } } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL, T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC])); return null; } private function getArgumentsInfo(Tokens $tokens, $start, $end) { $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 = [ 'type' => null, 'allows_null' => true, ]; } if (!$info['allows_null']) { $nextIndex = $tokens->getNextMeaningfulToken($index); if ( $tokens[$nextIndex]->equals('=') && $tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals([T_STRING, 'null']) ) { $info['allows_null'] = true; } } $argumentsInfo[$token->getContent()] = $info; } return $argumentsInfo; } private function getReturnTypeInfo(Tokens $tokens, $closingParenthesisIndex) { $colonIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex); if ($tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)) { return $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex)); } return [ 'type' => null, 'allows_null' => true, ]; } private function parseTypeHint(Tokens $tokens, $index) { $allowsNull = false; if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { $allowsNull = true; $index = $tokens->getNextMeaningfulToken($index); } $type = ''; while ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE])) { $type .= $tokens[$index]->getContent(); $index = $tokens->getNextMeaningfulToken($index); } return [ 'type' => $type, 'allows_null' => $allowsNull, ]; } private function annotationIsSuperfluous(Annotation $annotation, array $info, array $symbolShortNames) { if ('param' === $annotation->getTag()->getName()) { $regex = '/@param\s+(?:\S|\s(?!\$))+\s\$\S+\s+\S/'; } else { $regex = '/@return\s+\S+\s+\S/'; } if (Preg::match($regex, $annotation->getContent())) { return false; } $annotationTypes = $this->toComparableNames($annotation->getTypes(), $symbolShortNames); if (['null'] === $annotationTypes) { return false; } if (['mixed'] === $annotationTypes && null === $info['type']) { return !$this->configuration['allow_mixed']; } $actualTypes = null === $info['type'] ? [] : [$info['type']]; if ($info['allows_null']) { $actualTypes[] = 'null'; } return $annotationTypes === $this->toComparableNames($actualTypes, $symbolShortNames); } private function toComparableNames(array $types, array $symbolShortNames) { $normalized = array_map( function ($type) use ($symbolShortNames) { $type = strtolower($type); if (isset($symbolShortNames[$type])) { return $symbolShortNames[$type]; } return $type; }, $types ); sort($normalized); return $normalized; } } isTokenKindFound(T_DOC_COMMENT); } public function getDefinition() { return new FixerDefinition( 'No alias PHPDoc tags should be used.', [ new CodeSample( ' ['link' => 'website']] ), ] ); } public function getPriority() { return 11; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $searchFor = array_keys($this->configuration['replacements']); foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType($searchFor); if (empty($annotations)) { continue; } foreach ($annotations as $annotation) { $annotation->getTag()->setName($this->configuration['replacements'][$annotation->getTag()->getName()]); } $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('replacements', [ (new FixerOptionBuilder('replacements', 'Mapping between replaced annotations with new ones.')) ->setAllowedTypes(['array']) ->setNormalizer(static function (Options $options, $value) { $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) || false !== strpos($to, '*/')) { throw new InvalidOptionsException(sprintf( 'Tag "%s" cannot be replaced by invalid tag "%s".', $from, $to )); } $normalizedValue[trim($from)] = trim($to); } foreach ($normalizedValue as $from => $to) { if (isset($normalizedValue[$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([ 'property-read' => 'property', 'property-write' => 'property', 'type' => 'var', 'link' => 'see', ]) ->getOption(), ], $this->getName()); } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { 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()]); } } private function fixDescription(DocBlock $doc) { foreach ($doc->getLines() as $index => $line) { if ($line->containsATag()) { break; } if ($line->containsUsefulContent()) { $next = $doc->getLine($index + 1); if ($next->containsATag()) { $line->addBlank(); break; } } } } private function fixAnnotations(DocBlock $doc) { foreach ($doc->getAnnotations() as $index => $annotation) { $next = $doc->getAnnotation($index + 1); if (null === $next) { break; } if (true === $next->getTag()->valid()) { if (TagComparator::shouldBeTogether($annotation->getTag(), $next->getTag())) { $this->ensureAreTogether($doc, $annotation, $next); } else { $this->ensureAreSeparate($doc, $annotation, $next); } } } return $doc->getContent(); } private function ensureAreTogether(DocBlock $doc, Annotation $first, Annotation $second) { $pos = $first->getEnd(); $final = $second->getStart(); for ($pos = $pos + 1; $pos < $final; ++$pos) { $doc->getLine($pos)->remove(); } } private function ensureAreSeparate(DocBlock $doc, Annotation $first, Annotation $second) { $pos = $first->getEnd(); $final = $second->getStart() - 1; if ($pos === $final) { $doc->getLine($pos)->addBlank(); return; } for ($pos = $pos + 1; $pos < $final; ++$pos) { $doc->getLine($pos)->remove(); } } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { 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, $summaryEnd) { $nonBlankLineAfterSummary = $this->findNonBlankLine($doc, $summaryEnd); $this->removeExtraBlankLinesBetween($doc, $summaryEnd, $nonBlankLineAfterSummary); } private function fixDescription(DocBlock $doc, $summaryEnd) { $annotationStart = $this->findFirstAnnotationOrEnd($doc); $descriptionEnd = $this->reverseFindLastUsefulContent($doc, $annotationStart); if (null === $descriptionEnd || $summaryEnd === $descriptionEnd) { return; } if ($annotationStart === \count($doc->getLines()) - 1) { return; } $this->removeExtraBlankLinesBetween($doc, $descriptionEnd, $annotationStart); } private function fixAllTheRest(DocBlock $doc) { $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, $from, $to) { 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) { if (!$current->isTheEnd() && !$current->containsUsefulContent() && !$next->isTheEnd() && !$next->containsUsefulContent()) { $current->remove(); } } private function findNonBlankLine(DocBlock $doc, $after) { foreach ($doc->getLines() as $index => $line) { if ($index <= $after) { continue; } if ($line->containsATag() || $line->containsUsefulContent() || $line->isTheEnd()) { return $index; } } } private function findFirstAnnotationOrEnd(DocBlock $doc) { $index = null; foreach ($doc->getLines() as $index => $line) { if ($line->containsATag()) { return $index; } } return $index; } private function reverseFindLastUsefulContent(DocBlock $doc, $from) { for ($index = $from - 1; $index >= 0; --$index) { if ($doc->getLine($index)->containsUsefulContent()) { return $index; } } return null; } } isTokenKindFound(T_NAMESPACE); } public function getPriority() { return -21; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_NAMESPACE)) { $this->fixLinesBeforeNamespace($tokens, $index, 2, 2); } } } } isTokenKindFound(T_NAMESPACE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $ending = $this->whitespacesConfig->getLineEnding(); $lastIndex = $tokens->count() - 1; for ($index = $lastIndex; $index >= 0; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_NAMESPACE)) { continue; } $semicolonIndex = $tokens->getNextTokenOfKind($index, [';', '{', [T_CLOSE_TAG]]); $semicolonToken = $tokens[$semicolonIndex]; if (!isset($tokens[$semicolonIndex + 1]) || !$semicolonToken->equals(';')) { continue; } $nextIndex = $semicolonIndex + 1; $nextToken = $tokens[$nextIndex]; if (!$nextToken->isWhitespace()) { $tokens->insertAt($semicolonIndex + 1, new Token([T_WHITESPACE, $ending.$ending])); } else { $tokens[$nextIndex] = new Token([ T_WHITESPACE, ($nextIndex === $lastIndex ? $ending : $ending.$ending).ltrim($nextToken->getContent()), ]); } } } } isTokenKindFound(T_NAMESPACE); } public function getDefinition() { return new FixerDefinition( 'The namespace declaration line shouldn\'t contain leading whitespace.', [ new CodeSample( 'isGivenKind(T_NAMESPACE)) { continue; } $beforeNamespaceIndex = $index - 1; $beforeNamespace = $tokens[$beforeNamespaceIndex]; if (!$beforeNamespace->isWhitespace()) { if (!self::endsWithWhitespace($beforeNamespace->getContent())) { $tokens->insertAt($index, new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding()])); } continue; } $lastNewline = strrpos($beforeNamespace->getContent(), "\n"); if (false === $lastNewline) { $beforeBeforeNamespace = $tokens[$index - 2]; if (self::endsWithWhitespace($beforeBeforeNamespace->getContent())) { $tokens->clearAt($beforeNamespaceIndex); } else { $tokens[$beforeNamespaceIndex] = new Token([T_WHITESPACE, ' ']); } } else { $tokens[$beforeNamespaceIndex] = new Token([T_WHITESPACE, substr($beforeNamespace->getContent(), 0, $lastNewline + 1)]); } } } private static function endsWithWhitespace($str) { if ('' === $str) { return false; } return '' === trim(substr($str, -1)); } } isTokenKindFound(T_NAMESPACE); } public function getDefinition() { return new FixerDefinition( 'There should be no blank lines before a namespace declaration.', [ new CodeSample( "count(); $index < $limit; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_NAMESPACE)) { continue; } $this->fixLinesBeforeNamespace($tokens, $index, 0, 1); } } } '0', '0' => '0', 'I' => '1', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', 'Α' => 'A', 'А' => 'A', 'A' => 'A', 'ʙ' => 'B', 'Β' => 'B', 'В' => 'B', 'B' => 'B', 'Ϲ' => 'C', 'С' => 'C', 'Ⅽ' => 'C', 'C' => 'C', 'Ⅾ' => 'D', 'D' => 'D', 'Ε' => 'E', 'Е' => 'E', 'E' => 'E', 'Ϝ' => 'F', 'F' => 'F', 'ɢ' => 'G', 'Ԍ' => 'G', 'G' => 'G', 'ʜ' => 'H', 'Η' => 'H', 'Н' => 'H', 'H' => 'H', 'l' => 'I', 'Ι' => 'I', 'І' => 'I', 'Ⅰ' => 'I', 'I' => 'I', 'Ј' => 'J', 'J' => 'J', 'Κ' => 'K', 'К' => 'K', 'K' => 'K', 'K' => 'K', 'ʟ' => 'L', 'Ⅼ' => 'L', 'L' => 'L', 'Μ' => 'M', 'М' => 'M', 'Ⅿ' => 'M', 'M' => 'M', 'ɴ' => 'N', 'Ν' => 'N', 'N' => 'N', 'Ο' => 'O', 'О' => 'O', 'O' => 'O', 'Ρ' => 'P', 'Р' => 'P', 'P' => 'P', 'Q' => 'Q', 'ʀ' => 'R', 'R' => 'R', 'Ѕ' => 'S', 'S' => 'S', 'Τ' => 'T', 'Т' => 'T', 'T' => 'T', 'U' => 'U', 'Ѵ' => 'V', 'Ⅴ' => 'V', 'V' => 'V', 'W' => 'W', 'Χ' => 'X', 'Х' => 'X', 'Ⅹ' => 'X', 'X' => 'X', 'ʏ' => 'Y', 'Υ' => 'Y', 'Ү' => 'Y', 'Y' => 'Y', 'Ζ' => 'Z', 'Z' => 'Z', '_' => '_', 'ɑ' => 'a', 'а' => 'a', 'a' => 'a', 'Ь' => 'b', 'b' => 'b', 'ϲ' => 'c', 'с' => 'c', 'ⅽ' => 'c', 'c' => 'c', 'ԁ' => 'd', 'ⅾ' => 'd', 'd' => 'd', 'е' => 'e', 'e' => 'e', 'f' => 'f', 'ɡ' => 'g', 'g' => 'g', 'һ' => 'h', 'h' => 'h', 'ɩ' => 'i', 'і' => 'i', 'ⅰ' => 'i', 'i' => 'i', 'ј' => 'j', 'j' => 'j', 'k' => 'k', 'ⅼ' => 'l', 'l' => 'l', 'ⅿ' => 'm', 'm' => 'm', 'n' => 'n', 'ο' => 'o', 'о' => 'o', 'o' => 'o', 'р' => 'p', 'p' => 'p', 'q' => 'q', 'r' => 'r', 'ѕ' => 's', 's' => 's', 't' => 't', 'u' => 'u', 'ν' => 'v', 'ѵ' => 'v', 'ⅴ' => 'v', 'v' => 'v', 'ѡ' => 'w', 'w' => 'w', 'х' => 'x', 'ⅹ' => 'x', 'x' => 'x', 'у' => 'y', 'y' => 'y', 'z' => 'z', ]; public function getDefinition() { return new FixerDefinition( 'Replace accidental usage of homoglyphs (non ascii characters) in names.', [new CodeSample("isAnyTokenKindsFound([T_VARIABLE, T_STRING]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind([T_VARIABLE, T_STRING])) { continue; } $replaced = Preg::replaceCallback('/[^[:ascii:]]/u', static function ($matches) { return isset(self::$replacements[$matches[0]]) ? self::$replacements[$matches[0]] : $matches[0] ; }, $token->getContent(), -1, $count); if ($count) { $tokens->offsetSet($index, new Token([$token->getId(), $replaced])); } } } } isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $this->fixSpacing($index, $tokens); } } } private function fixSpacing($index, Tokens $tokens) { if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $startIndex = $index; $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); } else { $startIndex = $tokens->getNextTokenOfKind($index, ['(']); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); } for ($i = $endIndex - 1; $i > $startIndex; --$i) { $i = $this->skipNonArrayElements($i, $tokens); if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) { $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); } } } private function skipNonArrayElements($index, Tokens $tokens) { if ($tokens[$index]->equals('}')) { return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); } if ($tokens[$index]->equals(')')) { $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $startIndex = $tokens->getPrevMeaningfulToken($startIndex); if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { return $startIndex; } } return $index; } } ` should not be surrounded by multi-line whitespaces.', [new CodeSample(" 2);\n")] ); } public function getPriority() { return 1; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOUBLE_ARROW); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOUBLE_ARROW)) { continue; } $this->fixWhitespace($tokens, $index - 1); if (!$tokens[$index + 2]->isComment()) { $this->fixWhitespace($tokens, $index + 1); } } } private function fixWhitespace(Tokens $tokens, $index) { $token = $tokens[$index]; if ($token->isWhitespace() && !$token->isWhitespace(" \t")) { $tokens[$index] = new Token([T_WHITESPACE, rtrim($token->getContent()).' ']); } } } isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $this->fixSpacing($index, $tokens); } } } private function fixSpacing($index, Tokens $tokens) { if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $startIndex = $index; $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); } else { $startIndex = $tokens->getNextTokenOfKind($index, ['(']); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); } for ($i = $endIndex - 1; $i > $startIndex; --$i) { $i = $this->skipNonArrayElements($i, $tokens); $currentToken = $tokens[$i]; $prevIndex = $tokens->getPrevNonWhitespace($i - 1); if ($currentToken->equals(',') && !$tokens[$prevIndex]->equals([T_END_HEREDOC]) && !$tokens[$prevIndex]->isComment()) { $tokens->removeLeadingWhitespace($i); } } } private function skipNonArrayElements($index, Tokens $tokens) { if ($tokens[$index]->equals('}')) { return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); } if ($tokens[$index]->equals(')')) { $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $startIndex = $tokens->getPrevMeaningfulToken($startIndex); if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { return $startIndex; } } return $index; } } isTokenKindFound(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) { $tokens[$index] = new Token('['); } elseif ($token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE)) { $tokens[$index] = new Token(']'); } } } } isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { self::fixArray($tokens, $index); } } } private static function fixArray(Tokens $tokens, $index) { $startIndex = $index; if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { $startIndex = $tokens->getNextMeaningfulToken($startIndex); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); } else { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); } $nextIndex = $startIndex + 1; $nextToken = $tokens[$nextIndex]; $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startIndex); $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; $tokenAfterNextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex + 1]; $prevIndex = $endIndex - 1; $prevToken = $tokens[$prevIndex]; $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($endIndex); $prevNonWhitespaceToken = $tokens[$prevNonWhitespaceIndex]; if ( $nextToken->isWhitespace(" \t") && ( !$nextNonWhitespaceToken->isComment() || $nextNonWhitespaceIndex === $prevNonWhitespaceIndex || $tokenAfterNextNonWhitespaceToken->isWhitespace(" \t") || '/*' === substr($nextNonWhitespaceToken->getContent(), 0, 2) ) ) { $tokens->clearAt($nextIndex); } if ( $prevToken->isWhitespace(" \t") && !$prevNonWhitespaceToken->equals(',') ) { $tokens->clearAt($prevIndex); } } } isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { if ($tokensAnalyzer->isArray($index)) { $this->fixArray($tokens, $index); } } } private function fixArray(Tokens $tokens, $index) { $tokensAnalyzer = new TokensAnalyzer($tokens); if ($tokensAnalyzer->isArrayMultiLine($index)) { return; } $startIndex = $index; if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { $startIndex = $tokens->getNextTokenOfKind($startIndex, ['(']); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); } else { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); } $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); $beforeEndToken = $tokens[$beforeEndIndex]; if ($beforeEndToken->equals(',')) { $tokens->removeTrailingWhitespace($beforeEndIndex); $tokens->clearAt($beforeEndIndex); } } } resolveCandidateTokenKind(); $this->resolveFixCallback(); } public function getDefinition() { return new FixerDefinition( 'PHP arrays should be declared using the configured syntax.', [ new CodeSample( " 'short'] ), ] ); } public function getPriority() { return 1; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound($this->candidateTokenKind); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $callback = $this->fixCallback; for ($index = $tokens->count() - 1; 0 <= $index; --$index) { if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) { $this->{$callback}($tokens, $index); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` array syntax.')) ->setAllowedValues(['long', 'short']) ->setDefault('long') ->getOption(), ]); } private function fixToLongArraySyntax(Tokens $tokens, $index) { $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); $tokens[$index] = new Token('('); $tokens[$closeIndex] = new Token(')'); $tokens->insertAt($index, new Token([T_ARRAY, 'array'])); } private function fixToShortArraySyntax(Tokens $tokens, $index) { $openIndex = $tokens->getNextTokenOfKind($index, ['(']); $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); $tokens[$openIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']); $tokens[$closeIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']); $tokens->clearTokenAndMergeSurroundingWhitespace($index); } private function resolveFixCallback() { $this->fixCallback = sprintf('fixTo%sArraySyntax', ucfirst($this->configuration['syntax'])); } private function resolveCandidateTokenKind() { $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_ARRAY_SQUARE_BRACE_OPEN : T_ARRAY; } } isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 1; $index >= 0; --$index) { if ($tokensAnalyzer->isArray($index) && $tokensAnalyzer->isArrayMultiLine($index)) { $this->fixArray($tokens, $index); } } } private function fixArray(Tokens $tokens, $index) { $startIndex = $index; if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { $startIndex = $tokens->getNextTokenOfKind($startIndex, ['(']); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); } else { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); } $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); $beforeEndToken = $tokens[$beforeEndIndex]; if ($startIndex !== $beforeEndIndex && !$beforeEndToken->equalsAny([',', [T_END_HEREDOC]])) { $tokens->insertAt($beforeEndIndex + 1, new Token(',')); $endToken = $tokens[$endIndex]; if (!$endToken->isComment() && !$endToken->isWhitespace()) { $tokens->ensureWhitespaceAtIndex($endIndex, 1, ' '); } } } } isAnyTokenKindsFound(Token::getKeywords()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isKeyword() && !$token->isGivenKind(self::$excludedTokens)) { $tokens[$index] = new Token([$token->getId(), strtolower($token->getContent())]); } } } } isAnyTokenKindsFound([T_STATIC, T_STRING]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->equalsAny([[T_STRING, 'self'], [T_STATIC, 'static'], [T_STRING, 'parent']], false)) { continue; } $newContent = strtolower($token->getContent()); if ($token->getContent() === $newContent) { continue; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR, T_OBJECT_OPERATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { continue; } $nextIndex = $tokens->getNextMeaningfulToken($index); if ($tokens[$nextIndex]->isGivenKind([T_FUNCTION, T_NS_SEPARATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { continue; } if ('static' === $newContent && $tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { continue; } $tokens[$index] = new Token([$token->getId(), $newContent]); } } } isAnyTokenKindsFound($this->getMagicConstantTokens()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $magicConstants = $this->getMagicConstants(); $magicConstantTokens = $this->getMagicConstantTokens(); foreach ($tokens as $index => $token) { if ($token->isGivenKind($magicConstantTokens)) { $tokens[$index] = new Token([$token->getId(), $magicConstants[$token->getId()]]); } } } private function getMagicConstants() { static $magicConstants = null; if (null === $magicConstants) { $magicConstants = [ T_LINE => '__LINE__', T_FILE => '__FILE__', T_DIR => '__DIR__', T_FUNC_C => '__FUNCTION__', T_CLASS_C => '__CLASS__', T_METHOD_C => '__METHOD__', T_NS_C => '__NAMESPACE__', CT::T_CLASS_CONSTANT => 'class', T_TRAIT_C => '__TRAIT__', ]; } return $magicConstants; } private function getMagicConstantTokens() { static $magicConstantTokens = null; if (null === $magicConstantTokens) { $magicConstantTokens = array_keys($this->getMagicConstants()); } return $magicConstantTokens; } } isTokenKindFound(T_STRING); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isNativeConstant()) { continue; } if ( $this->isNeighbourAccepted($tokens, $tokens->getPrevMeaningfulToken($index)) && $this->isNeighbourAccepted($tokens, $tokens->getNextMeaningfulToken($index)) ) { $tokens[$index] = new Token([$token->getId(), strtolower($token->getContent())]); } } } private function isNeighbourAccepted(Tokens $tokens, $index) { static $forbiddenTokens = [ T_AS, T_CLASS, T_CONST, T_EXTENDS, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_INTERFACE, T_NEW, T_NS_SEPARATOR, T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM, T_TRAIT, T_USE, CT::T_USE_TRAIT, CT::T_USE_LAMBDA, ]; $token = $tokens[$index]; if ($token->equalsAny(['{', '}'])) { return false; } return !$token->isGivenKind($forbiddenTokens); } } isTokenKindFound(T_STRING); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $nativeFunctionNames = null; if (null === $nativeFunctionNames) { $nativeFunctionNames = $this->getNativeFunctionNames(); } for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { if (!$tokens[$index]->isGivenKind(T_STRING)) { continue; } $next = $tokens->getNextMeaningfulToken($index); if (!$tokens[$next]->equals('(')) { $index = $next; continue; } $functionNamePrefix = $tokens->getPrevMeaningfulToken($index); if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) { continue; } if ($tokens[$functionNamePrefix]->isGivenKind(T_NS_SEPARATOR)) { $prev = $tokens->getPrevMeaningfulToken($functionNamePrefix); if ($tokens[$prev]->isGivenKind([T_STRING, T_NEW])) { continue; } } $lower = strtolower($tokens[$index]->getContent()); if (!\array_key_exists($lower, $nativeFunctionNames)) { continue; } $tokens[$index] = new Token([T_STRING, $nativeFunctionNames[$lower]]); $index = $next; } } private function getNativeFunctionNames() { $allFunctions = get_defined_functions(); $functions = []; foreach ($allFunctions['internal'] as $function) { $functions[strtolower($function)] = $function; } return $functions; } } '__call', '__callstatic' => '__callStatic', '__clone' => '__clone', '__construct' => '__construct', '__debuginfo' => '__debugInfo', '__destruct' => '__destruct', '__get' => '__get', '__invoke' => '__invoke', '__isset' => '__isset', '__set' => '__set', '__set_state' => '__set_state', '__sleep' => '__sleep', '__tostring' => '__toString', '__unset' => '__unset', '__wakeup' => '__wakeup', ]; public function getDefinition() { return new FixerDefinition( 'Magic method definitions and calls must be using the correct casing.', [ new CodeSample( '__INVOKE(1); ' ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_OBJECT_OPERATOR, T_DOUBLE_COLON]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $inClass = 0; $tokenCount = \count($tokens); for ($index = 1; $index < $tokenCount - 2; ++$index) { if (0 === $inClass && $tokens[$index]->isClassy()) { $inClass = 1; $index = $tokens->getNextTokenOfKind($index, ['{']); continue; } if (0 !== $inClass) { if ($tokens[$index]->equals('{')) { ++$inClass; continue; } if ($tokens[$index]->equals('}')) { --$inClass; continue; } } if (!$tokens[$index]->isGivenKind(T_STRING)) { continue; } $content = $tokens[$index]->getContent(); if ('__' !== substr($content, 0, 2)) { continue; } $name = strtolower($content); if (!$this->isMagicMethodName($name)) { continue; } $nameInCorrectCasing = $this->getMagicMethodNameInCorrectCasing($name); if ($nameInCorrectCasing === $content) { continue; } if ($this->isFunctionSignature($tokens, $index)) { if (0 !== $inClass) { $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); } continue; } if ($this->isMethodCall($tokens, $index)) { $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); continue; } if ( ('__callstatic' === $name || '__set_state' === $name) && $this->isStaticMethodCall($tokens, $index) ) { $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); } } } private function isFunctionSignature(Tokens $tokens, $index) { $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) { return false; } return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); } private function isMethodCall(Tokens $tokens, $index) { $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->equals([T_OBJECT_OPERATOR, '->'])) { return false; } return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); } private function isStaticMethodCall(Tokens $tokens, $index) { $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON)) { return false; } return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); } private function isMagicMethodName($name) { return isset(self::$magicNames[$name]); } private function getMagicMethodNameInCorrectCasing($name) { return self::$magicNames[$name]; } private function setTokenToCorrectCasing(Tokens $tokens, $index, $nameInCorrectCasing) { $tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]); } } isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $map = null; if (null === $map) { $trueToken = new Token([T_STRING, 'true']); $map = [ 'array_keys' => [null, null, $trueToken], 'array_search' => [null, null, $trueToken], 'base64_decode' => [null, $trueToken], 'in_array' => [null, null, $trueToken], 'mb_detect_encoding' => [null, [new Token([T_STRING, 'mb_detect_order']), new Token('('), new Token(')')], $trueToken], ]; } for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; $previousIndex = $tokens->getPrevMeaningfulToken($index); if (null !== $previousIndex && $tokens[$previousIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { continue; } $lowercaseContent = strtolower($token->getContent()); if ($token->isGivenKind(T_STRING) && isset($map[$lowercaseContent])) { $this->fixFunction($tokens, $index, $map[$lowercaseContent]); } } } private function fixFunction(Tokens $tokens, $functionIndex, array $functionParams) { $startBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startBraceIndex); $paramsQuantity = 0; $expectParam = true; for ($index = $startBraceIndex + 1; $index < $endBraceIndex; ++$index) { $token = $tokens[$index]; if ($expectParam && !$token->isWhitespace() && !$token->isComment()) { ++$paramsQuantity; $expectParam = false; } if ($token->equals('(')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); continue; } if ($token->equals(',')) { $expectParam = true; continue; } } $functionParamsQuantity = \count($functionParams); if ($paramsQuantity === $functionParamsQuantity) { return; } $tokensToInsert = []; for ($i = $paramsQuantity; $i < $functionParamsQuantity; ++$i) { if (!$functionParams[$i]) { return; } $tokensToInsert[] = new Token(','); $tokensToInsert[] = new Token([T_WHITESPACE, ' ']); if (!\is_array($functionParams[$i])) { $tokensToInsert[] = clone $functionParams[$i]; continue; } foreach ($functionParams[$i] as $param) { $tokensToInsert[] = clone $param; } } $beforeEndBraceIndex = $tokens->getTokenNotOfKindSibling($endBraceIndex, -1, [[T_WHITESPACE], ',']); $tokens->insertAt($beforeEndBraceIndex + 1, $tokensToInsert); } } isAnyTokenKindsFound([T_IS_EQUAL, T_IS_NOT_EQUAL]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $map = [ T_IS_EQUAL => [ 'id' => T_IS_IDENTICAL, 'content' => '===', ], T_IS_NOT_EQUAL => [ 'id' => T_IS_NOT_IDENTICAL, 'content' => '!==', ], ]; foreach ($tokens as $index => $token) { $tokenId = $token->getId(); if (isset($map[$tokenId])) { $tokens[$index] = new Token([$map[$tokenId]['id'], $map[$tokenId]['content']]); } } } } = 7.0.', [ new VersionSpecificCodeSample( "= 70000 && $tokens[0]->isGivenKind(T_OPEN_TAG); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $searchIndex = $tokens->getNextMeaningfulToken(0); if (null === $searchIndex) { $this->insertSequence($tokens); return; } $sequence = $this->getDeclareStrictTypeSequence(); $sequenceLocation = $tokens->findSequence($sequence, $searchIndex, null, false); if (null === $sequenceLocation) { $this->insertSequence($tokens); return; } $this->fixStrictTypesCasing($tokens, $sequenceLocation); } private function getDeclareStrictTypeSequence() { static $sequence = null; if (null === $sequence) { $sequence = [ new Token([T_DECLARE, 'declare']), new Token('('), new Token([T_STRING, 'strict_types']), new Token('='), new Token([T_LNUMBER, '1']), new Token(')'), ]; } return $sequence; } private function fixStrictTypesCasing(Tokens $tokens, array $sequence) { foreach ($sequence as $index => $token) { if ($token->isGivenKind(T_STRING)) { $tokens[$index] = new Token([T_STRING, strtolower($token->getContent())]); break; } } } private function insertSequence(Tokens $tokens) { $sequence = $this->getDeclareStrictTypeSequence(); $sequence[] = new Token(';'); $endIndex = \count($sequence); $tokens->insertAt(1, $sequence); if (false !== strpos($tokens[0]->getContent(), "\n")) { $tokens[0] = new Token([$tokens[0]->getId(), trim($tokens[0]->getContent()).' ']); } if ($endIndex === \count($tokens) - 1) { return; } $lineEnding = $this->whitespacesConfig->getLineEnding(); if (!$tokens[1 + $endIndex]->isWhitespace()) { $tokens->insertAt(1 + $endIndex, new Token([T_WHITESPACE, $lineEnding])); return; } $content = $tokens[1 + $endIndex]->getContent(); $tokens[1 + $endIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]); } } isTokenKindFound('}'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($this->findCurlyBraceOpen($tokens) as $index) { if ($this->isOverComplete($tokens, $index)) { $this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); } } } private function clearOverCompleteBraces(Tokens $tokens, $openIndex, $closeIndex) { $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); } private function findCurlyBraceOpen(Tokens $tokens) { for ($i = \count($tokens) - 1; $i > 0; --$i) { if ($tokens[$i]->equals('{')) { yield $i; } } } private function isOverComplete(Tokens $tokens, $index) { static $whiteList = ['{', '}', [T_OPEN_TAG], ':', ';']; return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($whiteList); } } isAnyTokenKindsFound( [ T_ENDIF, T_ENDWHILE, T_ENDFOREACH, T_ENDFOR, ] ); } public function getPriority() { return 1; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; $this->fixElseif($index, $token, $tokens); $this->fixElse($index, $token, $tokens); $this->fixOpenCloseControls($index, $token, $tokens); } } private function findParenthesisEnd(Tokens $tokens, $structureTokenIndex) { $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); $nextToken = $tokens[$nextIndex]; if (!$nextToken->equals('(')) { return $structureTokenIndex; } return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); } private function fixOpenCloseControls($index, Token $token, Tokens $tokens) { if ($token->isGivenKind([T_IF, T_FOREACH, T_WHILE, T_FOR])) { $openIndex = $tokens->getNextTokenOfKind($index, ['(']); $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); $afterParenthesisIndex = $tokens->getNextNonWhitespace($closeIndex); $afterParenthesis = $tokens[$afterParenthesisIndex]; if (!$afterParenthesis->equals(':')) { return; } $items = []; if (!$tokens[$afterParenthesisIndex - 1]->isWhitespace()) { $items[] = new Token([T_WHITESPACE, ' ']); } $items[] = new Token('{'); if (!$tokens[$afterParenthesisIndex + 1]->isWhitespace()) { $items[] = new Token([T_WHITESPACE, ' ']); } $tokens->clearAt($afterParenthesisIndex); $tokens->insertAt($afterParenthesisIndex, $items); } if (!$token->isGivenKind([T_ENDIF, T_ENDFOREACH, T_ENDWHILE, T_ENDFOR])) { return; } $nextTokenIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextTokenIndex]; $tokens[$index] = new Token('}'); if ($nextToken->equals(';')) { $tokens->clearAt($nextTokenIndex); } } private function fixElse($index, Token $token, Tokens $tokens) { if (!$token->isGivenKind(T_ELSE)) { return; } $tokenAfterElseIndex = $tokens->getNextMeaningfulToken($index); $tokenAfterElse = $tokens[$tokenAfterElseIndex]; if (!$tokenAfterElse->equals(':')) { return; } $this->addBraces($tokens, new Token([T_ELSE, 'else']), $index, $tokenAfterElseIndex); } private function fixElseif($index, Token $token, Tokens $tokens) { if (!$token->isGivenKind(T_ELSEIF)) { return; } $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $tokenAfterParenthesisIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); $tokenAfterParenthesis = $tokens[$tokenAfterParenthesisIndex]; if (!$tokenAfterParenthesis->equals(':')) { return; } $this->addBraces($tokens, new Token([T_ELSEIF, 'elseif']), $index, $tokenAfterParenthesisIndex); } private function addBraces(Tokens $tokens, Token $token, $index, $colonIndex) { $items = [ new Token('}'), new Token([T_WHITESPACE, ' ']), $token, ]; if (!$tokens[$index + 1]->isWhitespace()) { $items[] = new Token([T_WHITESPACE, ' ']); } $tokens->clearAt($index); $tokens->insertAt( $index, $items ); $colonIndex += \count($items); $items = [new Token('{')]; if (!$tokens[$colonIndex + 1]->isWhitespace()) { $items[] = new Token([T_WHITESPACE, ' ']); } $tokens->clearAt($colonIndex); $tokens->insertAt( $colonIndex, $items ); } } isTokenKindFound(T_ELSE); } public function getDefinition() { return new FixerDefinition( 'There should not be useless `else` cases.', [ new CodeSample(" $token) { if (!$token->isGivenKind(T_ELSE)) { continue; } if ($tokens[$tokens->getNextMeaningfulToken($index)]->equalsAny([':', [T_IF]])) { continue; } $this->fixEmptyElse($tokens, $index); if ($tokens->isEmptyAt($index)) { continue; } if ($this->isSuperfluousElse($tokens, $index)) { $this->clearElse($tokens, $index); } } } private function fixEmptyElse(Tokens $tokens, $index) { $next = $tokens->getNextMeaningfulToken($index); if ($tokens[$next]->equals('{')) { $close = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $next); if (1 === $close - $next) { $this->clearElse($tokens, $index); } elseif ($tokens->getNextMeaningfulToken($next) === $close) { $this->clearElse($tokens, $index); } return; } $end = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); if ($next === $end) { $this->clearElse($tokens, $index); } } private function clearElse(Tokens $tokens, $index) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); $next = $tokens->getNextMeaningfulToken($index); if (!$tokens[$next]->equals('{')) { return; } $tokens->clearTokenAndMergeSurroundingWhitespace($tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $next)); $tokens->clearTokenAndMergeSurroundingWhitespace($next); } } isAllTokenKindsFound([T_IF, T_ELSE]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_ELSE)) { continue; } $ifTokenIndex = $tokens->getNextMeaningfulToken($index); if (!$tokens[$ifTokenIndex]->isGivenKind(T_IF)) { continue; } $conditionEndBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($ifTokenIndex)); $afterConditionIndex = $tokens->getNextMeaningfulToken($conditionEndBraceIndex); if ($tokens[$afterConditionIndex]->equals(':')) { continue; } $tokens->clearAt($index + 1); $tokens[$index] = new Token([T_ELSEIF, 'elseif']); $tokens->clearAt($ifTokenIndex); $beforeIfTokenIndex = $tokens->getPrevNonWhitespace($ifTokenIndex); if ($tokens[$beforeIfTokenIndex]->isComment() && $tokens[$ifTokenIndex + 1]->isWhitespace()) { $tokens->clearAt($ifTokenIndex + 1); } } } } resolveConfiguration(); } public function getDefinition() { return new FixerDefinition( 'Write conditions in Yoda style (`true`), non-Yoda style (`false`) or ignore those conditions (`null`) based on configuration.', [ new CodeSample( ' 3; // less than ', [ 'equal' => true, 'identical' => false, 'less_and_greater' => null, ] ), new CodeSample( ' true, ] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound($this->candidateTypes); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->fixTokens($tokens); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('equal', 'Style for equal (`==`, `!=`) statements.')) ->setAllowedTypes(['bool', 'null']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('identical', 'Style for identical (`===`, `!==`) statements.')) ->setAllowedTypes(['bool', 'null']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('less_and_greater', 'Style for less and greater than (`<`, `<=`, `>`, `>=`) statements.')) ->setAllowedTypes(['bool', 'null']) ->setDefault(null) ->getOption(), (new FixerOptionBuilder('always_move_variable', 'Whether variables should always be on non assignable side when applying Yoda style.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } private function findComparisonEnd(Tokens $tokens, $index) { ++$index; $count = \count($tokens); while ($index < $count) { $token = $tokens[$index]; if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { ++$index; continue; } if ($this->isOfLowerPrecedence($token)) { break; } $block = Tokens::detectBlockType($token); if (null === $block) { ++$index; continue; } if (!$block['isStart']) { break; } $index = $tokens->findBlockEnd($block['type'], $index) + 1; } $prev = $tokens->getPrevMeaningfulToken($index); return $tokens[$prev]->isGivenKind(T_CLOSE_TAG) ? $tokens->getPrevMeaningfulToken($prev) : $prev; } private function findComparisonStart(Tokens $tokens, $index) { --$index; $nonBlockFound = false; while (0 <= $index) { $token = $tokens[$index]; if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { --$index; continue; } if ($this->isOfLowerPrecedence($token)) { break; } $block = Tokens::detectBlockType($token); if (null === $block) { --$index; $nonBlockFound = true; continue; } if ( $block['isStart'] || ($nonBlockFound && Tokens::BLOCK_TYPE_CURLY_BRACE === $block['type']) ) { break; } $index = $tokens->findBlockStart($block['type'], $index) - 1; } return $tokens->getNextMeaningfulToken($index); } private function fixTokens(Tokens $tokens) { for ($i = \count($tokens) - 1; $i > 1; --$i) { if ($tokens[$i]->isGivenKind($this->candidateTypes)) { $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getId()]; } elseif ( ($tokens[$i]->equals('<') && \in_array('<', $this->candidateTypes, true)) || ($tokens[$i]->equals('>') && \in_array('>', $this->candidateTypes, true)) ) { $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getContent()]; } else { continue; } $fixableCompareInfo = $this->getCompareFixableInfo($tokens, $i, $yoda); if (null === $fixableCompareInfo) { continue; } $i = $this->fixTokensCompare( $tokens, $fixableCompareInfo['left']['start'], $fixableCompareInfo['left']['end'], $i, $fixableCompareInfo['right']['start'], $fixableCompareInfo['right']['end'] ); } return $tokens; } private function fixTokensCompare( Tokens $tokens, $startLeft, $endLeft, $compareOperatorIndex, $startRight, $endRight ) { $type = $tokens[$compareOperatorIndex]->getId(); $content = $tokens[$compareOperatorIndex]->getContent(); if (\array_key_exists($type, $this->candidatesMap)) { $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$type]; } elseif (\array_key_exists($content, $this->candidatesMap)) { $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$content]; } $right = $this->fixTokensComparePart($tokens, $startRight, $endRight); $left = $this->fixTokensComparePart($tokens, $startLeft, $endLeft); for ($i = $startRight; $i <= $endRight; ++$i) { $tokens->clearAt($i); } for ($i = $startLeft; $i <= $endLeft; ++$i) { $tokens->clearAt($i); } $tokens->insertAt($startRight, $left); $tokens->insertAt($startLeft, $right); return $startLeft; } private function fixTokensComparePart(Tokens $tokens, $start, $end) { $newTokens = $tokens->generatePartialCode($start, $end); $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('clearAt(\count($newTokens) - 1); $newTokens->clearAt(0); $newTokens->clearEmptyTokens(); return $newTokens; } private function getCompareFixableInfo(Tokens $tokens, $index, $yoda) { $left = $this->getLeftSideCompareFixableInfo($tokens, $index); $right = $this->getRightSideCompareFixableInfo($tokens, $index); if ($yoda) { $expectedAssignableSide = $right; $expectedValueSide = $left; } else { if ($tokens[$tokens->getNextMeaningfulToken($right['end'])]->equals('=')) { return null; } $expectedAssignableSide = $left; $expectedValueSide = $right; } if ( !( !$this->isVariable($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'], false) && !$this->isListStatement($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end']) && $this->isVariable($tokens, $expectedValueSide['start'], $expectedValueSide['end'], false) ) && !( $this->configuration['always_move_variable'] && !$this->isVariable($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'], true) && !$this->isListStatement($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end']) && $this->isVariable($tokens, $expectedValueSide['start'], $expectedValueSide['end'], true) ) ) { return null; } return [ 'left' => $left, 'right' => $right, ]; } private function getLeftSideCompareFixableInfo(Tokens $tokens, $index) { return [ 'start' => $this->findComparisonStart($tokens, $index), 'end' => $tokens->getPrevMeaningfulToken($index), ]; } private function getRightSideCompareFixableInfo(Tokens $tokens, $index) { return [ 'start' => $tokens->getNextMeaningfulToken($index), 'end' => $this->findComparisonEnd($tokens, $index), ]; } private function isListStatement(Tokens $tokens, $index, $end) { for ($i = $index; $i <= $end; ++$i) { if ($tokens[$i]->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) { return true; } } return false; } private function isOfLowerPrecedence(Token $token) { static $tokens; if (null === $tokens) { $tokens = [ T_AND_EQUAL, T_BOOLEAN_AND, T_BOOLEAN_OR, T_CASE, T_CONCAT_EQUAL, T_DIV_EQUAL, T_DOUBLE_ARROW, T_GOTO, T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR, T_MINUS_EQUAL, T_MUL_EQUAL, T_OR_EQUAL, T_PLUS_EQUAL, T_RETURN, T_SL_EQUAL, T_SR_EQUAL, T_THROW, T_XOR_EQUAL, T_ECHO, T_PRINT, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, ]; if (\defined('T_POW_EQUAL')) { $tokens[] = T_POW_EQUAL; } if (\defined('T_COALESCE')) { $tokens[] = T_COALESCE; } } static $otherTokens = [ '&', '|', '^', '?', ':', '=', ',', ';', ]; return $token->isGivenKind($tokens) || $token->equalsAny($otherTokens); } private function isVariable(Tokens $tokens, $start, $end, $strict) { $tokenAnalyzer = new TokensAnalyzer($tokens); if ($start === $end) { return $tokens[$start]->isGivenKind(T_VARIABLE); } if ($strict) { if ($tokens[$start]->equals('(')) { return false; } for ($index = $start; $index <= $end; ++$index) { if ( $tokens[$index]->isCast() || $tokens[$index]->isGivenKind(T_INSTANCEOF) || $tokens[$index]->equalsAny(['.', '!']) || $tokenAnalyzer->isBinaryOperator($index) ) { return false; } } } $index = $start; while ( $tokens[$index]->equals('(') && $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) === $end ) { $index = $tokens->getNextMeaningfulToken($index); $end = $tokens->getPrevMeaningfulToken($end); } $expectString = false; while ($index <= $end) { $current = $tokens[$index]; if ($current->isComment() || $current->isWhitespace() || $tokens->isEmptyAt($index)) { ++$index; continue; } if ($index === $end) { return $current->isGivenKind($expectString ? T_STRING : T_VARIABLE); } if ($current->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) { return false; } $nextIndex = $tokens->getNextMeaningfulToken($index); $next = $tokens[$nextIndex]; if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_DOUBLE_COLON)) { $index = $tokens->getNextMeaningfulToken($nextIndex); continue; } if ($current->isGivenKind(T_NS_SEPARATOR) && $next->isGivenKind(T_STRING)) { $index = $nextIndex; continue; } if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_NS_SEPARATOR)) { $index = $nextIndex; continue; } if ($current->isGivenKind([T_STRING, T_VARIABLE]) && $next->isGivenKind(T_OBJECT_OPERATOR)) { $index = $tokens->getNextMeaningfulToken($nextIndex); $expectString = true; continue; } if ( $current->isGivenKind($expectString ? T_STRING : T_VARIABLE) && $next->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']]) ) { $index = $tokens->findBlockEnd( $next->equals('[') ? Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE : Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $nextIndex ); if ($index === $end) { return true; } $index = $tokens->getNextMeaningfulToken($index); if (!$tokens[$index]->equalsAny([[T_OBJECT_OPERATOR, '->'], '[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']])) { return false; } $index = $tokens->getNextMeaningfulToken($index); $expectString = true; continue; } if ($strict && $current->isGivenKind([T_STRING, T_VARIABLE]) && $next->equals('(')) { return false; } if ($expectString && $current->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_OPEN)) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index); if ($index === $end) { return true; } $index = $tokens->getNextMeaningfulToken($index); if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { return false; } $index = $tokens->getNextMeaningfulToken($index); $expectString = true; continue; } break; } return !$this->isConstant($tokens, $start, $end); } private function isConstant(Tokens $tokens, $index, $end) { $expectNumberOnly = false; $expectNothing = false; for (; $index <= $end; ++$index) { $token = $tokens[$index]; if ($token->isComment() || $token->isWhitespace()) { if ($expectNothing) { return false; } continue; } if ($expectNumberOnly && !$token->isGivenKind([T_LNUMBER, T_DNUMBER])) { return false; } if ($token->equals('-')) { $expectNumberOnly = true; continue; } if ( $token->isGivenKind([T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING]) || $token->equalsAny([[T_STRING, 'true'], [T_STRING, 'false'], [T_STRING, 'null']]) ) { $expectNothing = true; continue; } return false; } return true; } private function resolveConfiguration() { $candidateTypes = []; $this->candidatesMap = []; if (null !== $this->configuration['equal']) { $candidateTypes[T_IS_EQUAL] = $this->configuration['equal']; $candidateTypes[T_IS_NOT_EQUAL] = $this->configuration['equal']; } if (null !== $this->configuration['identical']) { $candidateTypes[T_IS_IDENTICAL] = $this->configuration['identical']; $candidateTypes[T_IS_NOT_IDENTICAL] = $this->configuration['identical']; } if (null !== $this->configuration['less_and_greater']) { $candidateTypes[T_IS_SMALLER_OR_EQUAL] = $this->configuration['less_and_greater']; $this->candidatesMap[T_IS_SMALLER_OR_EQUAL] = new Token([T_IS_GREATER_OR_EQUAL, '>=']); $candidateTypes[T_IS_GREATER_OR_EQUAL] = $this->configuration['less_and_greater']; $this->candidatesMap[T_IS_GREATER_OR_EQUAL] = new Token([T_IS_SMALLER_OR_EQUAL, '<=']); $candidateTypes['<'] = $this->configuration['less_and_greater']; $this->candidatesMap['<'] = new Token('>'); $candidateTypes['>'] = $this->configuration['less_and_greater']; $this->candidatesMap['>'] = new Token('<'); } $this->candidateTypesConfiguration = $candidateTypes; $this->candidateTypes = array_keys($candidateTypes); } } isAnyTokenKindsFound([T_ELSE, T_ELSEIF]); } public function getDefinition() { return new FixerDefinition( 'Replaces superfluous `elseif` with `if`.', [ new CodeSample(" $token) { if ($this->isElseif($tokens, $index) && $this->isSuperfluousElse($tokens, $index)) { $this->convertElseifToIf($tokens, $index); } } } private function isElseif(Tokens $tokens, $index) { if ($tokens[$index]->isGivenKind(T_ELSEIF)) { return true; } return $tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF); } private function convertElseifToIf(Tokens $tokens, $index) { if ($tokens[$index]->isGivenKind(T_ELSE)) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } else { $tokens[$index] = new Token([T_IF, 'if']); } $whitespace = ''; for ($previous = $index - 1; $previous > 0; --$previous) { $token = $tokens[$previous]; if ($token->isWhitespace() && Preg::match('/(\R[^\R]*)$/', $token->getContent(), $matches)) { $whitespace = $matches[1]; break; } } $previousToken = $tokens[$index - 1]; if (!$previousToken->isWhitespace()) { $tokens->insertAt($index, new Token([T_WHITESPACE, $whitespace])); } elseif (!Preg::match('/\R/', $previousToken->getContent())) { $tokens[$index - 1] = new Token([T_WHITESPACE, $whitespace]); } } } isAnyTokenKindsFound([T_CASE, T_DEFAULT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind([T_CASE, T_DEFAULT])) { continue; } $ternariesCount = 0; for ($colonIndex = $index + 1;; ++$colonIndex) { if ($tokens[$colonIndex]->equals('?')) { ++$ternariesCount; } if ($tokens[$colonIndex]->equalsAny([':', ';'])) { if (0 === $ternariesCount) { break; } --$ternariesCount; } } $valueIndex = $tokens->getPrevNonWhitespace($colonIndex); if ($valueIndex === $colonIndex - 1 || $tokens[$valueIndex]->isComment()) { continue; } $tokens->clearAt($valueIndex + 1); } } } isAnyTokenKindsFound([T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->clearIncludies($tokens, $this->findIncludies($tokens)); } private function clearIncludies(Tokens $tokens, array $includies) { foreach ($includies as $includy) { if ($includy['end'] && !$tokens[$includy['end']]->isGivenKind(T_CLOSE_TAG)) { $afterEndIndex = $tokens->getNextNonWhitespace($includy['end']); if (null === $afterEndIndex || !$tokens[$afterEndIndex]->isComment()) { $tokens->removeLeadingWhitespace($includy['end']); } } $braces = $includy['braces']; if ($braces) { $nextToken = $tokens[$tokens->getNextMeaningfulToken($braces['close'])]; if ($nextToken->equalsAny([';', [T_CLOSE_TAG]])) { $this->removeWhitespaceAroundIfPossible($tokens, $braces['open']); $this->removeWhitespaceAroundIfPossible($tokens, $braces['close']); $tokens->clearTokenAndMergeSurroundingWhitespace($braces['open']); $tokens->clearTokenAndMergeSurroundingWhitespace($braces['close']); } } $nextIndex = $tokens->getNonEmptySibling($includy['begin'], 1); if ($tokens[$nextIndex]->isWhitespace()) { $tokens[$nextIndex] = new Token([T_WHITESPACE, ' ']); } elseif ($braces || $tokens[$nextIndex]->isGivenKind([T_VARIABLE, T_CONSTANT_ENCAPSED_STRING, T_COMMENT])) { $tokens->insertAt($includy['begin'] + 1, new Token([T_WHITESPACE, ' '])); } } } private function findIncludies(Tokens $tokens) { static $includyTokenKinds = [T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]; $includies = []; foreach ($tokens->findGivenKind($includyTokenKinds) as $includyTokens) { foreach ($includyTokens as $index => $token) { $includy = [ 'begin' => $index, 'braces' => null, 'end' => $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]), ]; $nextTokenIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextTokenIndex]; if ($nextToken->equals('(')) { $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextTokenIndex); if ($tokens[$tokens->getNextMeaningfulToken($braceCloseIndex)]->equalsAny([';', [T_CLOSE_TAG]])) { $includy['braces'] = [ 'open' => $nextTokenIndex, 'close' => $braceCloseIndex, ]; } } $includies[$index] = $includy; } } krsort($includies); return $includies; } private function removeWhitespaceAroundIfPossible(Tokens $tokens, $index) { $nextIndex = $tokens->getNextNonWhitespace($index); if (null === $nextIndex || !$tokens[$nextIndex]->isComment()) { $tokens->removeLeadingWhitespace($index); } $prevIndex = $tokens->getPrevNonWhitespace($index); if (null === $prevIndex || !$tokens[$prevIndex]->isComment()) { $tokens->removeTrailingWhitespace($index); } } } ['lookupTokens' => T_BREAK, 'neededSuccessors' => [';']], 'clone' => ['lookupTokens' => T_CLONE, 'neededSuccessors' => [';', ':', ',', ')'], 'forbiddenContents' => ['?', ':']], 'continue' => ['lookupTokens' => T_CONTINUE, 'neededSuccessors' => [';']], 'echo_print' => ['lookupTokens' => [T_ECHO, T_PRINT], 'neededSuccessors' => [';', [T_CLOSE_TAG]]], 'return' => ['lookupTokens' => T_RETURN, 'neededSuccessors' => [';', [T_CLOSE_TAG]]], 'switch_case' => ['lookupTokens' => T_CASE, 'neededSuccessors' => [';', ':']], 'yield' => ['lookupTokens' => T_YIELD, 'neededSuccessors' => [';', ')']], ]; public function __construct() { parent::__construct(); if (\defined('T_COALESCE')) { self::$loops['clone']['forbiddenContents'][] = [T_COALESCE, '??']; } } public function isCandidate(Tokens $tokens) { $types = []; foreach (self::$loops as $loop) { $types[] = (array) $loop['lookupTokens']; } $types = array_merge(...$types); return $tokens->isAnyTokenKindsFound($types); } public function getDefinition() { return new FixerDefinition( 'Removes unneeded parentheses around control statements.', [ new CodeSample( ' ['break', 'continue']] ), ] ); } public function getPriority() { return 30; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $loops = array_intersect_key(self::$loops, array_flip($this->configuration['statements'])); foreach ($tokens as $index => $token) { if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { continue; } $blockStartIndex = $index; $index = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$index]; foreach ($loops as $loop) { if (!$prevToken->isGivenKind($loop['lookupTokens'])) { continue; } $blockEndIndex = $tokens->findBlockEnd( $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, $blockStartIndex ); $blockEndNextIndex = $tokens->getNextMeaningfulToken($blockEndIndex); if (!$tokens[$blockEndNextIndex]->equalsAny($loop['neededSuccessors'])) { continue; } if (\array_key_exists('forbiddenContents', $loop)) { $forbiddenTokenIndex = $tokens->getNextTokenOfKind($blockStartIndex, $loop['forbiddenContents']); if (null !== $forbiddenTokenIndex && $forbiddenTokenIndex < $blockEndIndex) { continue; } } if ($tokens[$blockStartIndex - 1]->isWhitespace() || $tokens[$blockStartIndex - 1]->isComment()) { $tokens->clearTokenAndMergeSurroundingWhitespace($blockStartIndex); } else { $tokens[$blockStartIndex] = new Token([T_WHITESPACE, ' ']); } $tokens->clearTokenAndMergeSurroundingWhitespace($blockEndIndex); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('statements', [ (new FixerOptionBuilder('statements', 'List of control statements to fix.')) ->setAllowedTypes(['array']) ->setDefault([ 'break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield', ]) ->getOption(), ], $this->getName()); } } isAnyTokenKindsFound([T_CASE, T_DEFAULT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isGivenKind([T_CASE, T_DEFAULT])) { $this->fixSwitchCase($tokens, $index); } } } protected function fixSwitchCase(Tokens $tokens, $index) { $ternariesCount = 0; do { if ($tokens[$index]->equalsAny(['(', '{'])) { $type = Tokens::detectBlockType($tokens[$index]); $index = $tokens->findBlockEnd($type['type'], $index); continue; } if ($tokens[$index]->equals('?')) { ++$ternariesCount; continue; } if ($tokens[$index]->equalsAny([':', ';'])) { if (0 === $ternariesCount) { break; } --$ternariesCount; } } while (++$index); if ($tokens[$index]->equals(';')) { $tokens[$index] = new Token(':'); } } } isTokenKindFound(T_LIST); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_LIST)) { continue; } $openIndex = $tokens->getNextMeaningfulToken($index); $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); $markIndex = null; $prevIndex = $tokens->getPrevNonWhitespace($closeIndex); while ($tokens[$prevIndex]->equals(',')) { $markIndex = $prevIndex; $prevIndex = $tokens->getPrevNonWhitespace($prevIndex); } if (null !== $markIndex) { $tokens->clearRange( $tokens->getPrevNonWhitespace($markIndex) + 1, $closeIndex - 1 ); } } } } isAnyTokenKindsFound([T_CASE, T_DEFAULT]); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('comment_text', 'The text to use in the added comment and to detect it.')) ->setAllowedTypes(['string']) ->setAllowedValues([ function ($value) { if (\is_string($value) && Preg::match('/\R/', $value)) { throw new InvalidOptionsException('The comment text must not contain new lines.'); } return true; }, ]) ->setNormalizer(function (Options $options, $value) { return rtrim($value); }) ->setDefault('no break') ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($position = \count($tokens) - 1; $position >= 0; --$position) { if ($tokens[$position]->isGivenKind([T_CASE, T_DEFAULT])) { $this->fixCase($tokens, $position); } } } private function fixCase(Tokens $tokens, $casePosition) { $empty = true; $fallThrough = true; $commentPosition = null; for ($i = $tokens->getNextTokenOfKind($casePosition, [':', ';']) + 1, $max = \count($tokens); $i < $max; ++$i) { if ($tokens[$i]->isGivenKind([T_SWITCH, T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_DO, T_FUNCTION, T_CLASS])) { $empty = false; $i = $this->getStructureEnd($tokens, $i); continue; } if ($tokens[$i]->isGivenKind([T_BREAK, T_CONTINUE, T_RETURN, T_EXIT, T_THROW, T_GOTO])) { $fallThrough = false; continue; } if ($tokens[$i]->equals('}') || $tokens[$i]->isGivenKind(T_ENDSWITCH)) { if (null !== $commentPosition) { $this->removeComment($tokens, $commentPosition); } break; } if ($this->isNoBreakComment($tokens[$i])) { $commentPosition = $i; continue; } if ($tokens[$i]->isGivenKind([T_CASE, T_DEFAULT])) { if (!$empty && $fallThrough) { if (null !== $commentPosition && $tokens->getPrevNonWhitespace($i) !== $commentPosition) { $this->removeComment($tokens, $commentPosition); $commentPosition = null; } if (null === $commentPosition) { $this->insertCommentAt($tokens, $i); } else { $text = $this->configuration['comment_text']; $tokens[$commentPosition] = new Token([ $tokens[$commentPosition]->getId(), str_ireplace($text, $text, $tokens[$commentPosition]->getContent()), ]); $this->ensureNewLineAt($tokens, $commentPosition); } } elseif (null !== $commentPosition) { $this->removeComment($tokens, $commentPosition); } break; } if (!$tokens[$i]->isGivenKind([T_COMMENT, T_WHITESPACE])) { $empty = false; } } } private function isNoBreakComment(Token $token) { if (!$token->isComment()) { return false; } $text = preg_quote($this->configuration['comment_text'], '~'); return 1 === Preg::match("~^((//|#)\\s*${text}\\s*)|(/\\*\\*?\\s*${text}\\s*\\*/)$~i", $token->getContent()); } private function insertCommentAt(Tokens $tokens, $casePosition) { $lineEnding = $this->whitespacesConfig->getLineEnding(); $newlinePosition = $this->ensureNewLineAt($tokens, $casePosition); $newlineToken = $tokens[$newlinePosition]; $nbNewlines = substr_count($newlineToken->getContent(), $lineEnding); if ($newlineToken->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $newlineToken->getContent())) { ++$nbNewlines; } elseif ($tokens[$newlinePosition - 1]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $tokens[$newlinePosition - 1]->getContent())) { ++$nbNewlines; if (!Preg::match('/\R/', $newlineToken->getContent())) { $tokens[$newlinePosition] = new Token([$newlineToken->getId(), $lineEnding.$newlineToken->getContent()]); } } if ($nbNewlines > 1) { Preg::match('/^(.*?)(\R[ \t]*)$/s', $newlineToken->getContent(), $matches); $indent = $this->getIndentAt($tokens, $newlinePosition - 1); $tokens[$newlinePosition] = new Token([$newlineToken->getId(), $matches[1].$lineEnding.$indent]); $tokens->insertAt(++$newlinePosition, new Token([T_WHITESPACE, $matches[2]])); } $tokens->insertAt($newlinePosition, new Token([T_COMMENT, '// '.$this->configuration['comment_text']])); $this->ensureNewLineAt($tokens, $newlinePosition); } private function ensureNewLineAt(Tokens $tokens, $position) { $lineEnding = $this->whitespacesConfig->getLineEnding(); $content = $lineEnding.$this->getIndentAt($tokens, $position); $whitespaceToken = $tokens[$position - 1]; if (!$whitespaceToken->isGivenKind(T_WHITESPACE)) { if ($whitespaceToken->isGivenKind(T_OPEN_TAG)) { $content = Preg::replace('/\R/', '', $content); if (!Preg::match('/\R/', $whitespaceToken->getContent())) { $tokens[$position - 1] = new Token([T_OPEN_TAG, Preg::replace('/\s+$/', $lineEnding, $whitespaceToken->getContent())]); } } if ('' !== $content) { $tokens->insertAt($position, new Token([T_WHITESPACE, $content])); return $position; } return $position - 1; } if ($tokens[$position - 2]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $tokens[$position - 2]->getContent())) { $content = Preg::replace('/^\R/', '', $content); } if (!Preg::match('/\R/', $whitespaceToken->getContent())) { $tokens[$position - 1] = new Token([T_WHITESPACE, $content]); } return $position - 1; } private function removeComment(Tokens $tokens, $commentPosition) { if ($tokens[$tokens->getPrevNonWhitespace($commentPosition)]->isGivenKind(T_OPEN_TAG)) { $whitespacePosition = $commentPosition + 1; $regex = '/^\R[ \t]*/'; } else { $whitespacePosition = $commentPosition - 1; $regex = '/\R[ \t]*$/'; } $whitespaceToken = $tokens[$whitespacePosition]; if ($whitespaceToken->isGivenKind(T_WHITESPACE)) { $content = Preg::replace($regex, '', $whitespaceToken->getContent()); if ('' !== $content) { $tokens[$whitespacePosition] = new Token([T_WHITESPACE, $content]); } else { $tokens->clearAt($whitespacePosition); } } $tokens->clearTokenAndMergeSurroundingWhitespace($commentPosition); } private function getIndentAt(Tokens $tokens, $position) { while (true) { $position = $tokens->getPrevTokenOfKind($position, [[T_WHITESPACE]]); if (null === $position) { break; } $content = $tokens[$position]->getContent(); $prevToken = $tokens[$position - 1]; if ($prevToken->isGivenKind(T_OPEN_TAG) && Preg::match('/\R$/', $prevToken->getContent())) { $content = $this->whitespacesConfig->getLineEnding().$content; } if (Preg::match('/\R([ \t]*)$/', $content, $matches)) { return $matches[1]; } } return ''; } private function getStructureEnd(Tokens $tokens, $position) { $initialToken = $tokens[$position]; if ($initialToken->isGivenKind([T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION])) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']) ); } elseif ($initialToken->isGivenKind(T_CLASS)) { $openParenthesisPosition = $tokens->getNextMeaningfulToken($position); if ('(' === $tokens[$openParenthesisPosition]->getContent()) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisPosition ); } } $position = $tokens->getNextMeaningfulToken($position); if ('{' !== $tokens[$position]->getContent()) { return $tokens->getNextTokenOfKind($position, [';']); } $position = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $position); if ($initialToken->isGivenKind(T_DO)) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']) ); return $tokens->getNextTokenOfKind($position, [';']); } return $position; } } candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN : T_LIST; } public function getDefinition() { return new FixerDefinition( 'List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1.', [ new VersionSpecificCodeSample( " 'short'] ), ] ); } public function getPriority() { return 1; } public function isCandidate(Tokens $tokens) { return \PHP_VERSION_ID >= 70100 && $tokens->isTokenKindFound($this->candidateTokenKind); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; 0 <= $index; --$index) { if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) { if (T_LIST === $this->candidateTokenKind) { $this->fixToShortSyntax($tokens, $index); } else { $this->fixToLongSyntax($tokens, $index); } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` `list` syntax.')) ->setAllowedValues(['long', 'short']) ->setDefault('long') ->getOption(), ]); } private function fixToLongSyntax(Tokens $tokens, $index) { static $typesOfInterest = [ [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE], [CT::T_ARRAY_SQUARE_BRACE_OPEN], ]; $closeIndex = $tokens->getNextTokenOfKind($index, $typesOfInterest); if (!$tokens[$closeIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE)) { return; } $tokens[$index] = new Token('('); $tokens[$closeIndex] = new Token(')'); $tokens->insertAt($index, new Token([T_LIST, 'list'])); } private function fixToShortSyntax(Tokens $tokens, $index) { $openIndex = $tokens->getNextTokenOfKind($index, ['(']); $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); $tokens[$openIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); $tokens[$closeIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } true] ), new CodeSample( $codeSample, ['double_quoted' => false] ), new CodeSample( $codeSample, ['heredoc_syntax' => false] ), ], 'In PHP double-quoted strings and heredocs some chars like `n`, `$` or `u` have special meanings if preceded by a backslash ' .'(and some are special only if followed by other special chars), while a backslash preceding other chars are interpreted like a plain ' .'backslash. The precise list of those special chars is hard to remember and to identify quickly: this fixer escapes backslashes ' .'that do not start a special interpretation with the char after them.' .PHP_EOL .'It is possible to fix also single-quoted strings: in this case there is no special chars apart from single-quote and backslash ' .'itself, so the fixer simply ensure that all backslashes are escaped. Both single and double backslashes are allowed in single-quoted ' .'strings, so the purpose in this context is mainly to have a uniformed way to have them written all over the codebase.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]); } public function getPriority() { return 1; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $singleQuotedRegex = '/(? $token) { $content = $token->getContent(); if ($token->equalsAny(['"', 'b"', 'B"'])) { $doubleQuoteOpened = !$doubleQuoteOpened; } if (!$token->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]) || false === strpos($content, '\\')) { continue; } if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && '\'' === substr(rtrim($tokens[$index - 1]->getContent()), -1)) { continue; } $firstTwoCharacters = strtolower(substr($content, 0, 2)); $isSingleQuotedString = $token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('\'' === $content[0] || 'b\'' === $firstTwoCharacters); $isDoubleQuotedString = ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('"' === $content[0] || 'b"' === $firstTwoCharacters)) || ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && $doubleQuoteOpened) ; $isHeredocSyntax = !$isSingleQuotedString && !$isDoubleQuotedString; if ( (false === $this->configuration['single_quoted'] && $isSingleQuotedString) || (false === $this->configuration['double_quoted'] && $isDoubleQuotedString) || (false === $this->configuration['heredoc_syntax'] && $isHeredocSyntax) ) { continue; } $regex = $heredocSyntaxRegex; if ($isSingleQuotedString) { $regex = $singleQuotedRegex; } elseif ($isDoubleQuotedString) { $regex = $doubleQuotedRegex; } $newContent = Preg::replace($regex, '\\\\\\\\$1', $content); if ($newContent !== $content) { $tokens[$index] = new Token([$token->getId(), $newContent]); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('single_quoted', 'Whether to fix single-quoted strings.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder('double_quoted', 'Whether to fix double-quoted strings.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('heredoc_syntax', 'Whether to fix heredoc syntax.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), ]); } } true] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_CONSTANT_ENCAPSED_STRING); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { continue; } $content = $token->getContent(); $prefix = ''; if ('b' === strtolower($content[0])) { $prefix = $content[0]; $content = substr($content, 1); } if ( '"' === $content[0] && (true === $this->configuration['strings_containing_single_quote_chars'] || false === strpos($content, "'")) && !Preg::match('/(?setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } } country !"; $c = "I have $farm[0] chickens !"; EOT )], 'The reasoning behind this rule is the following:' ."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow' ."\n".'- PHP manual marks `"$var"` syntax as implicit and `"${var}"` syntax as explicit: explicit code should always be preferred' ."\n".'- Explicit syntax allows word concatenation inside strings, e.g. `"${var}IsAVar"`, implicit doesn\'t' ."\n".'- Explicit syntax is easier to detect for IDE/editors and therefore has colors/hightlight with higher contrast, which is easier to read' ."\n".'Backtick operator is skipped because it is harder to handle; you can use `backtick_to_shell_exec` fixer to normalize backticks to strings' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_VARIABLE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $backtickStarted = false; for ($index = \count($tokens) - 1; $index > 0; --$index) { $token = $tokens[$index]; if ($token->equals('`')) { $backtickStarted = !$backtickStarted; continue; } if ($backtickStarted || !$token->isGivenKind(T_VARIABLE)) { continue; } $prevToken = $tokens[$index - 1]; if (!$this->isStringPartToken($prevToken)) { continue; } $distinctVariableIndex = $index; $variableTokens = [ $distinctVariableIndex => [ 'tokens' => [$index => $token], 'firstVariableTokenIndex' => $index, 'lastVariableTokenIndex' => $index, ], ]; $nextIndex = $index + 1; while (!$this->isStringPartToken($tokens[$nextIndex])) { if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { $distinctVariableIndex = $nextIndex; $variableTokens[$distinctVariableIndex] = [ 'tokens' => [$nextIndex => $tokens[$nextIndex]], 'firstVariableTokenIndex' => $nextIndex, 'lastVariableTokenIndex' => $nextIndex, ]; } else { $variableTokens[$distinctVariableIndex]['tokens'][$nextIndex] = $tokens[$nextIndex]; $variableTokens[$distinctVariableIndex]['lastVariableTokenIndex'] = $nextIndex; } ++$nextIndex; } krsort($variableTokens, \SORT_NUMERIC); foreach ($variableTokens as $distinctVariableSet) { if (1 === \count($distinctVariableSet['tokens'])) { $singleVariableIndex = key($distinctVariableSet['tokens']); $singleVariableToken = current($distinctVariableSet['tokens']); $tokens->overrideRange($singleVariableIndex, $singleVariableIndex, [ new Token([T_DOLLAR_OPEN_CURLY_BRACES, '${']), new Token([T_STRING_VARNAME, substr($singleVariableToken->getContent(), 1)]), new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']), ]); } else { foreach ($distinctVariableSet['tokens'] as $variablePartIndex => $variablePartToken) { if ($variablePartToken->isGivenKind(T_NUM_STRING)) { $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]); continue; } if ($variablePartToken->isGivenKind(T_STRING) && $tokens[$variablePartIndex + 1]->equals(']')) { $tokens[$variablePartIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$variablePartToken->getContent()."'"]); } } $tokens->insertAt($distinctVariableSet['lastVariableTokenIndex'] + 1, new Token([CT::T_CURLY_CLOSE, '}'])); $tokens->insertAt($distinctVariableSet['firstVariableTokenIndex'], new Token([T_CURLY_OPEN, '{'])); } } } } private function isStringPartToken(Token $token) { return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) || $token->isGivenKind(T_START_HEREDOC) || '"' === $token->getContent() || 'b"' === strtolower($token->getContent()) ; } } isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC]); } public function getDefinition() { return new FixerDefinition( 'There should not be a binary flag before strings.', [ new CodeSample(" $token) { if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC])) { continue; } $content = $token->getContent(); if ('b' === strtolower($content[0])) { $tokens[$index] = new Token([$token->getId(), substr($content, 1)]); } } } } isTokenKindFound(T_START_HEREDOC); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_START_HEREDOC) || false !== strpos($token->getContent(), "'")) { continue; } if ($tokens[$index + 1]->isGivenKind(T_END_HEREDOC)) { $tokens[$index] = $this->convertToNowdoc($token); continue; } if ( !$tokens[$index + 1]->isGivenKind(T_ENCAPSED_AND_WHITESPACE) || !$tokens[$index + 2]->isGivenKind(T_END_HEREDOC) ) { continue; } $content = $tokens[$index + 1]->getContent(); if (Preg::match('/(?convertToNowdoc($token); $content = str_replace(['\\\\', '\\$'], ['\\', '$'], $content); $tokens[$index + 1] = new Token([ $tokens[$index + 1]->getId(), $content, ]); } } private function convertToNowdoc(Token $token) { return new Token([ $token->getId(), Preg::replace('/^([Bb]?<<<)([ \t]*)"?([^\s"]+)"?/', '$1$2\'$3\'', $token->getContent()), ]); } } isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE]); } public function isRisky() { return true; } public function getDefinition() { return new FixerDefinition( 'All multi-line strings must use correct line ending.', [ new CodeSample( "whitespacesConfig->getLineEnding(); foreach ($tokens as $tokenIndex => $token) { if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE])) { continue; } $tokens[$tokenIndex] = new Token([ $token->getId(), Preg::replace( '#\R#u', $ending, $token->getContent() ), ]); } } } isTokenKindFound(T_USE); } public function getDefinition() { return new FixerDefinition( 'Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block.', [ new CodeSample( 'whitespacesConfig->getLineEnding(); $tokensAnalyzer = new TokensAnalyzer($tokens); $added = 0; foreach ($tokensAnalyzer->getImportUseIndexes() as $index) { $index += $added; $indent = ''; if ($tokens[$index - 1]->isWhitespace(" \t") && $tokens[$index - 2]->isGivenKind(T_COMMENT)) { $indent = $tokens[$index - 1]->getContent(); } elseif ($tokens[$index - 1]->isWhitespace()) { $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$index - 1]); } $semicolonIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); $insertIndex = $semicolonIndex; if ($tokens[$semicolonIndex]->isGivenKind(T_CLOSE_TAG)) { if ($tokens[$insertIndex - 1]->isWhitespace()) { --$insertIndex; } $tokens->insertAt($insertIndex, new Token(';')); ++$added; } if ($semicolonIndex === \count($tokens) - 1) { $tokens->insertAt($insertIndex + 1, new Token([T_WHITESPACE, $ending.$ending.$indent])); ++$added; } else { $newline = $ending; $tokens[$semicolonIndex]->isGivenKind(T_CLOSE_TAG) ? --$insertIndex : ++$insertIndex; if ($tokens[$insertIndex]->isWhitespace(" \t") && $tokens[$insertIndex + 1]->isComment()) { ++$insertIndex; } if ($tokens[$insertIndex]->isComment()) { ++$insertIndex; } $afterSemicolon = $tokens->getNextMeaningfulToken($semicolonIndex); if (null === $afterSemicolon || !$tokens[$afterSemicolon]->isGivenKind(T_USE)) { $newline .= $ending; } if ($tokens[$insertIndex]->isWhitespace()) { $nextToken = $tokens[$insertIndex]; $nextMeaningfulAfterUseIndex = $tokens->getNextMeaningfulToken($insertIndex); if (null !== $nextMeaningfulAfterUseIndex && $tokens[$nextMeaningfulAfterUseIndex]->isGivenKind(T_USE)) { if (substr_count($nextToken->getContent(), "\n") < 2) { $tokens[$insertIndex] = new Token([T_WHITESPACE, $newline.$indent.ltrim($nextToken->getContent())]); } } else { $tokens[$insertIndex] = new Token([T_WHITESPACE, $newline.$indent.ltrim($nextToken->getContent())]); } } else { $tokens->insertAt($insertIndex, new Token([T_WHITESPACE, $newline.$indent])); ++$added; } } } } } isTokenKindFound(T_USE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $usesIndexes = $tokensAnalyzer->getImportUseIndexes(); foreach ($usesIndexes as $idx) { $nextTokenIdx = $tokens->getNextMeaningfulToken($idx); $nextToken = $tokens[$nextTokenIdx]; if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { $tokens->clearAt($nextTokenIdx); } elseif ($nextToken->isGivenKind([CT::T_FUNCTION_IMPORT, CT::T_CONST_IMPORT])) { $nextTokenIdx = $tokens->getNextMeaningfulToken($nextTokenIdx); if ($tokens[$nextTokenIdx]->isGivenKind(T_NS_SEPARATOR)) { $tokens->clearAt($nextTokenIdx); } } } } } isTokenKindFound(T_USE); } public function supports(\SplFileInfo $file) { $path = $file->getPathname(); if (false !== strpos($path, \DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR) && false === strpos($path, \DIRECTORY_SEPARATOR.'tests'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR) ) { return false; } return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); if (0 === \count($useDeclarations)) { return; } foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { $currentNamespaceUseDeclarations = array_filter( $useDeclarations, function (NamespaceUseAnalysis $useDeclaration) use ($namespace) { return $useDeclaration->getStartIndex() >= $namespace->getScopeStartIndex() && $useDeclaration->getEndIndex() <= $namespace->getScopeEndIndex() ; } ); $usagesSearchIgnoredIndexes = []; foreach ($currentNamespaceUseDeclarations as $useDeclaration) { $usagesSearchIgnoredIndexes[$useDeclaration->getStartIndex()] = $useDeclaration->getEndIndex(); } foreach ($currentNamespaceUseDeclarations as $useDeclaration) { if (!$this->importIsUsed($tokens, $namespace, $usagesSearchIgnoredIndexes, $useDeclaration->getShortName())) { $this->removeUseDeclaration($tokens, $useDeclaration); } } $this->removeUsesInSameNamespace($tokens, $currentNamespaceUseDeclarations, $namespace); } } private function importIsUsed(Tokens $tokens, NamespaceAnalysis $namespace, array $ignoredIndexes, $shortName) { for ($index = $namespace->getScopeStartIndex(); $index <= $namespace->getScopeEndIndex(); ++$index) { if (isset($ignoredIndexes[$index])) { $index = $ignoredIndexes[$index]; continue; } $token = $tokens[$index]; if ( $token->isGivenKind(T_STRING) && 0 === strcasecmp($shortName, $token->getContent()) && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_NS_SEPARATOR, T_CONST, T_OBJECT_OPERATOR]) ) { return true; } if ($token->isComment() && Preg::match( '/(?getContent() )) { return true; } } return false; } private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useDeclaration) { for ($index = $useDeclaration->getEndIndex() - 1; $index >= $useDeclaration->getStartIndex(); --$index) { if ($tokens[$index]->isComment()) { continue; } if (!$tokens[$index]->isWhitespace() || false === strpos($tokens[$index]->getContent(), "\n")) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); continue; } $prevIndex = $tokens->getPrevNonWhitespace($index); if ($tokens[$prevIndex]->isComment()) { $content = $tokens[$index]->getContent(); $tokens[$index] = new Token([T_WHITESPACE, substr($content, strrpos($content, "\n"))]); } else { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } if ($tokens[$useDeclaration->getEndIndex()]->equals(';')) { $tokens->clearAt($useDeclaration->getEndIndex()); } $prevIndex = $useDeclaration->getStartIndex() - 1; $prevToken = $tokens[$prevIndex]; if ($prevToken->isWhitespace()) { $content = rtrim($prevToken->getContent(), " \t"); if ('' === $content) { $tokens->clearAt($prevIndex); } else { $tokens[$prevIndex] = new Token([T_WHITESPACE, $content]); } $prevToken = $tokens[$prevIndex]; } if (!isset($tokens[$useDeclaration->getEndIndex() + 1])) { return; } $nextIndex = $tokens->getNonEmptySibling($useDeclaration->getEndIndex(), 1); if (null === $nextIndex) { return; } $nextToken = $tokens[$nextIndex]; if ($nextToken->isWhitespace()) { $content = Preg::replace( "#^\r\n|^\n#", '', ltrim($nextToken->getContent(), " \t"), 1 ); if ('' !== $content) { $tokens[$nextIndex] = new Token([T_WHITESPACE, $content]); } else { $tokens->clearAt($nextIndex); } $nextToken = $tokens[$nextIndex]; } if ($prevToken->isWhitespace() && $nextToken->isWhitespace()) { $content = $prevToken->getContent().$nextToken->getContent(); if ('' !== $content) { $tokens[$nextIndex] = new Token([T_WHITESPACE, $content]); } else { $tokens->clearAt($nextIndex); } $tokens->clearAt($prevIndex); } } private function removeUsesInSameNamespace(Tokens $tokens, array $useDeclarations, NamespaceAnalysis $namespaceDeclaration) { $namespace = $namespaceDeclaration->getFullName(); $nsLength = \strlen($namespace.'\\'); foreach ($useDeclarations as $useDeclaration) { if ($useDeclaration->isAliased()) { continue; } $useDeclarationFullName = ltrim($useDeclaration->getFullName(), '\\'); if (0 !== strpos($useDeclarationFullName, $namespace.'\\')) { continue; } $partName = substr($useDeclarationFullName, $nsLength); if (false === strpos($partName, '\\')) { $this->removeUseDeclaration($tokens, $useDeclaration); } } } } isTokenKindFound(T_FUNCTION) && ( \count((new NamespacesAnalyzer())->getDeclarations($tokens)) || \count((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens)) ); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $lastIndex = $tokens->count() - 1; for ($index = $lastIndex; $index >= 0; --$index) { if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { continue; } $this->fixFunctionReturnType($tokens, $index); $this->fixFunctionArguments($tokens, $index); } } private function fixFunctionArguments(Tokens $tokens, $index) { $arguments = (new FunctionsAnalyzer())->getFunctionArguments($tokens, $index); foreach ($arguments as $argument) { if (!$argument->hasTypeAnalysis()) { continue; } $this->detectAndReplaceTypeWithShortType($tokens, $argument->getTypeAnalysis()); } } private function fixFunctionReturnType(Tokens $tokens, $index) { if (\PHP_VERSION_ID < 70000) { return; } $returnType = (new FunctionsAnalyzer())->getFunctionReturnType($tokens, $index); if (!$returnType) { return; } $this->detectAndReplaceTypeWithShortType($tokens, $returnType); } private function detectAndReplaceTypeWithShortType( Tokens $tokens, TypeAnalysis $type ) { if ($type->isReservedType()) { return; } $typeName = $type->getName(); $shortType = (new TypeShortNameResolver())->resolve($tokens, $type->getName()); if ($shortType === $typeName) { return; } $tokens->overrideRange( $type->getStartIndex(), $type->getEndIndex(), (new NamespacedStringTokenGenerator())->generate($shortType) ); } } self::SORT_LENGTH] ), new VersionSpecificCodeSample( " self::SORT_LENGTH, 'imports_order' => [ self::IMPORT_TYPE_CONST, self::IMPORT_TYPE_CLASS, self::IMPORT_TYPE_FUNCTION, ], ] ), new VersionSpecificCodeSample( ' self::SORT_ALPHA, 'imports_order' => [ self::IMPORT_TYPE_CONST, self::IMPORT_TYPE_CLASS, self::IMPORT_TYPE_FUNCTION, ], ] ), new VersionSpecificCodeSample( ' self::SORT_NONE, 'imports_order' => [ self::IMPORT_TYPE_CONST, self::IMPORT_TYPE_CLASS, self::IMPORT_TYPE_FUNCTION, ], ] ), ] ); } public function getPriority() { return -30; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_USE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); if (0 === \count($namespacesImports)) { return; } $usesOrder = []; foreach ($namespacesImports as $uses) { $usesOrder[] = $this->getNewOrder(array_reverse($uses), $tokens); } $usesOrder = array_replace(...$usesOrder); $usesOrder = array_reverse($usesOrder, true); $mapStartToEnd = []; foreach ($usesOrder as $use) { $mapStartToEnd[$use['startIndex']] = $use['endIndex']; } foreach ($usesOrder as $index => $use) { $declarationTokens = Tokens::fromCode( sprintf( 'clearRange(0, 2); $declarationTokens->clearAt(\count($declarationTokens) - 1); $declarationTokens->clearEmptyTokens(); $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); if ($use['group']) { $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->equals(',')) { $tokens[$prev] = new Token(';'); $tokens->insertAt($prev + 1, new Token([T_USE, 'use'])); if (!$tokens[$prev + 2]->isWhitespace()) { $tokens->insertAt($prev + 2, new Token([T_WHITESPACE, ' '])); } } } } } protected function createConfigurationDefinition() { $supportedSortTypes = $this->supportedSortTypes; return new FixerConfigurationResolver([ (new AliasedFixerOptionBuilder( new FixerOptionBuilder('sort_algorithm', 'whether the statements should be sorted alphabetically or by length, or not sorted'), 'sortAlgorithm' )) ->setAllowedValues($this->supportedSortAlgorithms) ->setDefault(self::SORT_ALPHA) ->getOption(), (new AliasedFixerOptionBuilder( new FixerOptionBuilder('imports_order', 'Defines the order of import types.'), 'importsOrder' )) ->setAllowedTypes(['array', 'null']) ->setAllowedValues([static function ($value) use ($supportedSortTypes) { if (null !== $value) { $missing = array_diff($supportedSortTypes, $value); if (\count($missing)) { throw new InvalidOptionsException(sprintf( 'Missing sort %s "%s".', 1 === \count($missing) ? 'type' : 'types', implode('", "', $missing) )); } $unknown = array_diff($value, $supportedSortTypes); if (\count($unknown)) { throw new InvalidOptionsException(sprintf( 'Unknown sort %s "%s".', 1 === \count($unknown) ? 'type' : 'types', implode('", "', $unknown) )); } } return true; }]) ->setDefault(null) ->getOption(), ]); } private function sortAlphabetically(array $first, array $second) { $firstNamespace = str_replace('\\', ' ', $this->prepareNamespace($first['namespace'])); $secondNamespace = str_replace('\\', ' ', $this->prepareNamespace($second['namespace'])); return strcasecmp($firstNamespace, $secondNamespace); } private function sortByLength(array $first, array $second) { $firstNamespace = (self::IMPORT_TYPE_CLASS === $first['importType'] ? '' : $first['importType'].' ').$this->prepareNamespace($first['namespace']); $secondNamespace = (self::IMPORT_TYPE_CLASS === $second['importType'] ? '' : $second['importType'].' ').$this->prepareNamespace($second['namespace']); $firstNamespaceLength = \strlen($firstNamespace); $secondNamespaceLength = \strlen($secondNamespace); if ($firstNamespaceLength === $secondNamespaceLength) { $sortResult = strcasecmp($firstNamespace, $secondNamespace); } else { $sortResult = $firstNamespaceLength > $secondNamespaceLength ? 1 : -1; } return $sortResult; } private function prepareNamespace($namespace) { return trim(Preg::replace('%/\*(.*)\*/%s', '', $namespace)); } private function getNewOrder(array $uses, Tokens $tokens) { $indexes = []; $originalIndexes = []; $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($i = \count($uses) - 1; $i >= 0; --$i) { $index = $uses[$i]; $startIndex = $tokens->getTokenNotOfKindSibling($index + 1, 1, [[T_WHITESPACE]]); $endIndex = $tokens->getNextTokenOfKind($startIndex, [';', [T_CLOSE_TAG]]); $previous = $tokens->getPrevMeaningfulToken($endIndex); $group = $tokens[$previous]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE); if ($tokens[$startIndex]->isGivenKind(CT::T_CONST_IMPORT)) { $type = self::IMPORT_TYPE_CONST; $index = $tokens->getNextNonWhitespace($startIndex); } elseif ($tokens[$startIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { $type = self::IMPORT_TYPE_FUNCTION; $index = $tokens->getNextNonWhitespace($startIndex); } else { $type = self::IMPORT_TYPE_CLASS; $index = $startIndex; } $namespaceTokens = []; while ($index <= $endIndex) { $token = $tokens[$index]; if ($index === $endIndex || (!$group && $token->equals(','))) { if ($group && self::SORT_NONE !== $this->configuration['sort_algorithm']) { $namespaceTokensCount = \count($namespaceTokens) - 1; $namespace = ''; for ($k = 0; $k < $namespaceTokensCount; ++$k) { if ($namespaceTokens[$k]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { $namespace .= '{'; break; } $namespace .= $namespaceTokens[$k]->getContent(); } $parts = []; $firstIndent = ''; $separator = ', '; $lastIndent = ''; for ($k1 = $k + 1; $k1 < $namespaceTokensCount; ++$k1) { $comment = ''; $namespacePart = ''; for ($k2 = $k1;; ++$k2) { if ($namespaceTokens[$k2]->equalsAny([',', [CT::T_GROUP_IMPORT_BRACE_CLOSE]])) { break; } if ($namespaceTokens[$k2]->isComment()) { $comment .= $namespaceTokens[$k2]->getContent(); continue; } if ( '' === $firstIndent && $namespaceTokens[$k2]->isWhitespace() && false !== strpos($namespaceTokens[$k2]->getContent(), $lineEnding) ) { $lastIndent = $lineEnding; $firstIndent = $lineEnding.$this->whitespacesConfig->getIndent(); $separator = ','.$firstIndent; } $namespacePart .= $namespaceTokens[$k2]->getContent(); } $namespacePart = trim($namespacePart); $comment = trim($comment); if ('' !== $comment) { $namespacePart .= ' '.$comment; } $parts[] = $namespacePart; $k1 = $k2; } $sortedParts = $parts; sort($parts); if ($sortedParts === $parts) { $namespace = Tokens::fromArray($namespaceTokens)->generateCode(); } else { $namespace .= $firstIndent.implode($separator, $parts).$lastIndent.'}'; } } else { $namespace = Tokens::fromArray($namespaceTokens)->generateCode(); } $indexes[$startIndex] = [ 'namespace' => $namespace, 'startIndex' => $startIndex, 'endIndex' => $index - 1, 'importType' => $type, 'group' => $group, ]; $originalIndexes[] = $startIndex; if ($index === $endIndex) { break; } $namespaceTokens = []; $nextPartIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[','], [T_WHITESPACE]]); $startIndex = $nextPartIndex; $index = $nextPartIndex; continue; } $namespaceTokens[] = $token; ++$index; } } if (null !== $this->configuration['imports_order']) { $groupedByTypes = []; foreach ($indexes as $startIndex => $item) { $groupedByTypes[$item['importType']][$startIndex] = $item; } foreach ($groupedByTypes as $type => $indexes) { $groupedByTypes[$type] = $this->sortByAlgorithm($indexes); } $sortedGroups = []; foreach ($this->configuration['imports_order'] as $type) { if (isset($groupedByTypes[$type]) && !empty($groupedByTypes[$type])) { foreach ($groupedByTypes[$type] as $startIndex => $item) { $sortedGroups[$startIndex] = $item; } } } $indexes = $sortedGroups; } else { $indexes = $this->sortByAlgorithm($indexes); } $index = -1; $usesOrder = []; foreach ($indexes as $v) { $usesOrder[$originalIndexes[++$index]] = $v; } return $usesOrder; } private function sortByAlgorithm(array $indexes) { if (self::SORT_ALPHA === $this->configuration['sort_algorithm']) { uasort($indexes, [$this, 'sortAlphabetically']); } elseif (self::SORT_LENGTH === $this->configuration['sort_algorithm']) { uasort($indexes, [$this, 'sortByLength']); } return $indexes; } } isTokenKindFound(T_USE); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $uses = array_reverse($tokensAnalyzer->getImportUseIndexes()); foreach ($uses as $index) { $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); $groupClose = $tokens->getPrevMeaningfulToken($endIndex); if ($tokens[$groupClose]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { $this->fixGroupUse($tokens, $index, $endIndex); } else { $this->fixMultipleUse($tokens, $index, $endIndex); } } } private function detectIndent(Tokens $tokens, $index) { if (!$tokens[$index - 1]->isWhitespace()) { return ''; } $explodedContent = explode("\n", $tokens[$index - 1]->getContent()); return end($explodedContent); } private function getGroupDeclaration(Tokens $tokens, $index) { $groupPrefix = ''; $comment = ''; for ($i = $index + 1;; ++$i) { if ($tokens[$i]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { $groupOpenIndex = $i; break; } if ($tokens[$i]->isComment()) { $comment .= $tokens[$i]->getContent(); if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i + 1]->isWhitespace()) { $groupPrefix .= ' '; } continue; } if ($tokens[$i]->isWhitespace()) { $groupPrefix .= ' '; continue; } $groupPrefix .= $tokens[$i]->getContent(); } return [ rtrim($groupPrefix), $groupOpenIndex, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $groupOpenIndex), $comment, ]; } private function getGroupStatements(Tokens $tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment) { $statements = []; $statement = $groupPrefix; for ($i = $groupOpenIndex + 1; $i <= $groupCloseIndex; ++$i) { $token = $tokens[$i]; if ($token->equals(',') && $tokens[$tokens->getNextMeaningfulToken($i)]->equals([CT::T_GROUP_IMPORT_BRACE_CLOSE])) { continue; } if ($token->equalsAny([',', [CT::T_GROUP_IMPORT_BRACE_CLOSE]])) { $statements[] = 'use'.$statement.';'; $statement = $groupPrefix; continue; } if ($token->isWhitespace()) { $j = $tokens->getNextMeaningfulToken($i); if ($tokens[$j]->equals([T_AS])) { $statement .= ' as '; $i += 2; } elseif ($tokens[$j]->equals([T_FUNCTION])) { $statement = ' function'.$statement; $i += 2; } elseif ($tokens[$j]->equals([T_CONST])) { $statement = ' const'.$statement; $i += 2; } if ($token->isWhitespace(" \t") || '//' !== substr($tokens[$i - 1]->getContent(), 0, 2)) { continue; } } $statement .= $token->getContent(); } if ('' !== $comment) { $statements[0] .= ' '.$comment; } return $statements; } private function fixGroupUse(Tokens $tokens, $index, $endIndex) { list($groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment) = $this->getGroupDeclaration($tokens, $index); $statements = $this->getGroupStatements($tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment); if (\count($statements) < 2) { return; } $tokens->clearRange($index, $groupCloseIndex); if ($tokens[$endIndex]->equals(';')) { $tokens->clearAt($endIndex); } $ending = $this->whitespacesConfig->getLineEnding(); $importTokens = Tokens::fromCode('clearAt(0); $importTokens->clearEmptyTokens(); $tokens->insertAt($index, $importTokens); } private function fixMultipleUse(Tokens $tokens, $index, $endIndex) { $ending = $this->whitespacesConfig->getLineEnding(); for ($i = $endIndex - 1; $i > $index; --$i) { if (!$tokens[$i]->equals(',')) { continue; } $tokens[$i] = new Token(';'); $i = $tokens->getNextMeaningfulToken($i); $tokens->insertAt($i, new Token([T_USE, 'use'])); $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); $indent = $this->detectIndent($tokens, $index); if ($tokens[$i - 1]->isWhitespace()) { $tokens[$i - 1] = new Token([T_WHITESPACE, $ending.$indent]); continue; } if (false === strpos($tokens[$i - 1]->getContent(), "\n")) { $tokens->insertAt($i, new Token([T_WHITESPACE, $ending.$indent])); } } } } proxyFixers); } protected function createProxyFixers() { $fixer = new IncrementStyleFixer(); $fixer->configure(['style' => 'pre']); return [$fixer]; } } ` with `!=`.', [new CodeSample(" \$c;\n")] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_IS_NOT_EQUAL); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_IS_NOT_EQUAL)) { $tokens[$index] = new Token([T_IS_NOT_EQUAL, '!=']); } } } } equals('=')) { $tokens[$index] = new Token(sprintf(self::ALIGNABLE_PLACEHOLDER, $this->deepestLevel).$token->getContent()); continue; } if ($token->isGivenKind(T_FUNCTION)) { ++$this->deepestLevel; continue; } if ($token->equals('(')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->equals('[')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); continue; } } } } isTokenKindFound('!'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if ($token->equals('!')) { if (!$tokens[$index + 1]->isWhitespace()) { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } else { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } } } } } self::STYLE_POST] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound([T_INC, T_DEC]); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('style', 'Whether to use pre- or post-increment and decrement operators.')) ->setAllowedValues([self::STYLE_PRE, self::STYLE_POST]) ->setDefault(self::STYLE_PRE) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind([T_INC, T_DEC])) { continue; } if (self::STYLE_PRE === $this->configuration['style'] && $tokensAnalyzer->isUnarySuccessorOperator($index)) { $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if (!$nextToken->equalsAny([';', ')'])) { continue; } $startIndex = $this->findStart($tokens, $index); $prevToken = $tokens[$tokens->getPrevMeaningfulToken($startIndex)]; if ($prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { $tokens->clearAt($index); $tokens->insertAt($startIndex, clone $token); } } elseif (self::STYLE_POST === $this->configuration['style'] && $tokensAnalyzer->isUnaryPredecessorOperator($index)) { $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if (!$prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { continue; } $endIndex = $this->findEnd($tokens, $index); $nextToken = $tokens[$tokens->getNextMeaningfulToken($endIndex)]; if ($nextToken->equalsAny([';', ')'])) { $tokens->clearAt($index); $tokens->insertAt($tokens->getNextNonWhitespace($endIndex), clone $token); } } } } private function findEnd(Tokens $tokens, $index) { $nextIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextIndex]; while ($nextToken->equalsAny([ '$', '[', [CT::T_DYNAMIC_PROP_BRACE_OPEN], [CT::T_DYNAMIC_VAR_BRACE_OPEN], [T_NS_SEPARATOR], [T_STATIC], [T_STRING], [T_VARIABLE], ])) { $blockType = Tokens::detectBlockType($nextToken); if (null !== $blockType) { $nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex); } $index = $nextIndex; $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); $nextToken = $tokens[$nextIndex]; } if ($nextToken->isGivenKind(T_OBJECT_OPERATOR)) { return $this->findEnd($tokens, $nextIndex); } if ($nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { return $this->findEnd($tokens, $tokens->getNextMeaningfulToken($nextIndex)); } return $index; } private function findStart(Tokens $tokens, $index) { do { $index = $tokens->getPrevMeaningfulToken($index); $token = $tokens[$index]; $blockType = Tokens::detectBlockType($token); if (null !== $blockType && !$blockType['isStart']) { $index = $tokens->findBlockStart($blockType['type'], $index); $token = $tokens[$index]; } } while (!$token->equalsAny(['$', [T_VARIABLE]])); $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->equals('$')) { $index = $prevIndex; $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; } if ($prevToken->isGivenKind(T_OBJECT_OPERATOR)) { return $this->findStart($tokens, $prevIndex); } if ($prevToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if (!$tokens[$prevPrevIndex]->isGivenKind([T_STATIC, T_STRING])) { return $this->findStart($tokens, $prevIndex); } $index = $tokens->getTokenNotOfKindSibling($prevIndex, -1, [[T_NS_SEPARATOR], [T_STATIC], [T_STRING]]); $index = $tokens->getNextMeaningfulToken($index); } return $index; } } `.', [new CodeSample(" b;\n")] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_OBJECT_OPERATOR)) { continue; } if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) { $tokens->clearAt($index - 1); } if ($tokens[$index + 1]->isWhitespace(" \t") && !$tokens[$index + 2]->isComment()) { $tokens->clearAt($index + 1); } } } } = 7.0.', [ new VersionSpecificCodeSample( "= 70000 && $tokens->isTokenKindFound(T_ISSET); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $issetIndexes = array_keys($tokens->findGivenKind(T_ISSET)); while ($issetIndex = array_pop($issetIndexes)) { $this->fixIsset($tokens, $issetIndex); } } private function fixIsset(Tokens $tokens, $index) { $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); if ($this->isHigherPrecedenceAssociativityOperator($tokens[$prevTokenIndex])) { return; } $startBraceIndex = $tokens->getNextTokenOfKind($index, ['(']); $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startBraceIndex); $ternaryQuestionMarkIndex = $tokens->getNextMeaningfulToken($endBraceIndex); if (!$tokens[$ternaryQuestionMarkIndex]->equals('?')) { return; } $issetTokens = $this->getMeaningfulSequence($tokens, $startBraceIndex, $endBraceIndex); if ($this->hasChangingContent($issetTokens)) { return; } $ternaryColonIndex = $tokens->getNextTokenOfKind($ternaryQuestionMarkIndex, [':']); $ternaryFirstOperandTokens = $this->getMeaningfulSequence($tokens, $ternaryQuestionMarkIndex, $ternaryColonIndex); if ($issetTokens->generateCode() !== $ternaryFirstOperandTokens->generateCode()) { return; } $ternaryFirstOperandIndex = $tokens->getNextMeaningfulToken($ternaryQuestionMarkIndex); $comments = []; $commentStarted = false; for ($loopIndex = $index; $loopIndex < $ternaryFirstOperandIndex; ++$loopIndex) { if ($tokens[$loopIndex]->isComment()) { $comments[] = $tokens[$loopIndex]; $commentStarted = true; } elseif ($commentStarted) { if ($tokens[$loopIndex]->isWhitespace()) { $comments[] = $tokens[$loopIndex]; } $commentStarted = false; } } $tokens[$ternaryColonIndex] = new Token([T_COALESCE, '??']); $tokens->overrideRange($index, $ternaryFirstOperandIndex - 1, $comments); } private function getMeaningfulSequence(Tokens $tokens, $start, $end) { $sequence = []; $index = $start; while ($index < $end) { $index = $tokens->getNextMeaningfulToken($index); if ($index >= $end || null === $index) { break; } $sequence[] = $tokens[$index]; } return Tokens::fromArray($sequence); } private function isHigherPrecedenceAssociativityOperator(Token $token) { static $operatorsPerId = [ T_ARRAY_CAST => true, T_BOOLEAN_AND => true, T_BOOLEAN_OR => true, T_BOOL_CAST => true, T_COALESCE => true, T_DEC => true, T_DOUBLE_CAST => true, T_INC => true, T_INT_CAST => true, T_IS_EQUAL => true, T_IS_GREATER_OR_EQUAL => true, T_IS_IDENTICAL => true, T_IS_NOT_EQUAL => true, T_IS_NOT_IDENTICAL => true, T_IS_SMALLER_OR_EQUAL => true, T_OBJECT_CAST => true, T_POW => true, T_SL => true, T_SPACESHIP => true, T_SR => true, T_STRING_CAST => true, T_UNSET_CAST => true, ]; static $operatorsPerContent = [ '!', '%', '&', '*', '+', '-', '/', ':', '^', '|', '~', ]; return isset($operatorsPerId[$token->getId()]) || $token->equalsAny($operatorsPerContent); } private function hasChangingContent(Tokens $tokens) { static $operatorsPerId = [ T_DEC, T_INC, T_STRING, T_YIELD, ]; foreach ($tokens as $token) { if ($token->isGivenKind($operatorsPerId) || $token->equals('(')) { return true; } } return false; } } isAnyTokenKindsFound([T_PLUS_EQUAL, T_MINUS_EQUAL]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index > 0; --$index) { $expressionEnd = $tokens[$index]; if (!$expressionEnd->equalsAny(self::EXPRESSION_END_TOKENS)) { continue; } $numberIndex = $tokens->getPrevMeaningfulToken($index); $number = $tokens[$numberIndex]; if (!$number->isGivenKind(T_LNUMBER) || '1' !== $number->getContent()) { continue; } $operatorIndex = $tokens->getPrevMeaningfulToken($numberIndex); $operator = $tokens[$operatorIndex]; if (!$operator->isGivenKind([T_PLUS_EQUAL, T_MINUS_EQUAL])) { continue; } $startIndex = $this->findStart($tokens, $tokens->getPrevMeaningfulToken($operatorIndex)); $this->clearRangeLeaveComments( $tokens, $tokens->getPrevMeaningfulToken($operatorIndex) + 1, $numberIndex ); $tokens->insertAt( $startIndex, new Token($operator->isGivenKind(T_PLUS_EQUAL) ? [T_INC, '++'] : [T_DEC, '--']) ); } } private function findStart(Tokens $tokens, $index) { while (!$tokens[$index]->equalsAny(['$', [T_VARIABLE]])) { if ($tokens[$index]->equals(']')) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); } elseif ($tokens[$index]->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_CLOSE)) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index); } elseif ($tokens[$index]->isGivenKind(CT::T_DYNAMIC_VAR_BRACE_CLOSE)) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, $index); } elseif ($tokens[$index]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE)) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $index); } else { $index = $tokens->getPrevMeaningfulToken($index); } } while ($tokens[$tokens->getPrevMeaningfulToken($index)]->equals('$')) { $index = $tokens->getPrevMeaningfulToken($index); } if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_OBJECT_OPERATOR)) { return $this->findStart($tokens, $tokens->getPrevMeaningfulToken($index)); } return $index; } private function clearRangeLeaveComments(Tokens $tokens, $indexStart, $indexEnd) { for ($i = $indexStart; $i <= $indexEnd; ++$i) { $token = $tokens[$i]; if ($token->isComment()) { continue; } if ($token->isWhitespace("\n\r")) { continue; } $tokens->clearAt($i); } } } isGivenKind([T_FOREACH, T_FOR, T_WHILE, T_IF, T_SWITCH])) { $index = $tokens->getNextMeaningfulToken($index); $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->isGivenKind(T_ARRAY)) { $from = $tokens->getNextMeaningfulToken($index); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $from); $index = $until; $this->injectArrayAlignmentPlaceholders($tokens, $from, $until); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind([T_STRING, T_VARIABLE])) { continue; } $from = $index; $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $from); $index = $until; $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); continue; } if ($token->isGivenKind(T_DOUBLE_ARROW)) { $tokenContent = sprintf(self::ALIGNABLE_PLACEHOLDER, $this->currentLevel).$token->getContent(); $nextIndex = $index + 1; $nextToken = $tokens[$nextIndex]; if (!$nextToken->isWhitespace()) { $tokenContent .= ' '; } elseif ($nextToken->isWhitespace(" \t")) { $tokens[$nextIndex] = new Token([T_WHITESPACE, ' ']); } $tokens[$index] = new Token([T_DOUBLE_ARROW, $tokenContent]); continue; } if ($token->equals(';')) { ++$this->deepestLevel; ++$this->currentLevel; continue; } if ($token->equals(',')) { for ($i = $index; $i < $endAt - 1; ++$i) { if (false !== strpos($tokens[$i - 1]->getContent(), "\n")) { break; } if ($tokens[$i + 1]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $arrayStartIndex = $tokens[$i + 1]->isGivenKind(T_ARRAY) ? $tokens->getNextMeaningfulToken($i + 1) : $i + 1 ; $blockType = Tokens::detectBlockType($tokens[$arrayStartIndex]); $arrayEndIndex = $tokens->findBlockEnd($blockType['type'], $arrayStartIndex); if ($tokens->isPartialCodeMultiline($arrayStartIndex, $arrayEndIndex)) { break; } } ++$index; } } } } private function injectArrayAlignmentPlaceholders(Tokens $tokens, $from, $until) { if ($tokens->isPartialCodeMultiline($from, $until)) { ++$this->deepestLevel; ++$this->currentLevel; $this->injectAlignmentPlaceholders($tokens, $from, $until); --$this->currentLevel; } } } isTokenKindFound(T_NEW); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $nextTokenKinds = null; if (null === $nextTokenKinds) { $nextTokenKinds = [ '?', ';', ',', '(', ')', '[', ']', ':', '<', '>', '+', '-', '*', '/', '%', '&', '^', '|', [T_CLASS], [T_IS_SMALLER_OR_EQUAL], [T_IS_GREATER_OR_EQUAL], [T_IS_EQUAL], [T_IS_NOT_EQUAL], [T_IS_IDENTICAL], [T_IS_NOT_IDENTICAL], [T_CLOSE_TAG], [T_LOGICAL_AND], [T_LOGICAL_OR], [T_LOGICAL_XOR], [T_BOOLEAN_AND], [T_BOOLEAN_OR], [T_SL], [T_SR], [T_INSTANCEOF], [T_AS], [T_DOUBLE_ARROW], [T_POW], [CT::T_ARRAY_SQUARE_BRACE_OPEN], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_BRACE_CLASS_INSTANTIATION_OPEN], [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE], ]; if (\defined('T_SPACESHIP')) { $nextTokenKinds[] = [T_SPACESHIP]; } } for ($index = $tokens->count() - 3; $index > 0; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_NEW)) { continue; } $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds); $nextToken = $tokens[$nextIndex]; if ($nextToken->isGivenKind(T_CLASS)) { if (!$tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals('(')) { $this->insertBracesAfter($tokens, $nextIndex); } continue; } while ($nextToken->equals('[')) { $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $nextIndex) + 1; $nextToken = $tokens[$nextIndex]; } if ($nextToken->isWhitespace()) { $nextIndex = $tokens->getNextNonWhitespace($nextIndex); $nextToken = $tokens[$nextIndex]; } if ($nextToken->equals('(')) { continue; } $this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex)); } } private function insertBracesAfter(Tokens $tokens, $index) { $tokens->insertAt(++$index, [new Token('('), new Token(')')]); } } ', '|', '^', '+', '-', '&', '&=', '&&', '||', '.=', '/=', '=>', '==', '>=', '===', '!=', '<>', '!==', '<=', 'and', 'or', 'xor', '-=', '%=', '*=', '|=', '+=', '<<', '<<=', '>>', '>>=', '^=', '**', '**=', '<=>', '??', ]; private $tokensAnalyzer; private $alignOperatorTokens = []; private $operators = []; public function configure(array $configuration = null) { if ( null !== $configuration && (\array_key_exists('align_equals', $configuration) || \array_key_exists('align_double_arrow', $configuration)) ) { $configuration = $this->resolveOldConfig($configuration); } parent::configure($configuration); $this->operators = $this->resolveOperatorsFromConfig(); } public function getDefinition() { return new FixerDefinition( 'Binary operators should be surrounded by space as configured.', [ new CodeSample( " ['=' => 'align', 'xor' => null]] ), new CodeSample( ' ['+=' => 'align_single_space']] ), new CodeSample( ' ['===' => 'align_single_space_minimal']] ), new CodeSample( ' ['|' => 'no_space']] ), ] ); } public function getPriority() { return -31; } public function isCandidate(Tokens $tokens) { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 2; $index > 0; --$index) { if (!$this->tokensAnalyzer->isBinaryOperator($index)) { continue; } if ('=' === $tokens[$index]->getContent()) { $isDeclare = $this->isEqualPartOfDeclareStatement($tokens, $index); if (false === $isDeclare) { $this->fixWhiteSpaceAroundOperator($tokens, $index); } else { $index = $isDeclare; } } else { $this->fixWhiteSpaceAroundOperator($tokens, $index); } --$index; } if (\count($this->alignOperatorTokens)) { $this->fixAlignment($tokens, $this->alignOperatorTokens); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('default', 'Default fix strategy.')) ->setDefault(self::SINGLE_SPACE) ->setAllowedValues(self::$allowedValues) ->getOption(), (new FixerOptionBuilder('operators', 'Dictionary of `binary operator` => `fix strategy` values that differ from the default strategy.')) ->setAllowedTypes(['array']) ->setAllowedValues([static function ($option) { foreach ($option as $operator => $value) { if (!\in_array($operator, self::$supportedOperators, true)) { throw new InvalidOptionsException( sprintf( 'Unexpected "operators" key, expected any of "%s", got "%s".', implode('", "', self::$supportedOperators), \is_object($operator) ? \get_class($operator) : \gettype($operator).'#'.$operator ) ); } if (!\in_array($value, self::$allowedValues, true)) { throw new InvalidOptionsException( sprintf( 'Unexpected value for operator "%s", expected any of "%s", got "%s".', $operator, implode('", "', self::$allowedValues), \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) ) ); } } return true; }]) ->setDefault([]) ->getOption(), (new FixerOptionBuilder('align_double_arrow', 'Whether to apply, remove or ignore double arrows alignment.')) ->setDefault(false) ->setAllowedValues([true, false, null]) ->setDeprecationMessage('Use options `operators` and `default` instead.') ->getOption(), (new FixerOptionBuilder('align_equals', 'Whether to apply, remove or ignore equals alignment.')) ->setDefault(false) ->setAllowedValues([true, false, null]) ->setDeprecationMessage('Use options `operators` and `default` instead.') ->getOption(), ]); } private function fixWhiteSpaceAroundOperator(Tokens $tokens, $index) { $tokenContent = strtolower($tokens[$index]->getContent()); if (!\array_key_exists($tokenContent, $this->operators)) { return; } if (self::SINGLE_SPACE === $this->operators[$tokenContent]) { $this->fixWhiteSpaceAroundOperatorToSingleSpace($tokens, $index); return; } if (self::NO_SPACE === $this->operators[$tokenContent]) { $this->fixWhiteSpaceAroundOperatorToNoSpace($tokens, $index); return; } $this->alignOperatorTokens[$tokenContent] = $this->operators[$tokenContent]; if (self::ALIGN === $this->operators[$tokenContent]) { return; } if ($tokens[$index + 1]->isWhitespace()) { if (self::ALIGN_SINGLE_SPACE_MINIMAL === $this->operators[$tokenContent]) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } return; } $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } private function fixWhiteSpaceAroundOperatorToSingleSpace(Tokens $tokens, $index) { if ($tokens[$index + 1]->isWhitespace()) { $content = $tokens[$index + 1]->getContent(); if (' ' !== $content && false === strpos($content, "\n") && !$tokens[$tokens->getNextNonWhitespace($index + 1)]->isComment()) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } } else { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } if ($tokens[$index - 1]->isWhitespace()) { $content = $tokens[$index - 1]->getContent(); if (' ' !== $content && false === strpos($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); } } else { $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); } } private function fixWhiteSpaceAroundOperatorToNoSpace(Tokens $tokens, $index) { if ($tokens[$index + 1]->isWhitespace()) { $content = $tokens[$index + 1]->getContent(); if (false === strpos($content, "\n") && !$tokens[$tokens->getNextNonWhitespace($index + 1)]->isComment()) { $tokens->clearAt($index + 1); } } if ($tokens[$index - 1]->isWhitespace()) { $content = $tokens[$index - 1]->getContent(); if (false === strpos($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { $tokens->clearAt($index - 1); } } } private function isEqualPartOfDeclareStatement(Tokens $tokens, $index) { $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevMeaningfulIndex]->isGivenKind(T_STRING)) { $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulIndex); if ($tokens[$prevMeaningfulIndex]->equals('(')) { $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulIndex); if ($tokens[$prevMeaningfulIndex]->isGivenKind(T_DECLARE)) { return $prevMeaningfulIndex; } } } return false; } private function resolveOperatorsFromConfig() { $operators = []; if (null !== $this->configuration['default']) { foreach (self::$supportedOperators as $operator) { $operators[$operator] = $this->configuration['default']; } } foreach ($this->configuration['operators'] as $operator => $value) { if (null === $value) { unset($operators[$operator]); } else { $operators[$operator] = $value; } } if (!\defined('T_SPACESHIP')) { unset($operators['<=>']); } if (!\defined('T_COALESCE')) { unset($operators['??']); } return $operators; } private function resolveOldConfig(array $configuration) { $newConfig = [ 'operators' => [], ]; foreach ($configuration as $name => $setting) { if ('align_double_arrow' === $name) { if (true === $configuration[$name]) { $newConfig['operators']['=>'] = self::ALIGN; } elseif (false === $configuration[$name]) { $newConfig['operators']['=>'] = self::SINGLE_SPACE; } elseif (null !== $configuration[$name]) { throw new InvalidFixerConfigurationException( $this->getName(), sprintf( 'Invalid configuration: The option "align_double_arrow" with value %s is invalid. Accepted values are: true, false, null.', $configuration[$name] ) ); } } elseif ('align_equals' === $name) { if (true === $configuration[$name]) { $newConfig['operators']['='] = self::ALIGN; } elseif (false === $configuration[$name]) { $newConfig['operators']['='] = self::SINGLE_SPACE; } elseif (null !== $configuration[$name]) { throw new InvalidFixerConfigurationException( $this->getName(), sprintf( 'Invalid configuration: The option "align_equals" with value %s is invalid. Accepted values are: true, false, null.', $configuration[$name] ) ); } } else { throw new InvalidFixerConfigurationException($this->getName(), 'Mixing old configuration with new configuration is not allowed.'); } } $message = sprintf( 'Given configuration is deprecated and will be removed in 3.0. Use configuration %s as replacement for %s.', HelpCommand::toString($newConfig), HelpCommand::toString($configuration) ); if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new InvalidFixerConfigurationException($this->getName(), "{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); return $newConfig; } private function fixAlignment(Tokens $tokens, array $toAlign) { $this->deepestLevel = 0; $this->currentLevel = 0; foreach ($toAlign as $tokenContent => $alignStrategy) { $tokensClone = clone $tokens; if ('=>' === $tokenContent) { $this->injectAlignmentPlaceholdersForArrow($tokensClone, 0, \count($tokens)); } else { $this->injectAlignmentPlaceholders($tokensClone, 0, \count($tokens), $tokenContent); } if (self::ALIGN_SINGLE_SPACE === $alignStrategy || self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy) { if ('=>' === $tokenContent) { for ($index = $tokens->count() - 2; $index > 0; --$index) { if ($tokens[$index]->isGivenKind(T_DOUBLE_ARROW)) { $this->fixWhiteSpaceBeforeOperator($tokensClone, $index, $alignStrategy); } } } elseif ('=' === $tokenContent) { for ($index = $tokens->count() - 2; $index > 0; --$index) { if ('=' === $tokens[$index]->getContent() && !$this->isEqualPartOfDeclareStatement($tokens, $index) && $this->tokensAnalyzer->isBinaryOperator($index)) { $this->fixWhiteSpaceBeforeOperator($tokensClone, $index, $alignStrategy); } } } else { for ($index = $tokens->count() - 2; $index > 0; --$index) { $content = $tokens[$index]->getContent(); if (strtolower($content) === $tokenContent && $this->tokensAnalyzer->isBinaryOperator($index)) { $this->fixWhiteSpaceBeforeOperator($tokensClone, $index, $alignStrategy); } } } } $tokens->setCode($this->replacePlaceholders($tokensClone, $alignStrategy)); } } private function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt, $tokenContent) { for ($index = $startAt; $index < $endAt; ++$index) { $token = $tokens[$index]; $content = $token->getContent(); if ( strtolower($content) === $tokenContent && $this->tokensAnalyzer->isBinaryOperator($index) && ('=' !== $content || !$this->isEqualPartOfDeclareStatement($tokens, $index)) ) { $tokens[$index] = new Token(sprintf(self::ALIGN_PLACEHOLDER, $this->deepestLevel).$content); continue; } if ($token->isGivenKind(T_FUNCTION)) { ++$this->deepestLevel; continue; } if ($token->equals('(')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->equals('[')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); continue; } } } private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, $startAt, $endAt) { for ($index = $startAt; $index < $endAt; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE, T_IF, T_SWITCH])) { $index = $tokens->getNextMeaningfulToken($index); $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->isGivenKind(T_ARRAY)) { $from = $tokens->getNextMeaningfulToken($index); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $from); $index = $until; $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { $from = $index; $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $from); $index = $until; $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); continue; } if ($token->isGivenKind(T_DOUBLE_ARROW)) { $tokenContent = sprintf(self::ALIGN_PLACEHOLDER, $this->currentLevel).$token->getContent(); $nextToken = $tokens[$index + 1]; if (!$nextToken->isWhitespace()) { $tokenContent .= ' '; } elseif ($nextToken->isWhitespace(" \t")) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } $tokens[$index] = new Token([T_DOUBLE_ARROW, $tokenContent]); continue; } if ($token->equals(';')) { ++$this->deepestLevel; ++$this->currentLevel; continue; } if ($token->equals(',')) { for ($i = $index; $i < $endAt - 1; ++$i) { if (false !== strpos($tokens[$i - 1]->getContent(), "\n")) { break; } if ($tokens[$i + 1]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $arrayStartIndex = $tokens[$i + 1]->isGivenKind(T_ARRAY) ? $tokens->getNextMeaningfulToken($i + 1) : $i + 1 ; $blockType = Tokens::detectBlockType($tokens[$arrayStartIndex]); $arrayEndIndex = $tokens->findBlockEnd($blockType['type'], $arrayStartIndex); if ($tokens->isPartialCodeMultiline($arrayStartIndex, $arrayEndIndex)) { break; } } ++$index; } } } } private function injectArrayAlignmentPlaceholders(Tokens $tokens, $from, $until) { if ($tokens->isPartialCodeMultiline($from, $until)) { ++$this->deepestLevel; ++$this->currentLevel; $this->injectAlignmentPlaceholdersForArrow($tokens, $from, $until); --$this->currentLevel; } } private function fixWhiteSpaceBeforeOperator(Tokens $tokens, $index, $alignStrategy) { if (!$tokens[$index - 1]->isWhitespace()) { $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); return; } if (self::ALIGN_SINGLE_SPACE_MINIMAL !== $alignStrategy || $tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { return; } $content = $tokens[$index - 1]->getContent(); if (' ' !== $content && false === strpos($content, "\n")) { $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); } } private function replacePlaceholders(Tokens $tokens, $alignStrategy) { $tmpCode = $tokens->generateCode(); for ($j = 0; $j <= $this->deepestLevel; ++$j) { $placeholder = sprintf(self::ALIGN_PLACEHOLDER, $j); if (false === strpos($tmpCode, $placeholder)) { continue; } $lines = explode("\n", $tmpCode); $groups = []; $groupIndex = 0; $groups[$groupIndex] = []; foreach ($lines as $index => $line) { if (substr_count($line, $placeholder) > 0) { $groups[$groupIndex][] = $index; } else { ++$groupIndex; $groups[$groupIndex] = []; } } foreach ($groups as $group) { if (\count($group) < 1) { continue; } if (self::ALIGN !== $alignStrategy) { foreach ($group as $index) { $currentPosition = strpos($lines[$index], $placeholder); $before = substr($lines[$index], 0, $currentPosition); if (self::ALIGN_SINGLE_SPACE === $alignStrategy) { if (1 > \strlen($before) || ' ' !== substr($before, -1)) { $before .= ' '; } } elseif (self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy) { if (1 !== Preg::match('/^\h+$/', $before)) { $before = rtrim($before).' '; } } $lines[$index] = $before.substr($lines[$index], $currentPosition); } } $rightmostSymbol = 0; foreach ($group as $index) { $rightmostSymbol = max($rightmostSymbol, strpos(utf8_decode($lines[$index]), $placeholder)); } foreach ($group as $index) { $line = $lines[$index]; $currentSymbol = strpos(utf8_decode($line), $placeholder); $delta = abs($rightmostSymbol - $currentSymbol); if ($delta > 0) { $line = str_replace($placeholder, str_repeat(' ', $delta).$placeholder, $line); $lines[$index] = $line; } } } $tmpCode = str_replace($placeholder, '', implode("\n", $lines)); } return $tmpCode; } } isAllTokenKindsFound(['?', ':']); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $ternaryLevel = 0; foreach ($tokens as $index => $token) { if ($token->equals('?')) { ++$ternaryLevel; $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; if ($nextNonWhitespaceToken->equals(':')) { if ($tokens[$index + 1]->isWhitespace()) { $tokens->clearAt($index + 1); } } else { $this->ensureWhitespaceExistence($tokens, $index + 1, true); } $this->ensureWhitespaceExistence($tokens, $index - 1, false); continue; } if ($ternaryLevel && $token->equals(':')) { $this->ensureWhitespaceExistence($tokens, $index + 1, true); $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; if (!$prevNonWhitespaceToken->equals('?')) { $this->ensureWhitespaceExistence($tokens, $index - 1, false); } --$ternaryLevel; } } } private function ensureWhitespaceExistence(Tokens $tokens, $index, $after) { if ($tokens[$index]->isWhitespace()) { if ( false === strpos($tokens[$index]->getContent(), "\n") && !$tokens[$index - 1]->isComment() ) { $tokens[$index] = new Token([T_WHITESPACE, ' ']); } return; } $index += $after ? 0 : 1; $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); } } count() - 1; $index >= 0; --$index) { if ($tokensAnalyzer->isUnarySuccessorOperator($index)) { if (!$tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { $tokens->removeLeadingWhitespace($index); } continue; } if ($tokensAnalyzer->isUnaryPredecessorOperator($index)) { $tokens->removeTrailingWhitespace($index); continue; } } } } isTokenKindFound('!'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if ($token->equals('!')) { if (!$tokens[$index + 1]->isWhitespace()) { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } if (!$tokens[$index - 1]->isWhitespace()) { $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); } } } } } isAnyTokenKindsFound([T_LOGICAL_AND, T_LOGICAL_OR]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_LOGICAL_AND)) { $tokens[$index] = new Token([T_BOOLEAN_AND, '&&']); } elseif ($token->isGivenKind(T_LOGICAL_OR)) { $tokens[$index] = new Token([T_BOOLEAN_OR, '||']); } } } } configuration['spacing']) { $this->fixCallback = 'fixConcatenationToSingleSpace'; } else { $this->fixCallback = 'fixConcatenationToNoSpace'; } } public function getDefinition() { return new FixerDefinition( 'Concatenation should be spaced according configuration.', [ new CodeSample( " 'none'] ), new CodeSample( " 'one'] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound('.'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $callBack = $this->fixCallback; for ($index = $tokens->count() - 1; $index >= 0; --$index) { if ($tokens[$index]->equals('.')) { $this->{$callBack}($tokens, $index); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('spacing', 'Spacing to apply around concatenation operator.')) ->setAllowedValues(['one', 'none']) ->setDefault('none') ->getOption(), ]); } private function fixConcatenationToNoSpace(Tokens $tokens, $index) { $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; if (!$prevNonWhitespaceToken->isGivenKind([T_LNUMBER, T_COMMENT, T_DOC_COMMENT]) || '/*' === substr($prevNonWhitespaceToken->getContent(), 0, 2)) { $tokens->removeLeadingWhitespace($index, " \t"); } if (!$tokens[$tokens->getNextNonWhitespace($index)]->isGivenKind([T_LNUMBER, T_COMMENT, T_DOC_COMMENT])) { $tokens->removeTrailingWhitespace($index, " \t"); } } private function fixConcatenationToSingleSpace(Tokens $tokens, $index) { $this->fixWhiteSpaceAroundConcatToken($tokens, $index, 1); $this->fixWhiteSpaceAroundConcatToken($tokens, $index, -1); } private function fixWhiteSpaceAroundConcatToken(Tokens $tokens, $index, $offset) { $offsetIndex = $index + $offset; if (!$tokens[$offsetIndex]->isWhitespace()) { $tokens->insertAt($index + (1 === $offset ?: 0), new Token([T_WHITESPACE, ' '])); return; } if (false !== strpos($tokens[$offsetIndex]->getContent(), "\n")) { return; } if ($tokens[$index + $offset * 2]->isComment()) { return; } $tokens[$offsetIndex] = new Token([T_WHITESPACE, ' ']); } } asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true); $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true); } public function getDefinition() { return new FixerDefinition( 'Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax.', [ new CodeSample( ' ['asterisk']] ), new CodeSample( " ['hash']] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_COMMENT)) { continue; } $content = $token->getContent(); $commentContent = substr($content, 2, -2) ?: ''; if ($this->hashEnabled && '#' === $content[0]) { $tokens[$index] = new Token([$token->getId(), '//'.substr($content, 1)]); continue; } if ( !$this->asteriskEnabled || false !== strpos($commentContent, '?>') || '/*' !== substr($content, 0, 2) || 1 === Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent) ) { continue; } $nextTokenIndex = $index + 1; if (isset($tokens[$nextTokenIndex])) { $nextToken = $tokens[$nextTokenIndex]; if (!$nextToken->isWhitespace() || 1 !== Preg::match('/\R/', $nextToken->getContent())) { continue; } $tokens[$nextTokenIndex] = new Token([$nextToken->getId(), ltrim($nextToken->getContent(), " \t")]); } $content = '//'; if (1 === Preg::match('/[^\s\*]/', $commentContent)) { $content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent); } $tokens[$index] = new Token([$token->getId(), $content]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('comment_types', 'List of comment types to fix')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(['asterisk', 'hash'])]) ->setDefault(['asterisk', 'hash']) ->getOption(), ]); } } isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_DOC_COMMENT)) { $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace('/[ \t]+$/m', '', $token->getContent())]); continue; } if ($token->isGivenKind(T_COMMENT)) { if ('/*' === substr($token->getContent(), 0, 2)) { $tokens[$index] = new Token([T_COMMENT, Preg::replace('/[ \t]+$/m', '', $token->getContent())]); } elseif (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) { $trimmedContent = ltrim($tokens[$index + 1]->getContent(), " \t"); if ('' !== $trimmedContent) { $tokens[$index + 1] = new Token([T_WHITESPACE, $trimmedContent]); } else { $tokens->clearAt($index + 1); } } } } } } 'Made with love.', ] ), new CodeSample( ' 'Made with love.', 'comment_type' => 'PHPDoc', 'location' => 'after_open', 'separate' => 'bottom', ] ), new CodeSample( ' 'Made with love.', 'comment_type' => 'comment', 'location' => 'after_declare_strict', ] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens[0]->isGivenKind(T_OPEN_TAG) && $tokens->isMonolithicPhp(); } public function getPriority() { return -30; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $headerNewIndex = $this->findHeaderCommentInsertionIndex($tokens); $headerCurrentIndex = $this->findHeaderCommentCurrentIndex($tokens, $headerNewIndex - 1); if (null === $headerCurrentIndex) { if ('' === $this->configuration['header']) { return; } $this->insertHeader($tokens, $headerNewIndex); } elseif ($this->getHeaderAsComment() !== $tokens[$headerCurrentIndex]->getContent()) { $tokens->clearTokenAndMergeSurroundingWhitespace($headerCurrentIndex); if ('' === $this->configuration['header']) { return; } $this->insertHeader($tokens, $headerNewIndex); } else { $headerNewIndex = $headerCurrentIndex; } $this->fixWhiteSpaceAroundHeader($tokens, $headerNewIndex); } protected function createConfigurationDefinition() { $fixerName = $this->getName(); return new FixerConfigurationResolver([ (new FixerOptionBuilder('header', 'Proper header content.')) ->setAllowedTypes(['string']) ->setNormalizer(static function (Options $options, $value) use ($fixerName) { if ('' === trim($value)) { return ''; } if (false !== strpos($value, '*/')) { throw new InvalidFixerConfigurationException($fixerName, 'Cannot use \'*/\' in header.'); } return $value; }) ->getOption(), (new AliasedFixerOptionBuilder( new FixerOptionBuilder('comment_type', 'Comment syntax type.'), 'commentType' )) ->setAllowedValues([self::HEADER_PHPDOC, self::HEADER_COMMENT]) ->setDefault(self::HEADER_COMMENT) ->getOption(), (new FixerOptionBuilder('location', 'The location of the inserted header.')) ->setAllowedValues(['after_open', 'after_declare_strict']) ->setDefault('after_declare_strict') ->getOption(), (new FixerOptionBuilder('separate', 'Whether the header should be separated from the file content with a new line.')) ->setAllowedValues(['both', 'top', 'bottom', 'none']) ->setDefault('both') ->getOption(), ]); } private function getHeaderAsComment() { $lineEnding = $this->whitespacesConfig->getLineEnding(); $comment = (self::HEADER_COMMENT === $this->configuration['comment_type'] ? '/*' : '/**').$lineEnding; $lines = explode("\n", str_replace("\r", '', $this->configuration['header'])); foreach ($lines as $line) { $comment .= rtrim(' * '.$line).$lineEnding; } return $comment.' */'; } private function findHeaderCommentCurrentIndex(Tokens $tokens, $headerNewIndex) { $index = $tokens->getNextNonWhitespace($headerNewIndex); return null === $index || !$tokens[$index]->isComment() ? null : $index; } private function findHeaderCommentInsertionIndex(Tokens $tokens) { if ('after_open' === $this->configuration['location']) { return 1; } $index = $tokens->getNextMeaningfulToken(0); if (null === $index) { return 1; } if (!$tokens[$index]->isGivenKind(T_DECLARE)) { return 1; } $next = $tokens->getNextMeaningfulToken($index); if (null === $next || !$tokens[$next]->equals('(')) { return 1; } $next = $tokens->getNextMeaningfulToken($next); if (null === $next || !$tokens[$next]->equals([T_STRING, 'strict_types'], false)) { return 1; } $next = $tokens->getNextMeaningfulToken($next); if (null === $next || !$tokens[$next]->equals('=')) { return 1; } $next = $tokens->getNextMeaningfulToken($next); if (null === $next || !$tokens[$next]->isGivenKind(T_LNUMBER)) { return 1; } $next = $tokens->getNextMeaningfulToken($next); if (null === $next || !$tokens[$next]->equals(')')) { return 1; } $next = $tokens->getNextMeaningfulToken($next); if (null === $next || !$tokens[$next]->equals(';')) { return 1; } return $next + 1; } private function fixWhiteSpaceAroundHeader(Tokens $tokens, $headerIndex) { $lineEnding = $this->whitespacesConfig->getLineEnding(); $expectedLineCount = 'both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate'] ? 2 : 1; if ($headerIndex === \count($tokens) - 1) { $tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount)])); } else { $afterCommentIndex = $tokens->getNextNonWhitespace($headerIndex); $lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex + 1, null === $afterCommentIndex ? \count($tokens) : $afterCommentIndex); if ($lineBreakCount < $expectedLineCount) { $missing = str_repeat($lineEnding, $expectedLineCount - $lineBreakCount); if ($tokens[$headerIndex + 1]->isWhitespace()) { $tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $missing.$tokens[$headerIndex + 1]->getContent()]); } else { $tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, $missing])); } } elseif ($lineBreakCount > 2) { if ($tokens[$headerIndex + 1]->isWhitespace()) { $tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $lineEnding.$lineEnding]); } } } $expectedLineCount = 'both' === $this->configuration['separate'] || 'top' === $this->configuration['separate'] ? 2 : 1; $prev = $tokens->getPrevNonWhitespace($headerIndex); $regex = '/[\t ]$/'; if ($tokens[$prev]->isGivenKind(T_OPEN_TAG) && Preg::match($regex, $tokens[$prev]->getContent())) { $tokens[$prev] = new Token([T_OPEN_TAG, Preg::replace($regex, $lineEnding, $tokens[$prev]->getContent())]); } $lineBreakCount = $this->getLineBreakCount($tokens, $prev, $headerIndex); if ($lineBreakCount < $expectedLineCount) { $tokens->insertAt($headerIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount - $lineBreakCount)])); } } private function getLineBreakCount(Tokens $tokens, $indexStart, $indexEnd) { $lineCount = 0; for ($i = $indexStart; $i < $indexEnd; ++$i) { $lineCount += substr_count($tokens[$i]->getContent(), "\n"); } return $lineCount; } private function insertHeader(Tokens $tokens, $index) { $tokens->insertAt($index, new Token([self::HEADER_COMMENT === $this->configuration['comment_type'] ? T_COMMENT : T_DOC_COMMENT, $this->getHeaderAsComment()])); } } isTokenKindFound(T_COMMENT); } public function isRisky() { return true; } public function getPriority() { return 26; } public function getDefinition() { return new FixerDefinition( 'Comments with annotation should be docblock when used on structural elements.', [new CodeSample("isGivenKind(T_COMMENT)) { continue; } if ($commentsAnalyzer->isHeaderComment($tokens, $index)) { continue; } if (!$commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) { continue; } $commentIndices = $commentsAnalyzer->getCommentBlockIndices($tokens, $index); if ($this->isCommentCandidate($tokens, $commentIndices)) { $this->fixComment($tokens, $commentIndices); } $index = max($commentIndices); } } private function isCommentCandidate(Tokens $tokens, array $indices) { return array_reduce( $indices, function ($carry, $index) use ($tokens) { return $carry || 1 === Preg::match('~(#|//|/\*+|\R(\s*\*)?)\s*\@[a-zA-Z0-9_\\\\-]+(?=\s|\(|$)~', $tokens[$index]->getContent()); }, false ); } private function fixComment(Tokens $tokens, $indices) { if (1 === \count($indices)) { $this->fixCommentSingleLine($tokens, reset($indices)); } else { $this->fixCommentMultiLine($tokens, $indices); } } private function fixCommentSingleLine(Tokens $tokens, $index) { $message = $this->getMessage($tokens[$index]->getContent()); if ('' !== trim(substr($message, 0, 1))) { $message = ' '.$message; } if ('' !== trim(substr($message, -1))) { $message .= ' '; } $tokens[$index] = new Token([T_DOC_COMMENT, '/**'.$message.'*/']); } private function fixCommentMultiLine(Tokens $tokens, array $indices) { $startIndex = reset($indices); $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$startIndex - 1]); $newContent = '/**'.$this->whitespacesConfig->getLineEnding(); $count = max($indices); for ($index = $startIndex; $index <= $count; ++$index) { if (!$tokens[$index]->isComment()) { continue; } if (false !== strpos($tokens[$index]->getContent(), '*/')) { return; } $newContent .= $indent.' *'.$this->getMessage($tokens[$index]->getContent()).$this->whitespacesConfig->getLineEnding(); } for ($index = $startIndex; $index <= $count; ++$index) { $tokens->clearAt($index); } $newContent .= $indent.' */'; $tokens->insertAt($startIndex, new Token([T_DOC_COMMENT, $newContent])); } private function getMessage($content) { if (0 === strpos($content, '#')) { return substr($content, 1); } if (0 === strpos($content, '//')) { return substr($content, 2); } return rtrim(ltrim($content, '/*'), '*/'); } } proxyFixers); } protected function createProxyFixers() { $fixer = new SingleLineCommentStyleFixer(); $fixer->configure(['comment_types' => ['hash']]); return [$fixer]; } } isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { $originalContent = $token->getContent(); if ( !$token->isGivenKind(T_DOC_COMMENT) && !($token->isGivenKind(T_COMMENT) && 0 === strpos($originalContent, '/*')) ) { continue; } $newContent = $originalContent; if ($token->isGivenKind(T_COMMENT)) { $newContent = Preg::replace('/^\\/\\*{2,}(?!\\/)/', '/*', $newContent); } $newContent = Preg::replace('/(?getId(), $newContent]); } } } } isTokenKindFound(T_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { if (!$tokens[$index]->isGivenKind(T_COMMENT)) { continue; } list($blockStart, $index, $isEmpty) = $this->getCommentBlock($tokens, $index); if (false === $isEmpty) { continue; } for ($i = $blockStart; $i <= $index; ++$i) { $tokens->clearTokenAndMergeSurroundingWhitespace($i); } } } private function getCommentBlock(Tokens $tokens, $index) { $commentType = $this->getCommentType($tokens[$index]->getContent()); $empty = $this->isEmptyComment($tokens[$index]->getContent()); $start = $index; $count = \count($tokens); ++$index; for (; $index < $count; ++$index) { if ($tokens[$index]->isComment()) { if ($commentType !== $this->getCommentType($tokens[$index]->getContent())) { break; } if ($empty) { $empty = $this->isEmptyComment($tokens[$index]->getContent()); } continue; } if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) { break; } } return [$start, $index - 1, $empty]; } private function getCommentType($content) { if ('#' === $content[0]) { return self::TYPE_HASH; } if ('*' === $content[1]) { return self::TYPE_SLASH_ASTERISK; } return self::TYPE_DOUBLE_SLASH; } private function getLineBreakCount(Tokens $tokens, $whiteStart, $whiteEnd) { $lineCount = 0; for ($i = $whiteStart; $i < $whiteEnd; ++$i) { $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent(), $matches); } return $lineCount; } private function isEmptyComment($content) { static $mapper = [ self::TYPE_HASH => '|^#\s*$|', self::TYPE_SLASH_ASTERISK => '|^/\*\s*\*/$|', self::TYPE_DOUBLE_SLASH => '|^//\s*$|', ]; $type = $this->getCommentType($content); return 1 === Preg::match($mapper[$type], $content); } } isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $end = $tokens->count() - 1; $functionsAnalyzer = new FunctionsAnalyzer(); foreach (self::$functions as $map) { $seq = [[T_STRING, $map[0]], '(', [T_CONSTANT_ENCAPSED_STRING]]; $currIndex = 0; while (null !== $currIndex) { $match = $tokens->findSequence($seq, $currIndex, $end, false); if (null === $match) { break; } $match = array_keys($match); $currIndex = $match[2]; if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $match[0])) { continue; } $next = $tokens->getNextMeaningfulToken($match[2]); if (null === $next || !$tokens[$next]->equalsAny([',', ')'])) { continue; } $regexTokenContent = $tokens[$match[2]]->getContent(); $string = substr($regexTokenContent, 1, -1); $quote = $regexTokenContent[0]; $delim = $this->getBestDelimiter($string); $preg = $delim.addcslashes($string, $delim).$delim.'D'.$map[2]; if (!$this->checkPreg($preg)) { continue; } $tokens[$match[0]] = new Token([T_STRING, $map[1]]); $tokens[$match[2]] = new Token([T_CONSTANT_ENCAPSED_STRING, $quote.$preg.$quote]); } } } private function checkPreg($pattern) { try { Preg::match($pattern, ''); return true; } catch (PregException $e) { return false; } } private function getBestDelimiter($pattern) { $delimiters = []; foreach (self::$delimiters as $k => $d) { if (false === strpos($pattern, $d)) { return $d; } $delimiters[$d] = [substr_count($pattern, $d), $k]; } uasort($delimiters, static function ($a, $b) { if ($a[0] === $b[0]) { return Utils::cmpInt($a, $b); } return $a[0] < $b[0] ? -1 : 1; }); return key($delimiters); } } ['alternativeName' => 'mb_strlen', 'argumentCount' => [1]], 'strpos' => ['alternativeName' => 'mb_strpos', 'argumentCount' => [2, 3]], 'strrpos' => ['alternativeName' => 'mb_strrpos', 'argumentCount' => [2, 3]], 'substr' => ['alternativeName' => 'mb_substr', 'argumentCount' => [2, 3]], 'strtolower' => ['alternativeName' => 'mb_strtolower', 'argumentCount' => [1]], 'strtoupper' => ['alternativeName' => 'mb_strtoupper', 'argumentCount' => [1]], 'stripos' => ['alternativeName' => 'mb_stripos', 'argumentCount' => [2, 3]], 'strripos' => ['alternativeName' => 'mb_strripos', 'argumentCount' => [2, 3]], 'strstr' => ['alternativeName' => 'mb_strstr', 'argumentCount' => [2, 3]], 'stristr' => ['alternativeName' => 'mb_stristr', 'argumentCount' => [2, 3]], 'strrchr' => ['alternativeName' => 'mb_strrchr', 'argumentCount' => [2]], 'substr_count' => ['alternativeName' => 'mb_substr_count', 'argumentCount' => [2, 3, 4]], ]; public function getDefinition() { return new FixerDefinition( 'Replace non multibyte-safe functions with corresponding mb function.', [ new CodeSample( 'isTokenKindFound(T_STRING); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach (self::$functions as $functionIdentity => $functionReplacement) { $currIndex = 0; while (null !== $currIndex) { $boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1); if (null === $boundaries) { continue 2; } list($functionName, $openParenthesis, $closeParenthesis) = $boundaries; $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); if (!\in_array($count, $functionReplacement['argumentCount'], true)) { continue 2; } $currIndex = $openParenthesis; $tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]); } } } } isAllTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_STRING, T_VARIABLE]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $map = [ 'array' => [T_ARRAY_CAST, '(array)'], 'bool' => [T_BOOL_CAST, '(bool)'], 'boolean' => [T_BOOL_CAST, '(bool)'], 'double' => [T_DOUBLE_CAST, '(float)'], 'float' => [T_DOUBLE_CAST, '(float)'], 'int' => [T_INT_CAST, '(int)'], 'integer' => [T_INT_CAST, '(int)'], 'object' => [T_OBJECT_CAST, '(object)'], 'string' => [T_STRING_CAST, '(string)'], ]; $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach (array_reverse($this->findSettypeCalls($tokens)) as $candidate) { $functionNameIndex = $candidate[0]; $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]); if (2 !== \count($arguments)) { continue; } $prev = $tokens->getPrevMeaningfulToken($functionNameIndex); if (!$tokens[$prev]->isGivenKind(T_OPEN_TAG) && !$tokens[$prev]->equalsAny([';', '{'])) { continue; } reset($arguments); $firstArgumentStart = key($arguments); if ($tokens[$firstArgumentStart]->isComment() || $tokens[$firstArgumentStart]->isWhitespace()) { $firstArgumentStart = $tokens->getNextMeaningfulToken($firstArgumentStart); } if (!$tokens[$firstArgumentStart]->isGivenKind(T_VARIABLE)) { continue; } $commaIndex = $tokens->getNextMeaningfulToken($firstArgumentStart); if (null === $commaIndex || !$tokens[$commaIndex]->equals(',')) { continue; } next($arguments); $secondArgumentStart = key($arguments); $secondArgumentEnd = $arguments[$secondArgumentStart]; if ($tokens[$secondArgumentStart]->isComment() || $tokens[$secondArgumentStart]->isWhitespace()) { $secondArgumentStart = $tokens->getNextMeaningfulToken($secondArgumentStart); } if ( !$tokens[$secondArgumentStart]->isGivenKind(T_CONSTANT_ENCAPSED_STRING) || $tokens->getNextMeaningfulToken($secondArgumentStart) < $secondArgumentEnd ) { continue; } $type = strtolower(trim($tokens[$secondArgumentStart]->getContent(), '"\'"')); if ('null' !== $type && !isset($map[$type])) { continue; } $argumentToken = $tokens[$firstArgumentStart]; $this->removeSettypeCall( $tokens, $functionNameIndex, $candidate[1], $firstArgumentStart, $commaIndex, $secondArgumentStart, $candidate[2] ); if ('null' === $type) { $this->findSettypeNullCall($tokens, $functionNameIndex, $argumentToken); } else { $this->fixSettypeCall($tokens, $functionNameIndex, $argumentToken, new Token($map[$type])); } } } private function findSettypeCalls(Tokens $tokens) { $candidates = []; $end = \count($tokens); for ($i = 1; $i < $end; ++$i) { $candidate = $this->find('settype', $tokens, $i, $end); if (null === $candidate) { break; } $i = $candidate[1]; $candidates[] = $candidate; } return $candidates; } private function removeSettypeCall( Tokens $tokens, $functionNameIndex, $openParenthesisIndex, $firstArgumentStart, $commaIndex, $secondArgumentStart, $closeParenthesisIndex ) { $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex); $prevIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); if ($tokens[$prevIndex]->equals(',')) { $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); } $tokens->clearTokenAndMergeSurroundingWhitespace($secondArgumentStart); $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStart); $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex); $tokens->clearAt($functionNameIndex); $tokens->clearEmptyTokens(); } private function fixSettypeCall( Tokens $tokens, $functionNameIndex, Token $argumentToken, Token $castToken ) { $tokens->insertAt( $functionNameIndex, [ clone $argumentToken, new Token([T_WHITESPACE, ' ']), new Token('='), new Token([T_WHITESPACE, ' ']), $castToken, new Token([T_WHITESPACE, ' ']), clone $argumentToken, ] ); $tokens->removeTrailingWhitespace($functionNameIndex + 6); } private function findSettypeNullCall( Tokens $tokens, $functionNameIndex, Token $argumentToken ) { $tokens->insertAt( $functionNameIndex, [ clone $argumentToken, new Token([T_WHITESPACE, ' ']), new Token('='), new Token([T_WHITESPACE, ' ']), new Token([T_STRING, 'null']), ] ); $tokens->removeTrailingWhitespace($functionNameIndex + 4); } } 'rtrim', 'close' => 'closedir', 'doubleval' => 'floatval', 'fputs' => 'fwrite', 'get_required_files' => 'get_included_files', 'ini_alter' => 'ini_set', 'is_double' => 'is_float', 'is_integer' => 'is_int', 'is_long' => 'is_int', 'is_real' => 'is_float', 'is_writeable' => 'is_writable', 'join' => 'implode', 'key_exists' => 'array_key_exists', 'magic_quotes_runtime' => 'set_magic_quotes_runtime', 'pos' => 'current', 'show_source' => 'highlight_file', 'sizeof' => 'count', 'strchr' => 'strstr', 'user_error' => 'trigger_error', ]; private static $imapSet = [ 'imap_create' => 'imap_createmailbox', 'imap_fetchtext' => 'imap_body', 'imap_header' => 'imap_headerinfo', 'imap_listmailbox' => 'imap_list', 'imap_listsubscribed' => 'imap_lsub', 'imap_rename' => 'imap_renamemailbox', 'imap_scan' => 'imap_listscan', 'imap_scanmailbox' => 'imap_listscan', ]; private static $mbregSet = [ 'mbereg' => 'mb_ereg', 'mbereg_match' => 'mb_ereg_match', 'mbereg_replace' => 'mb_ereg_replace', 'mbereg_search' => 'mb_ereg_search', 'mbereg_search_getpos' => 'mb_ereg_search_getpos', 'mbereg_search_getregs' => 'mb_ereg_search_getregs', 'mbereg_search_init' => 'mb_ereg_search_init', 'mbereg_search_pos' => 'mb_ereg_search_pos', 'mbereg_search_regs' => 'mb_ereg_search_regs', 'mbereg_search_setpos' => 'mb_ereg_search_setpos', 'mberegi' => 'mb_eregi', 'mberegi_replace' => 'mb_eregi_replace', 'mbregex_encoding' => 'mb_regex_encoding', 'mbsplit' => 'mb_split', ]; public function configure(array $configuration = null) { parent::configure($configuration); $this->aliases = []; foreach ($this->configuration['sets'] as $set) { if ('@all' === $set) { $this->aliases = self::$internalSet; $this->aliases = array_merge($this->aliases, self::$imapSet); $this->aliases = array_merge($this->aliases, self::$mbregSet); break; } if ('@internal' === $set) { $this->aliases = array_merge($this->aliases, self::$internalSet); } elseif ('@IMAP' === $set) { $this->aliases = array_merge($this->aliases, self::$imapSet); } elseif ('@mbreg' === $set) { $this->aliases = array_merge($this->aliases, self::$mbregSet); } } } public function getDefinition() { return new FixerDefinition( 'Master functions shall be used instead of aliases.', [ new CodeSample( ' ['@mbreg']] ), ], null, 'Risky when any of the alias functions are overridden.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $functionsAnalyzer = new FunctionsAnalyzer(); foreach ($tokens->findGivenKind(T_STRING) as $index => $token) { $tokenContent = strtolower($token->getContent()); if (!isset($this->aliases[$tokenContent])) { continue; } $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if (!$nextToken->equals('(')) { continue; } if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { continue; } $tokens[$index] = new Token([T_STRING, $this->aliases[$tokenContent]]); } } protected function createConfigurationDefinition() { $sets = ['@internal', '@IMAP', '@mbreg', '@all']; return new FixerConfigurationResolver([ (new FixerOptionBuilder('sets', 'List of sets to fix. Defined sets are `@internal` (native functions), `@IMAP` (IMAP functions), `@mbreg` (from `ext-mbstring`) `@all` (all listed sets).')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset($sets)]) ->setDefault(['@internal', '@IMAP']) ->getOption(), ]); } } 'echo']; private $callBack; private $candidateTokenType; public function configure(array $configuration = null) { parent::configure($configuration); if ('echo' === $this->configuration['use']) { $this->candidateTokenType = T_PRINT; $this->callBack = 'fixPrintToEcho'; } else { $this->candidateTokenType = T_ECHO; $this->callBack = 'fixEchoToPrint'; } } public function getDefinition() { return new FixerDefinition( 'Either language construct `print` or `echo` should be used.', [ new CodeSample(" 'print']), ] ); } public function getPriority() { return -10; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound($this->candidateTokenType); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $callBack = $this->callBack; foreach ($tokens as $index => $token) { if ($token->isGivenKind($this->candidateTokenType)) { $this->{$callBack}($tokens, $index); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('use', 'The desired language construct.')) ->setAllowedValues(['print', 'echo']) ->setDefault('echo') ->getOption(), ]); } private function fixEchoToPrint(Tokens $tokens, $index) { $nextTokenIndex = $tokens->getNextMeaningfulToken($index); $endTokenIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); $canBeConverted = true; for ($i = $nextTokenIndex; $i < $endTokenIndex; ++$i) { if ($tokens[$i]->equalsAny(['(', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) { $blockType = Tokens::detectBlockType($tokens[$i]); $i = $tokens->findBlockEnd($blockType['type'], $i); } if ($tokens[$i]->equals(',')) { $canBeConverted = false; break; } } if (false === $canBeConverted) { return; } $tokens[$index] = new Token([T_PRINT, 'print']); } private function fixPrintToEcho(Tokens $tokens, $index) { $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if (!$prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { return; } $tokens[$index] = new Token([T_ECHO, 'echo']); } } count() > 7 && $tokens->isTokenKindFound(T_STRING); } public function getDefinition() { return new FixerDefinition( 'Converts `pow` to the `**` operator.', [ new CodeSample( "findPowCalls($tokens); $argumentsAnalyzer = new ArgumentsAnalyzer(); $numberOfTokensAdded = 0; $previousCloseParenthesisIndex = \count($tokens); foreach (array_reverse($candidates) as $candidate) { if ($previousCloseParenthesisIndex < $candidate[2]) { $previousCloseParenthesisIndex = $candidate[2]; $candidate[2] += $numberOfTokensAdded; } else { $previousCloseParenthesisIndex = $candidate[2]; $numberOfTokensAdded = 0; } $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]); if (2 !== \count($arguments)) { continue; } $numberOfTokensAdded += $this->fixPowToExponentiation( $tokens, $candidate[0], $candidate[1], $candidate[2], $arguments ); } } private function findPowCalls(Tokens $tokens) { $candidates = []; $end = \count($tokens) - 6; for ($i = 1; $i < $end; ++$i) { $candidate = $this->find('pow', $tokens, $i, $end); if (null === $candidate) { break; } $i = $candidate[1]; $candidates[] = $candidate; } return $candidates; } private function fixPowToExponentiation(Tokens $tokens, $functionNameIndex, $openParenthesisIndex, $closeParenthesisIndex, array $arguments) { $tokens[$tokens->getNextTokenOfKind(reset($arguments), [','])] = new Token([T_POW, '**']); $tokens->clearAt($closeParenthesisIndex); $previousIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); if ($tokens[$previousIndex]->equals(',')) { $tokens->clearAt($previousIndex); } $added = 0; foreach (array_reverse($arguments, true) as $argumentStartIndex => $argumentEndIndex) { if ($this->isParenthesisNeeded($tokens, $argumentStartIndex, $argumentEndIndex)) { $tokens->insertAt($argumentEndIndex + 1, new Token(')')); $tokens->insertAt($argumentStartIndex, new Token('(')); $added += 2; } } $tokens->clearAt($openParenthesisIndex); $tokens->clearAt($functionNameIndex); $prev = $tokens->getPrevMeaningfulToken($functionNameIndex); if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { $tokens->clearAt($prev); } return $added; } private function isParenthesisNeeded(Tokens $tokens, $argumentStartIndex, $argumentEndIndex) { static $allowedKinds = [ T_DNUMBER, T_LNUMBER, T_VARIABLE, T_STRING, T_OBJECT_OPERATOR, T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_CAST, T_INT_CAST, T_INC, T_DEC, T_NS_SEPARATOR, T_WHITESPACE, T_DOUBLE_COLON, T_LINE, T_COMMENT, T_DOC_COMMENT, CT::T_NAMESPACE_OPERATOR, ]; for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) { if ($tokens[$i]->isGivenKind($allowedKinds) || $tokens->isEmptyAt($i)) { continue; } if (null !== $blockType = Tokens::detectBlockType($tokens[$i])) { $i = $tokens->findBlockEnd($blockType['type'], $i); continue; } if ($tokens[$i]->equals('$')) { $i = $tokens->getNextMeaningfulToken($i); if ($tokens[$i]->isGivenKind(CT::T_DYNAMIC_VAR_BRACE_OPEN)) { $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, $i); continue; } } if ($tokens[$i]->equals('+') && $tokens->getPrevMeaningfulToken($i) < $argumentStartIndex) { continue; } return true; } return false; } } isTokenKindFound('`'); } public function getDefinition() { return new FixerDefinition( 'Converts backtick operators to `shell_exec` calls.', [ new CodeSample( <<<'EOT' call()}`; EOT ), ], 'Conversion is done only when it is non risky, so when special chars like single-quotes, double-quotes and backticks are not used inside the command.' ); } public function getPriority() { return 2; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $backtickStarted = false; $backtickTokens = []; for ($index = $tokens->count() - 1; $index > 0; --$index) { $token = $tokens[$index]; if (!$token->equals('`')) { if ($backtickStarted) { $backtickTokens[$index] = $token; } continue; } $backtickTokens[$index] = $token; if ($backtickStarted) { $this->fixBackticks($tokens, $backtickTokens); $backtickTokens = []; } $backtickStarted = !$backtickStarted; } } private function fixBackticks(Tokens $tokens, array $backtickTokens) { ksort($backtickTokens); $openingBacktickIndex = key($backtickTokens); end($backtickTokens); $closingBacktickIndex = key($backtickTokens); array_shift($backtickTokens); array_pop($backtickTokens); $count = \count($backtickTokens); $newTokens = [ new Token([T_STRING, 'shell_exec']), new Token('('), ]; if (1 !== $count) { $newTokens[] = new Token('"'); } foreach ($backtickTokens as $token) { if (!$token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { $newTokens[] = $token; continue; } $content = $token->getContent(); if (Preg::match('/[`"\']/u', $content)) { return; } $kind = T_ENCAPSED_AND_WHITESPACE; if (1 === $count) { $content = '"'.$content.'"'; $kind = T_CONSTANT_ENCAPSED_STRING; } $newTokens[] = new Token([$kind, $content]); } if (1 !== $count) { $newTokens[] = new Token('"'); } $newTokens[] = new Token(')'); $tokens->overrideRange($openingBacktickIndex, $closingBacktickIndex, $newTokens); } } [0], 'mt_rand' => [1, 2], 'rand' => [0, 2], 'srand' => [0, 1], ]; public function configure(array $configuration = null) { parent::configure($configuration); foreach ($this->configuration['replacements'] as $functionName => $replacement) { $this->configuration['replacements'][$functionName] = [ 'alternativeName' => $replacement, 'argumentCount' => self::$argumentCounts[$functionName], ]; } } public function getDefinition() { return new FixerDefinition( 'Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs.', [ new CodeSample(" ['getrandmax' => 'mt_getrandmax']] ), ], null, 'Risky when the configured functions are overridden.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach ($this->configuration['replacements'] as $functionIdentity => $functionReplacement) { if ($functionIdentity === $functionReplacement['alternativeName']) { continue; } $currIndex = 0; while (null !== $currIndex) { $boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1); if (null === $boundaries) { continue 2; } list($functionName, $openParenthesis, $closeParenthesis) = $boundaries; $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); if (!\in_array($count, $functionReplacement['argumentCount'], true)) { continue 2; } $currIndex = $openParenthesis; $tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]); if (0 === $count && 'random_int' === $functionReplacement['alternativeName']) { $tokens->insertAt($currIndex + 1, [ new Token([T_LNUMBER, '0']), new Token(','), new Token([T_WHITESPACE, ' ']), new Token([T_STRING, 'getrandmax']), new Token('('), new Token(')'), ]); $currIndex += 6; } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('replacements', [ (new FixerOptionBuilder('replacements', 'Mapping between replaced functions with the new ones.')) ->setAllowedTypes(['array']) ->setAllowedValues([static function ($value) { foreach ($value as $functionName => $replacement) { if (!\array_key_exists($functionName, self::$argumentCounts)) { throw new InvalidOptionsException(sprintf( 'Function "%s" is not handled by the fixer.', $functionName )); } if (!\is_string($replacement)) { throw new InvalidOptionsException(sprintf( 'Replacement for function "%s" must be a string, "%s" given.', $functionName, \is_object($replacement) ? \get_class($replacement) : \gettype($replacement) )); } } return true; }]) ->setDefault([ 'getrandmax' => 'mt_getrandmax', 'rand' => 'mt_rand', 'srand' => 'mt_srand', ]) ->getOption(), ], $this->getName()); } } isAllTokenKindsFound([T_CLASS, T_FINAL]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensCount = \count($tokens); for ($index = 0; $index < $tokensCount; ++$index) { if (!$tokens[$index]->isGivenKind(T_CLASS)) { continue; } $classOpen = $tokens->getNextTokenOfKind($index, ['{']); $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; $classIsFinal = $prevToken->isGivenKind(T_FINAL); $this->fixClass($tokens, $classOpen, $classIsFinal); } } private function fixClass(Tokens $tokens, $classOpenIndex, $classIsFinal) { $tokensCount = \count($tokens); for ($index = $classOpenIndex + 1; $index < $tokensCount; ++$index) { if ($tokens[$index]->equals('}')) { return; } if ($tokens[$index]->equals('{')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); continue; } if (!$tokens[$index]->isGivenKind(T_FINAL)) { continue; } if (!$classIsFinal && !$this->isPrivateMethod($tokens, $index, $classOpenIndex)) { continue; } $tokens->clearAt($index); $nextTokenIndex = $index + 1; if ($tokens[$nextTokenIndex]->isWhitespace()) { $tokens->clearAt($nextTokenIndex); } } } private function isPrivateMethod(Tokens $tokens, $index, $classOpenIndex) { $index = max($classOpenIndex + 1, $tokens->getPrevTokenOfKind($index, [';', '{', '}'])); while (!$tokens[$index]->isGivenKind(T_FUNCTION)) { if ($tokens[$index]->isGivenKind(T_PRIVATE)) { return true; } ++$index; } return false; } } isAnyTokenKindsFound(Token::getClassyTokenKinds()); } public function getDefinition() { return new FixerDefinition( 'There should be no empty lines after class opening brace.', [ new CodeSample( ' $token) { if (!$token->isClassy()) { continue; } $startBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); if (!$tokens[$startBraceIndex + 1]->isWhitespace()) { continue; } $this->fixWhitespace($tokens, $startBraceIndex + 1); } } private function fixWhitespace(Tokens $tokens, $index) { $content = $tokens[$index]->getContent(); if (substr_count($content, "\n") > 1) { $tokens[$index] = new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().substr($content, strrpos($content, "\n") + 1)]); } } } isTokenKindFound(T_CLASS); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $classes = array_keys($tokens->findGivenKind(T_CLASS)); $numClasses = \count($classes); for ($i = 0; $i < $numClasses; ++$i) { $index = $classes[$i]; if ($tokensAnalyzer->isAnonymousClass($index)) { continue; } $nspIndex = $tokens->getPrevTokenOfKind($index, [[T_NAMESPACE, 'namespace']]); if (null !== $nspIndex) { $nspIndex = $tokens->getNextMeaningfulToken($nspIndex); if (!$tokens[$nspIndex]->equals('{')) { $nspIndex = $tokens->getNextTokenOfKind($nspIndex, [';', '{']); if ($tokens[$nspIndex]->equals(';')) { break; } $nspEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nspIndex); if ($index < $nspEnd) { for ($j = $i + 1; $j < $numClasses; ++$j) { if ($classes[$j] < $nspEnd) { ++$i; } } continue; } } } $classNameIndex = $tokens->getNextMeaningfulToken($index); $className = $tokens[$classNameIndex]->getContent(); $classStart = $tokens->getNextTokenOfKind($classNameIndex, ['{']); $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); $this->fixConstructor($tokens, $className, $classStart, $classEnd); $this->fixParent($tokens, $classStart, $classEnd); } } private function fixConstructor(Tokens $tokens, $className, $classStart, $classEnd) { $php4 = $this->findFunction($tokens, $className, $classStart, $classEnd); if (null === $php4) { return; } if (!empty($php4['modifiers'][T_ABSTRACT]) || !empty($php4['modifiers'][T_STATIC])) { return; } $php5 = $this->findFunction($tokens, '__construct', $classStart, $classEnd); if (null === $php5) { $tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']); $this->fixInfiniteRecursion($tokens, $php4['bodyIndex'], $php4['endIndex']); return; } list($seq, $case) = $this->getWrapperMethodSequence($tokens, '__construct', $php4['startIndex'], $php4['bodyIndex']); if (null !== $tokens->findSequence($seq, $php4['bodyIndex'] - 1, $php4['endIndex'], $case)) { for ($i = $php4['startIndex']; $i <= $php4['endIndex']; ++$i) { $tokens->clearAt($i); } return; } list($seq, $case) = $this->getWrapperMethodSequence($tokens, $className, $php4['startIndex'], $php4['bodyIndex']); if (null !== $tokens->findSequence($seq, $php5['bodyIndex'] - 1, $php5['endIndex'], $case)) { for ($i = $php5['startIndex']; $i <= $php5['endIndex']; ++$i) { $tokens->clearAt($i); } $tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']); } } private function fixParent(Tokens $tokens, $classStart, $classEnd) { foreach ($tokens->findGivenKind(T_EXTENDS) as $index => $token) { $parentIndex = $tokens->getNextMeaningfulToken($index); $parentClass = $tokens[$parentIndex]->getContent(); $parentSeq = $tokens->findSequence([ [T_STRING], [T_DOUBLE_COLON], [T_STRING, $parentClass], '(', ], $classStart, $classEnd, [2 => false]); if (null !== $parentSeq) { $parentSeq = array_keys($parentSeq); if ($tokens[$parentSeq[0]]->equalsAny([[T_STRING, 'parent'], [T_STRING, $parentClass]], false)) { $tokens[$parentSeq[0]] = new Token([T_STRING, 'parent']); $tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']); } } $parentSeq = $tokens->findSequence([ [T_VARIABLE, '$this'], [T_OBJECT_OPERATOR], [T_STRING, $parentClass], '(', ], $classStart, $classEnd, [2 => false]); if (null !== $parentSeq) { $parentSeq = array_keys($parentSeq); $tokens[$parentSeq[0]] = new Token([ T_STRING, 'parent', ]); $tokens[$parentSeq[1]] = new Token([ T_DOUBLE_COLON, '::', ]); $tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']); } } } private function fixInfiniteRecursion(Tokens $tokens, $start, $end) { $seq = [ [T_VARIABLE, '$this'], [T_OBJECT_OPERATOR], [T_STRING, '__construct'], ]; while (true) { $callSeq = $tokens->findSequence($seq, $start, $end, [2 => false]); if (null === $callSeq) { return; } $callSeq = array_keys($callSeq); $tokens[$callSeq[0]] = new Token([T_STRING, 'parent']); $tokens[$callSeq[1]] = new Token([T_DOUBLE_COLON, '::']); } } private function getWrapperMethodSequence(Tokens $tokens, $method, $startIndex, $bodyIndex) { $seq = [ '{', [T_VARIABLE, '$this'], [T_OBJECT_OPERATOR], [T_STRING, $method], '(', ]; $case = [3 => false]; $index = $startIndex; while (true) { $index = $tokens->getNextTokenOfKind($index, [[T_VARIABLE]]); if (null === $index || $index >= $bodyIndex) { break; } if (\count($seq) > 5) { $seq[] = ','; } $seq[] = [T_VARIABLE, $tokens[$index]->getContent()]; } $seq[] = ')'; $seq[] = ';'; $seq[] = '}'; return [$seq, $case]; } private function findFunction(Tokens $tokens, $name, $startIndex, $endIndex) { $function = $tokens->findSequence([ [T_FUNCTION], [T_STRING, $name], '(', ], $startIndex, $endIndex, false); if (null === $function) { return null; } $function = array_keys($function); $possibleModifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_ABSTRACT, T_FINAL]; $modifiers = []; $prevBlock = $tokens->getPrevMeaningfulToken($function[0]); while (null !== $prevBlock && $tokens[$prevBlock]->isGivenKind($possibleModifiers)) { $modifiers[$tokens[$prevBlock]->getId()] = $prevBlock; $prevBlock = $tokens->getPrevMeaningfulToken($prevBlock); } if (isset($modifiers[T_ABSTRACT])) { $bodyStart = null; $funcEnd = $tokens->getNextTokenOfKind($function[2], [';']); } else { $bodyStart = $tokens->getNextTokenOfKind($function[2], ['{']); $funcEnd = null !== $bodyStart ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $bodyStart) : null; } return [ 'nameIndex' => $function[1], 'startIndex' => $prevBlock + 1, 'endIndex' => $funcEnd, 'bodyIndex' => $bodyStart, 'modifiers' => $modifiers, ]; } } isAnyTokenKindsFound([T_CLASS, T_TRAIT]) && $tokens->isAnyTokenKindsFound([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { if (!$tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR])) { continue; } while (true) { $varTokenIndex = $index = $tokens->getNextMeaningfulToken($index); if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { break; } $index = $tokens->getNextMeaningfulToken($index); if ($tokens[$index]->equals('=')) { $index = $tokens->getNextMeaningfulToken($index); if ($tokens[$index]->isGivenKind(T_NS_SEPARATOR)) { $index = $tokens->getNextMeaningfulToken($index); } if ($tokens[$index]->equals([T_STRING, 'null'], false)) { for ($i = $varTokenIndex + 1; $i <= $index; ++$i) { if ( !($tokens[$i]->isWhitespace() && false !== strpos($tokens[$i]->getContent(), "\n")) && !$tokens[$i]->isComment() ) { $tokens->clearAt($i); } } } ++$index; } if (!$tokens[$index]->equals(',')) { break; } } } } } true] ), new CodeSample( ' true] ), new CodeSample( ' true] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->getSize() - 4; $index > 0; --$index) { if ($tokens[$index]->isClassy()) { $this->fixClassyDefinition($tokens, $index); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new AliasedFixerOptionBuilder( new FixerOptionBuilder('multi_line_extends_each_single_line', 'Whether definitions should be multiline.'), 'multiLineExtendsEachSingleLine' )) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new AliasedFixerOptionBuilder( new FixerOptionBuilder('single_item_single_line', 'Whether definitions should be single line when including a single item.'), 'singleItemSingleLine' )) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new AliasedFixerOptionBuilder( new FixerOptionBuilder('single_line', 'Whether definitions should be single line.'), 'singleLine' )) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } private function fixClassyDefinition(Tokens $tokens, $classyIndex) { $classDefInfo = $this->getClassyDefinitionInfo($tokens, $classyIndex); if (false !== $classDefInfo['implements']) { $classDefInfo['implements'] = $this->fixClassyDefinitionImplements( $tokens, $classDefInfo['open'], $classDefInfo['implements'] ); } if (false !== $classDefInfo['extends']) { $classDefInfo['extends'] = $this->fixClassyDefinitionExtends( $tokens, false === $classDefInfo['implements'] ? $classDefInfo['open'] : $classDefInfo['implements']['start'], $classDefInfo['extends'] ); } $classDefInfo['open'] = $this->fixClassyDefinitionOpenSpacing($tokens, $classDefInfo); if ($classDefInfo['implements']) { $end = $classDefInfo['implements']['start']; } elseif ($classDefInfo['extends']) { $end = $classDefInfo['extends']['start']; } else { $end = $tokens->getPrevNonWhitespace($classDefInfo['open']); } $this->makeClassyDefinitionSingleLine( $tokens, $classDefInfo['anonymousClass'] ? $tokens->getPrevMeaningfulToken($classyIndex) : $classDefInfo['start'], $end ); } private function fixClassyDefinitionExtends(Tokens $tokens, $classOpenIndex, array $classExtendsInfo) { $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); if ($this->configuration['single_line'] || false === $classExtendsInfo['multiLine']) { $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex); $classExtendsInfo['multiLine'] = false; } elseif ($this->configuration['single_item_single_line'] && 1 === $classExtendsInfo['numberOfExtends']) { $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex); $classExtendsInfo['multiLine'] = false; } elseif ($this->configuration['multi_line_extends_each_single_line'] && $classExtendsInfo['multiLine']) { $this->makeClassyInheritancePartMultiLine($tokens, $classExtendsInfo['start'], $endIndex); $classExtendsInfo['multiLine'] = true; } return $classExtendsInfo; } private function fixClassyDefinitionImplements(Tokens $tokens, $classOpenIndex, array $classImplementsInfo) { $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); if ($this->configuration['single_line'] || false === $classImplementsInfo['multiLine']) { $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex); $classImplementsInfo['multiLine'] = false; } elseif ($this->configuration['single_item_single_line'] && 1 === $classImplementsInfo['numberOfImplements']) { $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex); $classImplementsInfo['multiLine'] = false; } else { $this->makeClassyInheritancePartMultiLine($tokens, $classImplementsInfo['start'], $endIndex); $classImplementsInfo['multiLine'] = true; } return $classImplementsInfo; } private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo) { if ($classDefInfo['anonymousClass']) { if (false !== $classDefInfo['implements']) { $spacing = $classDefInfo['implements']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' '; } elseif (false !== $classDefInfo['extends']) { $spacing = $classDefInfo['extends']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' '; } else { $spacing = ' '; } } else { $spacing = $this->whitespacesConfig->getLineEnding(); } $openIndex = $tokens->getNextTokenOfKind($classDefInfo['classy'], ['{']); if (' ' !== $spacing && false !== strpos($tokens[$openIndex - 1]->getContent(), "\n")) { return $openIndex; } if ($tokens[$openIndex - 1]->isWhitespace()) { if (' ' !== $spacing || !$tokens[$tokens->getPrevNonWhitespace($openIndex - 1)]->isComment()) { $tokens[$openIndex - 1] = new Token([T_WHITESPACE, $spacing]); } return $openIndex; } $tokens->insertAt($openIndex, new Token([T_WHITESPACE, $spacing])); return $openIndex + 1; } private function getClassyDefinitionInfo(Tokens $tokens, $classyIndex) { $openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']); $prev = $tokens->getPrevMeaningfulToken($classyIndex); $startIndex = $tokens[$prev]->isGivenKind([T_FINAL, T_ABSTRACT]) ? $prev : $classyIndex; $extends = false; $implements = false; $anonymousClass = false; if (!$tokens[$classyIndex]->isGivenKind(T_TRAIT)) { $extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex); $extends = \count($extends) ? $this->getClassyInheritanceInfo($tokens, key($extends), 'numberOfExtends') : false; if (!$tokens[$classyIndex]->isGivenKind(T_INTERFACE)) { $implements = $tokens->findGivenKind(T_IMPLEMENTS, $classyIndex, $openIndex); $implements = \count($implements) ? $this->getClassyInheritanceInfo($tokens, key($implements), 'numberOfImplements') : false; $tokensAnalyzer = new TokensAnalyzer($tokens); $anonymousClass = $tokensAnalyzer->isAnonymousClass($classyIndex); } } return [ 'start' => $startIndex, 'classy' => $classyIndex, 'open' => $openIndex, 'extends' => $extends, 'implements' => $implements, 'anonymousClass' => $anonymousClass, ]; } private function getClassyInheritanceInfo(Tokens $tokens, $startIndex, $label) { $implementsInfo = ['start' => $startIndex, $label => 1, 'multiLine' => false]; ++$startIndex; $endIndex = $tokens->getNextTokenOfKind($startIndex, ['{', [T_IMPLEMENTS], [T_EXTENDS]]); $endIndex = $tokens[$endIndex]->equals('{') ? $tokens->getPrevNonWhitespace($endIndex) : $endIndex; for ($i = $startIndex; $i < $endIndex; ++$i) { if ($tokens[$i]->equals(',')) { ++$implementsInfo[$label]; continue; } if (!$implementsInfo['multiLine'] && false !== strpos($tokens[$i]->getContent(), "\n")) { $implementsInfo['multiLine'] = true; } } return $implementsInfo; } private function makeClassyDefinitionSingleLine(Tokens $tokens, $startIndex, $endIndex) { for ($i = $endIndex; $i >= $startIndex; --$i) { if ($tokens[$i]->isWhitespace()) { $prevNonWhite = $tokens->getPrevNonWhitespace($i); $nextNonWhite = $tokens->getNextNonWhitespace($i); if ($tokens[$prevNonWhite]->isComment() || $tokens[$nextNonWhite]->isComment()) { $content = $tokens[$prevNonWhite]->getContent(); if (!('#' === $content || '//' === substr($content, 0, 2))) { $content = $tokens[$nextNonWhite]->getContent(); if (!('#' === $content || '//' === substr($content, 0, 2))) { $tokens[$i] = new Token([T_WHITESPACE, ' ']); } } continue; } if ($tokens[$i + 1]->equalsAny([',', '(', ')']) || $tokens[$i - 1]->equals('(')) { $tokens->clearAt($i); continue; } $tokens[$i] = new Token([T_WHITESPACE, ' ']); continue; } if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) { $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); continue; } if (!$tokens[$i]->isComment()) { continue; } if (!$tokens[$i + 1]->isWhitespace() && !$tokens[$i + 1]->isComment() && false === strpos($tokens[$i]->getContent(), "\n")) { $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); } if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i - 1]->isComment()) { $tokens->insertAt($i, new Token([T_WHITESPACE, ' '])); } } } private function makeClassyInheritancePartMultiLine(Tokens $tokens, $startIndex, $endIndex) { for ($i = $endIndex; $i > $startIndex; --$i) { $previousInterfaceImplementingIndex = $tokens->getPrevTokenOfKind($i, [',', [T_IMPLEMENTS], [T_EXTENDS]]); $breakAtIndex = $tokens->getNextMeaningfulToken($previousInterfaceImplementingIndex); $this->makeClassyDefinitionSingleLine( $tokens, $breakAtIndex, $i ); $isOnOwnLine = false; for ($j = $breakAtIndex; $j > $previousInterfaceImplementingIndex; --$j) { if (false !== strpos($tokens[$j]->getContent(), "\n")) { $isOnOwnLine = true; break; } } if (!$isOnOwnLine) { if ($tokens[$breakAtIndex - 1]->isWhitespace()) { $tokens[$breakAtIndex - 1] = new Token([ T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent(), ]); } else { $tokens->insertAt($breakAtIndex, new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent()])); } } $i = $previousInterfaceImplementingIndex + 1; } } } ['const']] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('elements', [ (new FixerOptionBuilder('elements', 'The structural elements to fix (PHP >= 7.1 required for `const`).')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])]) ->setNormalizer(static function (Options $options, $value) { if (\PHP_VERSION_ID < 70100 && \in_array('const', $value, true)) { throw new InvalidOptionsForEnvException('"const" option can only be enabled with PHP 7.1+.'); } return $value; }) ->setDefault(['property', 'method']) ->getOption(), ], $this->getName()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $elements = $tokensAnalyzer->getClassyElements(); foreach (array_reverse($elements, true) as $index => $element) { if (!\in_array($element['type'], $this->configuration['elements'], true)) { continue; } $abstractFinalIndex = null; $visibilityIndex = null; $staticIndex = null; $prevIndex = $tokens->getPrevMeaningfulToken($index); while ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR])) { if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) { $abstractFinalIndex = $prevIndex; } elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) { $staticIndex = $prevIndex; } else { $visibilityIndex = $prevIndex; } $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } if ($tokens[$prevIndex]->equals(',')) { continue; } if (null !== $staticIndex) { if ($this->isKeywordPlacedProperly($tokens, $staticIndex, $index)) { $index = $staticIndex; } else { $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $staticIndex, $index); } } if (null === $visibilityIndex) { $tokens->insertAt($index, [new Token([T_PUBLIC, 'public']), new Token([T_WHITESPACE, ' '])]); } else { if ($tokens[$visibilityIndex]->isGivenKind(T_VAR)) { $tokens[$visibilityIndex] = new Token([T_PUBLIC, 'public']); } if ($this->isKeywordPlacedProperly($tokens, $visibilityIndex, $index)) { $index = $visibilityIndex; } else { $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilityIndex, $index); } } if (null === $abstractFinalIndex) { continue; } if ($this->isKeywordPlacedProperly($tokens, $abstractFinalIndex, $index)) { continue; } $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $abstractFinalIndex, $index); } } private function isKeywordPlacedProperly(Tokens $tokens, $keywordIndex, $comparedIndex) { return $keywordIndex + 2 === $comparedIndex && ' ' === $tokens[$keywordIndex + 1]->getContent(); } private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, $fromIndex, $toIndex) { $tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([T_WHITESPACE, ' '])]); $tokens->clearAt($fromIndex); if ($tokens[$fromIndex + 1]->isWhitespace()) { $tokens->clearAt($fromIndex + 1); } } } isAllTokenKindsFound([T_CLASS, T_FINAL, T_PROTECTED]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $end = \count($tokens) - 3; for ($index = 0; $index < $end; ++$index) { if (!$tokens[$index]->isGivenKind(T_CLASS)) { continue; } $classOpen = $tokens->getNextTokenOfKind($index, ['{']); $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen); if (!$this->skipClass($tokens, $index, $classOpen, $classClose)) { $this->fixClass($tokens, $classOpen, $classClose); } $index = $classClose; } } private function fixClass(Tokens $tokens, $classOpenIndex, $classCloseIndex) { for ($index = $classOpenIndex + 1; $index < $classCloseIndex; ++$index) { if ($tokens[$index]->equals('{')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); continue; } if (!$tokens[$index]->isGivenKind(T_PROTECTED)) { continue; } $tokens[$index] = new Token([T_PRIVATE, 'private']); } } private function skipClass(Tokens $tokens, $classIndex, $classOpenIndex, $classCloseIndex) { $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)]; if (!$prevToken->isGivenKind(T_FINAL)) { return true; } for ($index = $classIndex; $index < $classOpenIndex; ++$index) { if ($tokens[$index]->isGivenKind(T_EXTENDS)) { return true; } } $useIndex = $tokens->getNextTokenOfKind($classIndex, [[CT::T_USE_TRAIT]]); return $useIndex && $useIndex < $classCloseIndex; } } isAnyTokenKindsFound(Token::getClassyTokenKinds()); } public function getDefinition() { return new FixerDefinition( 'There MUST NOT be more than one property or constant declared per statement.', [ new CodeSample( ' ['property']] ), ] ); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $analyzer = new TokensAnalyzer($tokens); $elements = array_reverse($analyzer->getClassyElements(), true); foreach ($elements as $index => $element) { if (!\in_array($element['type'], $this->configuration['elements'], true)) { continue; } $this->fixElement($tokens, $index); } } protected function createConfigurationDefinition() { $values = ['const', 'property']; return new FixerConfigurationResolverRootless('elements', [ (new FixerOptionBuilder('elements', 'List of strings which element should be modified.')) ->setDefault($values) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset($values)]) ->getOption(), ], $this->getName()); } private function fixElement(Tokens $tokens, $index) { $tokensAnalyzer = new TokensAnalyzer($tokens); $repeatIndex = $index; while (true) { $repeatIndex = $tokens->getNextMeaningfulToken($repeatIndex); $repeatToken = $tokens[$repeatIndex]; if ($tokensAnalyzer->isArray($repeatIndex)) { if ($repeatToken->isGivenKind(T_ARRAY)) { $repeatIndex = $tokens->getNextTokenOfKind($repeatIndex, ['(']); $repeatIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $repeatIndex); } else { $repeatIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $repeatIndex); } continue; } if ($repeatToken->equals(';')) { return; } if ($repeatToken->equals(',')) { break; } } $start = $tokens->getPrevTokenOfKind($index, [';', '{', '}']); $this->expandElement( $tokens, $tokens->getNextMeaningfulToken($start), $tokens->getNextTokenOfKind($index, [';']) ); } private function expandElement(Tokens $tokens, $startIndex, $endIndex) { $divisionContent = null; if ($tokens[$startIndex - 1]->isWhitespace()) { $divisionContent = $tokens[$startIndex - 1]->getContent(); if (Preg::match('#(\n|\r\n)#', $divisionContent, $matches)) { $divisionContent = $matches[0].trim($divisionContent, "\r\n"); } } for ($i = $endIndex - 1; $i > $startIndex; --$i) { $token = $tokens[$i]; if ($token->equals(')')) { $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) { $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $i); continue; } if (!$tokens[$i]->equals(',')) { continue; } $tokens[$i] = new Token(';'); if ($tokens[$i + 1]->isWhitespace()) { $tokens->clearAt($i + 1); } if (null !== $divisionContent && '' !== $divisionContent) { $tokens->insertAt($i + 1, new Token([T_WHITESPACE, $divisionContent])); } $sequence = $this->getModifiersSequences($tokens, $startIndex, $endIndex); $tokens->insertAt($i + 2, $sequence); } } private function getModifiersSequences(Tokens $tokens, $startIndex, $endIndex) { $sequence = []; for ($i = $startIndex; $i < $endIndex - 1; ++$i) { if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { continue; } if (!$tokens[$i]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_CONST, T_VAR])) { break; } $sequence[] = clone $tokens[$i]; $sequence[] = new Token([T_WHITESPACE, ' ']); } return $sequence; } } isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) { if (!$tokens[$index]->isGivenKind([T_CLASS, T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($index)) { continue; } $nameIndex = $tokens->getNextTokenOfKind($index, [[T_STRING]]); $startIndex = $tokens->getNextTokenOfKind($nameIndex, ['{']); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); $name = $tokens[$nameIndex]->getContent(); $this->replaceNameOccurrences($tokens, $namespace->getFullName(), $name, $startIndex, $endIndex); $index = $endIndex; } } } private function replaceNameOccurrences(Tokens $tokens, $namespace, $name, $startIndex, $endIndex) { $tokensAnalyzer = new TokensAnalyzer($tokens); $insideMethodSignatureUntil = null; for ($i = $startIndex; $i < $endIndex; ++$i) { if ($i === $insideMethodSignatureUntil) { $insideMethodSignatureUntil = null; } $token = $tokens[$i]; if ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($i)) { $i = $tokens->getNextTokenOfKind($i, ['{']); $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); continue; } if ($token->isGivenKind(T_FUNCTION)) { $i = $tokens->getNextTokenOfKind($i, ['(']); $insideMethodSignatureUntil = $tokens->getNextTokenOfKind($i, ['{', ';']); continue; } if (!$token->equals([T_STRING, $name], false)) { continue; } $nextToken = $tokens[$tokens->getNextMeaningfulToken($i)]; if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { continue; } $classStartIndex = $i; $prevToken = $tokens[$tokens->getPrevMeaningfulToken($i)]; if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { $classStartIndex = $this->getClassStart($tokens, $i, $namespace); if (null === $classStartIndex) { continue; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classStartIndex)]; } if ($prevToken->isGivenKind(T_OBJECT_OPERATOR)) { continue; } if ( $prevToken->isGivenKind([T_INSTANCEOF, T_NEW]) || $nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM) || ( null !== $insideMethodSignatureUntil && $i < $insideMethodSignatureUntil && $prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [CT::T_NULLABLE_TYPE]]) ) ) { for ($j = $classStartIndex; $j < $i; ++$j) { $tokens->clearTokenAndMergeSurroundingWhitespace($j); } $tokens[$i] = new Token([T_STRING, 'self']); } } } private function getClassStart(Tokens $tokens, $index, $namespace) { $namespace = ('' !== $namespace ? '\\'.$namespace : '').'\\'; foreach (array_reverse(Preg::split('/(\\\\)/', $namespace, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) as $piece) { $index = $tokens->getPrevMeaningfulToken($index); if ('\\' === $piece) { if (!$tokens[$index]->isGivenKind([T_NS_SEPARATOR])) { return null; } } else { if (!$tokens[$index]->equals([T_STRING, $piece], false)) { return null; } } } return $index; } } null, 'public' => null, 'protected' => null, 'private' => null, 'constant' => null, 'constant_public' => ['constant', 'public'], 'constant_protected' => ['constant', 'protected'], 'constant_private' => ['constant', 'private'], 'property' => null, 'property_static' => ['property'], 'property_public' => ['property', 'public'], 'property_protected' => ['property', 'protected'], 'property_private' => ['property', 'private'], 'property_public_static' => ['property_static', 'property_public'], 'property_protected_static' => ['property_static', 'property_protected'], 'property_private_static' => ['property_static', 'property_private'], 'method' => null, 'method_static' => ['method'], 'method_public' => ['method', 'public'], 'method_protected' => ['method', 'protected'], 'method_private' => ['method', 'private'], 'method_public_static' => ['method_static', 'method_public'], 'method_protected_static' => ['method_static', 'method_protected'], 'method_private_static' => ['method_static', 'method_private'], ]; private static $specialTypes = [ 'construct' => null, 'destruct' => null, 'magic' => null, 'phpunit' => null, ]; private $supportedSortAlgorithms = [ self::SORT_NONE, self::SORT_ALPHA, ]; private $typePosition; public function configure(array $configuration = null) { parent::configure($configuration); $this->typePosition = []; $pos = 0; foreach ($this->configuration['order'] as $type) { $this->typePosition[$type] = $pos++; } foreach (self::$typeHierarchy as $type => $parents) { if (isset($this->typePosition[$type])) { continue; } if (!$parents) { $this->typePosition[$type] = null; continue; } foreach ($parents as $parent) { if (isset($this->typePosition[$parent])) { $this->typePosition[$type] = $this->typePosition[$parent]; continue 2; } } $this->typePosition[$type] = null; } $lastPosition = \count($this->configuration['order']); foreach ($this->typePosition as &$pos) { if (null === $pos) { $pos = $lastPosition; } $pos *= 10; } } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } public function getDefinition() { return new FixerDefinition( 'Orders the elements of classes/interfaces/traits.', [ new CodeSample( ' ['method_private', 'method_public']] ), new CodeSample( ' ['method_public'], 'sortAlgorithm' => 'alpha'] ), ] ); } public function getPriority() { return 65; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($i = 1, $count = $tokens->count(); $i < $count; ++$i) { if (!$tokens[$i]->isClassy()) { continue; } $i = $tokens->getNextTokenOfKind($i, ['{']); $elements = $this->getElements($tokens, $i); if (!$elements) { continue; } $sorted = $this->sortElements($elements); $endIndex = $elements[\count($elements) - 1]['end']; if ($sorted !== $elements) { $this->sortTokens($tokens, $i, $endIndex, $sorted); } $i = $endIndex; } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('order', [ (new FixerOptionBuilder('order', 'List of strings defining order of elements.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(array_keys(array_merge(self::$typeHierarchy, self::$specialTypes)))]) ->setDefault([ 'use_trait', 'constant_public', 'constant_protected', 'constant_private', 'property_public', 'property_protected', 'property_private', 'construct', 'destruct', 'magic', 'phpunit', 'method_public', 'method_protected', 'method_private', ]) ->getOption(), (new FixerOptionBuilder('sortAlgorithm', 'How multiple occurrences of same type statements should be sorted')) ->setAllowedValues($this->supportedSortAlgorithms) ->setDefault(self::SORT_NONE) ->getOption(), ], $this->getName()); } private function getElements(Tokens $tokens, $startIndex) { static $elementTokenKinds = [CT::T_USE_TRAIT, T_CONST, T_VARIABLE, T_FUNCTION]; ++$startIndex; $elements = []; while (true) { $element = [ 'start' => $startIndex, 'visibility' => 'public', 'static' => false, ]; for ($i = $startIndex;; ++$i) { $token = $tokens[$i]; if ($token->equals('}')) { return $elements; } if ($token->isGivenKind(T_STATIC)) { $element['static'] = true; continue; } if ($token->isGivenKind([T_PROTECTED, T_PRIVATE])) { $element['visibility'] = strtolower($token->getContent()); continue; } if (!$token->isGivenKind($elementTokenKinds)) { continue; } $type = $this->detectElementType($tokens, $i); if (\is_array($type)) { $element['type'] = $type[0]; $element['name'] = $type[1]; } else { $element['type'] = $type; } if ('property' === $element['type']) { $element['name'] = $tokens[$i]->getContent(); } elseif (\in_array($element['type'], ['use_trait', 'constant', 'method', 'magic'], true)) { $element['name'] = $tokens[$tokens->getNextMeaningfulToken($i)]->getContent(); } $element['end'] = $this->findElementEnd($tokens, $i); break; } $elements[] = $element; $startIndex = $element['end'] + 1; } } private function detectElementType(Tokens $tokens, $index) { $token = $tokens[$index]; if ($token->isGivenKind(CT::T_USE_TRAIT)) { return 'use_trait'; } if ($token->isGivenKind(T_CONST)) { return 'constant'; } if ($token->isGivenKind(T_VARIABLE)) { return 'property'; } $nameToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if ($nameToken->equals([T_STRING, '__construct'], false)) { return 'construct'; } if ($nameToken->equals([T_STRING, '__destruct'], false)) { return 'destruct'; } if ( $nameToken->equalsAny([ [T_STRING, 'setUpBeforeClass'], [T_STRING, 'tearDownAfterClass'], [T_STRING, 'setUp'], [T_STRING, 'tearDown'], ], false) ) { return ['phpunit', strtolower($nameToken->getContent())]; } if ('__' === substr($nameToken->getContent(), 0, 2)) { return 'magic'; } return 'method'; } private function findElementEnd(Tokens $tokens, $index) { $index = $tokens->getNextTokenOfKind($index, ['{', ';']); if ($tokens[$index]->equals('{')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); } for (++$index; $tokens[$index]->isWhitespace(" \t") || $tokens[$index]->isComment(); ++$index); --$index; return $tokens[$index]->isWhitespace() ? $index - 1 : $index; } private function sortElements(array $elements) { static $phpunitPositions = [ 'setupbeforeclass' => 1, 'teardownafterclass' => 2, 'setup' => 3, 'teardown' => 4, ]; foreach ($elements as &$element) { $type = $element['type']; if (\array_key_exists($type, self::$specialTypes)) { if (isset($this->typePosition[$type])) { $element['position'] = $this->typePosition[$type]; if ('phpunit' === $type) { $element['position'] += $phpunitPositions[$element['name']]; } continue; } $type = 'method'; } if (\in_array($type, ['constant', 'property', 'method'], true)) { $type .= '_'.$element['visibility']; if ($element['static']) { $type .= '_static'; } } $element['position'] = $this->typePosition[$type]; } unset($element); usort($elements, function (array $a, array $b) { if ($a['position'] === $b['position']) { return $this->sortGroupElements($a, $b); } return $a['position'] > $b['position'] ? 1 : -1; }); return $elements; } private function sortGroupElements(array $a, array $b) { $selectedSortAlgorithm = $this->configuration['sortAlgorithm']; if (self::SORT_ALPHA === $selectedSortAlgorithm) { return strcasecmp($a['name'], $b['name']); } return $a['start'] > $b['start'] ? 1 : -1; } private function sortTokens(Tokens $tokens, $startIndex, $endIndex, array $elements) { $replaceTokens = []; foreach ($elements as $element) { for ($i = $element['start']; $i <= $element['end']; ++$i) { $replaceTokens[] = clone $tokens[$i]; } } $tokens->overrideRange($startIndex + 1, $endIndex, $replaceTokens); } } configuration['annotation-white-list'], $this->configuration['annotation-black-list'] ); if (\count($intersect)) { throw new InvalidFixerConfigurationException($this->getName(), sprintf('Annotation cannot be used in both the white- and black list, got duplicates: "%s".', implode('", "', array_keys($intersect)))); } } public function getDefinition() { return new FixerDefinition( 'Internal classes should be `final`.', [ new CodeSample(" ['@Custom'], ] ), ], null, 'Changing classes to `final` might cause code execution to break.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; 0 <= $index; --$index) { if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isClassCandidate($tokens, $index)) { continue; } $tokens->insertAt( $index, [ new Token([T_FINAL, 'final']), new Token([T_WHITESPACE, ' ']), ] ); } } protected function createConfigurationDefinition() { $annotationsAsserts = [static function (array $values) { foreach ($values as $value) { if (!\is_string($value) || '' === $value) { return false; } } return true; }]; $annotationsNormalizer = static function (Options $options, array $value) { $newValue = []; foreach ($value as $key) { if ('@' === $key[0]) { $key = substr($key, 1); } $newValue[strtolower($key)] = true; } return $newValue; }; return new FixerConfigurationResolver([ (new FixerOptionBuilder('annotation-white-list', 'Class level annotations tags that must be set in order to fix the class. (case insensitive)')) ->setAllowedTypes(['array']) ->setAllowedValues($annotationsAsserts) ->setDefault(['@internal']) ->setNormalizer($annotationsNormalizer) ->getOption(), (new FixerOptionBuilder('annotation-black-list', 'Class level annotations tags that must be omitted to fix the class, even if all of the white list ones are used as well. (case insensitive)')) ->setAllowedTypes(['array']) ->setAllowedValues($annotationsAsserts) ->setDefault(['@final', '@Entity', '@ORM']) ->setNormalizer($annotationsNormalizer) ->getOption(), ]); } private function isClassCandidate(Tokens $tokens, $index) { if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_ABSTRACT, T_FINAL])) { return false; } $docToken = $tokens[$tokens->getPrevNonWhitespace($index)]; if (!$docToken->isGivenKind(T_DOC_COMMENT)) { return false; } $doc = new DocBlock($docToken->getContent()); $tags = []; foreach ($doc->getAnnotations() as $annotation) { $tag = strtolower($annotation->getTag()->getName()); if (isset($this->configuration['annotation-black-list'][$tag])) { return false; } $tags[$tag] = true; } foreach ($this->configuration['annotation-white-list'] as $tag => $true) { if (!isset($tags[$tag])) { return false; } } return true; } } classElementTypes = []; foreach ($this->configuration['elements'] as $element) { $this->classElementTypes[$element] = true; } } public function getDefinition() { return new FixerDefinition( 'Class, trait and interface elements must be separated with one blank line.', [ new CodeSample( ' ['property']] ), new CodeSample( ' ['const']] ), ] ); } public function getPriority() { return 55; } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } protected function applyFix(SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $class = $classStart = $classEnd = false; foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) { if (!isset($this->classElementTypes[$element['type']])) { continue; } if ($element['classIndex'] !== $class) { $class = $element['classIndex']; $classStart = $tokens->getNextTokenOfKind($class, ['{']); $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); } if ('method' === $element['type'] && !$tokens[$class]->isGivenKind(T_INTERFACE)) { $attributes = $tokensAnalyzer->getMethodAttributes($index); $methodEnd = true === $attributes['abstract'] ? $tokens->getNextTokenOfKind($index, [';']) : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($index, ['{'])) ; $this->fixSpaceBelowClassMethod($tokens, $classEnd, $methodEnd); $this->fixSpaceAboveClassElement($tokens, $classStart, $index); continue; } $this->fixSpaceBelowClassElement($tokens, $classEnd, $tokens->getNextTokenOfKind($index, [';'])); $this->fixSpaceAboveClassElement($tokens, $classStart, $index); } } protected function createConfigurationDefinition() { $types = ['const', 'method', 'property']; return new FixerConfigurationResolver([ (new FixerOptionBuilder('elements', sprintf('List of classy elements; \'%s\'.', implode("', '", $types)))) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset($types)]) ->setDefault(['const', 'method', 'property']) ->getOption(), ]); } private function fixSpaceBelowClassElement(Tokens $tokens, $classEndIndex, $elementEndIndex) { for ($nextNotWhite = $elementEndIndex + 1;; ++$nextNotWhite) { if (($tokens[$nextNotWhite]->isComment() || $tokens[$nextNotWhite]->isWhitespace()) && false === strpos($tokens[$nextNotWhite]->getContent(), "\n")) { continue; } break; } if ($tokens[$nextNotWhite]->isWhitespace()) { $nextNotWhite = $tokens->getNextNonWhitespace($nextNotWhite); } $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $nextNotWhite === $classEndIndex ? 1 : 2); } private function fixSpaceBelowClassMethod(Tokens $tokens, $classEndIndex, $elementEndIndex) { $nextNotWhite = $tokens->getNextNonWhitespace($elementEndIndex); $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $nextNotWhite === $classEndIndex ? 1 : 2); } private function fixSpaceAboveClassElement(Tokens $tokens, $classStartIndex, $elementIndex) { static $methodAttr = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC]; $firstElementAttributeIndex = $elementIndex; for ($i = $elementIndex; $i > $classStartIndex; --$i) { $nonWhiteAbove = $tokens->getNonWhitespaceSibling($i, -1); if (null !== $nonWhiteAbove && $tokens[$nonWhiteAbove]->isGivenKind($methodAttr)) { $firstElementAttributeIndex = $nonWhiteAbove; } else { break; } } if ($tokens[$nonWhiteAbove]->isGivenKind(T_COMMENT)) { if (1 === $firstElementAttributeIndex - $nonWhiteAbove) { $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 1); return; } if (substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 1) { $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 2); return; } if ($tokens[$nonWhiteAbove - 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove - 1]->getContent(), "\n") > 0) { $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 1); $nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove); $nonWhiteAboveComment = $tokens->getNonWhitespaceSibling($nonWhiteAbove, -1); $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $classStartIndex ? 1 : 2); } else { $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 2); } return; } if (false === $tokens[$nonWhiteAbove]->isGivenKind(T_DOC_COMMENT)) { $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, $nonWhiteAbove === $classStartIndex ? 1 : 2); return; } $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 1); $nonWhiteAbovePHPDoc = $tokens->getNonWhitespaceSibling($nonWhiteAbove, -1); $this->correctLineBreaks($tokens, $nonWhiteAbovePHPDoc, $nonWhiteAbove, $nonWhiteAbovePHPDoc === $classStartIndex ? 1 : 2); } private function correctLineBreaks(Tokens $tokens, $startIndex, $endIndex, $reqLineCount = 2) { $lineEnding = $this->whitespacesConfig->getLineEnding(); ++$startIndex; $numbOfWhiteTokens = $endIndex - $startIndex; if (0 === $numbOfWhiteTokens) { $tokens->insertAt($startIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $reqLineCount)])); return; } $lineBreakCount = $this->getLineBreakCount($tokens, $startIndex, $endIndex); if ($reqLineCount === $lineBreakCount) { return; } if ($lineBreakCount < $reqLineCount) { $tokens[$startIndex] = new Token([ T_WHITESPACE, str_repeat($lineEnding, $reqLineCount - $lineBreakCount).$tokens[$startIndex]->getContent(), ]); return; } if (1 === $numbOfWhiteTokens) { $tokens[$startIndex] = new Token([ T_WHITESPACE, Preg::replace('/\r\n|\n/', '', $tokens[$startIndex]->getContent(), $lineBreakCount - $reqLineCount), ]); return; } $toReplaceCount = $lineBreakCount - $reqLineCount; for ($i = $startIndex; $i < $endIndex && $toReplaceCount > 0; ++$i) { $tokenLineCount = substr_count($tokens[$i]->getContent(), "\n"); if ($tokenLineCount > 0) { $tokens[$i] = new Token([ T_WHITESPACE, Preg::replace('/\r\n|\n/', '', $tokens[$i]->getContent(), min($toReplaceCount, $tokenLineCount)), ]); $toReplaceCount -= $tokenLineCount; } } } private function getLineBreakCount(Tokens $tokens, $whiteSpaceStartIndex, $whiteSpaceEndIndex) { $lineCount = 0; for ($i = $whiteSpaceStartIndex; $i < $whiteSpaceEndIndex; ++$i) { $lineCount += substr_count($tokens[$i]->getContent(), "\n"); } return $lineCount; } private function findCommentBlockStart(Tokens $tokens, $commentIndex) { $start = $commentIndex; for ($i = $commentIndex - 1; $i > 0; --$i) { if ($tokens[$i]->isComment()) { $start = $i; continue; } if (!$tokens[$i]->isWhitespace() || $this->getLineBreakCount($tokens, $i, $i + 1) > 1) { break; } } return $start; } } proxyFixers); } protected function createProxyFixers() { $fixer = new ClassAttributesSeparationFixer(); $fixer->configure(['elements' => ['method']]); return [$fixer]; } } 'namespaced'] ), new CodeSample( ' [ 'MY_CUSTOM_PI', ], ] ), new CodeSample( ' false, 'include' => [ 'MY_CUSTOM_PI', ], ] ), new CodeSample( ' [ 'M_PI', ], ] ), ], null, 'Risky when any of the constants are namespaced or overridden.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } public function configure(array $configuration = null) { parent::configure($configuration); $uniqueConfiguredExclude = array_unique($this->configuration['exclude']); $constantsToEscape = array_values($this->configuration['include']); if (true === $this->configuration['fix_built_in']) { $getDefinedConstants = get_defined_constants(true); unset($getDefinedConstants['user']); foreach ($getDefinedConstants as $constants) { $constantsToEscape = array_merge($constantsToEscape, array_keys($constants)); } } $constantsToEscape = array_diff( array_unique($constantsToEscape), $uniqueConfiguredExclude ); static $caseInsensitiveConstants = ['null', 'false', 'true']; $caseInsensitiveConstantsToEscape = []; foreach ($constantsToEscape as $constantIndex => $constant) { $loweredConstant = strtolower($constant); if (\in_array($loweredConstant, $caseInsensitiveConstants, true)) { $caseInsensitiveConstantsToEscape[] = $loweredConstant; unset($constantsToEscape[$constantIndex]); } } $caseInsensitiveConstantsToEscape = array_diff( array_unique($caseInsensitiveConstantsToEscape), array_map(function ($function) { return strtolower($function); }, $uniqueConfiguredExclude) ); $this->constantsToEscape = array_fill_keys($constantsToEscape, true); ksort($this->constantsToEscape); $this->caseInsensitiveConstantsToEscape = array_fill_keys($caseInsensitiveConstantsToEscape, true); ksort($this->caseInsensitiveConstantsToEscape); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if ('all' === $this->configuration['scope']) { $this->fixConstantInvocations($tokens, 0, \count($tokens) - 1); return; } $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); foreach (array_reverse($namespaces) as $namespace) { if ('' === $namespace->getFullName()) { continue; } $this->fixConstantInvocations($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex()); } } protected function createConfigurationDefinition() { $constantChecker = static function ($value) { foreach ($value as $constantName) { if (!\is_string($constantName) || '' === trim($constantName) || trim($constantName) !== $constantName) { throw new InvalidOptionsException(sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', \is_object($constantName) ? \get_class($constantName) : \gettype($constantName) )); } } return true; }; return new FixerConfigurationResolver([ (new FixerOptionBuilder('fix_built_in', 'Whether to fix constants returned by `get_defined_constants`. User constants are not accounted in this list and must be specified in the include one.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), (new FixerOptionBuilder('include', 'List of additional constants to fix.')) ->setAllowedTypes(['array']) ->setAllowedValues([$constantChecker]) ->setDefault([]) ->getOption(), (new FixerOptionBuilder('exclude', 'List of constants to ignore.')) ->setAllowedTypes(['array']) ->setAllowedValues([$constantChecker]) ->setDefault(['null', 'false', 'true']) ->getOption(), (new FixerOptionBuilder('scope', 'Only fix constant invocations that are made within a namespace or fix all.')) ->setAllowedValues(['all', 'namespaced']) ->setDefault('all') ->getOption(), ]); } private function fixConstantInvocations(Tokens $tokens, $start, $end) { $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); $useConstantDeclarations = []; foreach ($useDeclarations as $use) { if ($use->isConstant()) { $useConstantDeclarations[$use->getShortName()] = true; } } $tokenAnalyzer = new TokensAnalyzer($tokens); $indexes = []; for ($index = $start; $index < $end; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_STRING)) { continue; } $tokenContent = $token->getContent(); if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) { continue; } if (isset($useConstantDeclarations[$tokenContent])) { continue; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { continue; } if (!$tokenAnalyzer->isConstantInvocation($index)) { continue; } $indexes[] = $index; } $indexes = array_reverse($indexes); foreach ($indexes as $index) { $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); } } } isTokenKindFound(T_CLASS); } protected function createConfigurationDefinition() { $types = ['normal', 'final', 'abstract']; return new FixerConfigurationResolver([ (new FixerOptionBuilder('types', 'What types of classes to mark as internal')) ->setAllowedValues([(new AllowedValueSubset($types))]) ->setAllowedTypes(['array']) ->setDefault(['normal', 'final']) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens, true) as $indexes) { $this->markClassInternal($tokens, $indexes[0]); } } private function markClassInternal(Tokens $tokens, $startIndex) { $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); if (!$this->isAllowedByConfiguration($tokens, $classIndex)) { return; } $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex); if ($this->hasDocBlock($tokens, $classIndex)) { $this->updateDocBlockIfNeeded($tokens, $docBlockIndex); return; } $this->createDocBlock($tokens, $docBlockIndex); } private function isAllowedByConfiguration(Tokens $tokens, $i) { $typeIndex = $tokens->getPrevMeaningfulToken($i); if ($tokens[$typeIndex]->isGivenKind(T_FINAL)) { return \in_array('final', $this->configuration['types'], true); } if ($tokens[$typeIndex]->isGivenKind(T_ABSTRACT)) { return \in_array('abstract', $this->configuration['types'], true); } return \in_array('normal', $this->configuration['types'], true); } private function createDocBlock(Tokens $tokens, $docBlockIndex) { $lineEnd = $this->whitespacesConfig->getLineEnding(); $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); $toInsert = [ new Token([T_DOC_COMMENT, '/**'.$lineEnd."${originalIndent} * @internal".$lineEnd."${originalIndent} */"]), new Token([T_WHITESPACE, $lineEnd.$originalIndent]), ]; $index = $tokens->getNextMeaningfulToken($docBlockIndex); $tokens->insertAt($index, $toInsert); } private function updateDocBlockIfNeeded(Tokens $tokens, $docBlockIndex) { $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); if (!empty($doc->getAnnotationsOfType('internal'))) { return; } $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex); $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex); $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); } private function hasDocBlock(Tokens $tokens, $index) { $docBlockIndex = $this->getDocBlockIndex($tokens, $index); return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); } private function getDocBlockIndex(Tokens $tokens, $index) { do { $index = $tokens->getPrevNonWhitespace($index); } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); return $index; } private function detectIndent(Tokens $tokens, $index) { if (!$tokens[$index - 1]->isWhitespace()) { return ''; } $explodedContent = explode($this->whitespacesConfig->getLineEnding(), $tokens[$index - 1]->getContent()); return end($explodedContent); } private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, $docBlockIndex) { $lines = $docBlock->getLines(); $originalIndent = $this->detectIndent($tokens, $docBlockIndex); $lineEnd = $this->whitespacesConfig->getLineEnding(); array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @internal'.$lineEnd); return $lines; } private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, $docBlockIndex) { $lines = $doc->getLines(); if (1 === \count($lines) && empty($doc->getAnnotationsOfType('internal'))) { $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); return new DocBlock(implode('', $lines)); } return $doc; } private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) { $lineContent = $this->getSingleLineDocBlockEntry($lines); $lineEnd = $this->whitespacesConfig->getLineEnding(); $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); return [ new Line('/**'.$lineEnd), new Line($originalIndent.' * '.$lineContent.$lineEnd), new Line($originalIndent.' */'), ]; } private function getSingleLineDocBlockEntry($line) { $line = $line[0]; $line = str_replace('*/', '', $line); $line = trim($line); $line = str_split($line); $i = \count($line); do { --$i; } while ('*' !== $line[$i] && '*' !== $line[$i - 1] && '/' !== $line[$i - 2]); if (' ' === $line[$i]) { ++$i; } $line = \array_slice($line, $i); return implode('', $line); } } hello = "hello"; } public function tearDown() { $this->hello = null; } } ' ), ], null, 'This fixer may change functions named `setUp()` or `tearDown()` outside of PHPUnit tests, '. 'when a class is wrongly seen as a PHPUnit test.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { $this->fixSetUpAndTearDown($tokens, $indexes[0], $indexes[1]); } } private function fixSetUpAndTearDown(Tokens $tokens, $startIndex, $endIndex) { $counter = 0; $tokensAnalyzer = new TokensAnalyzer($tokens); for ($i = $endIndex - 1; $i > $startIndex; --$i) { if (2 === $counter) { break; } if (!$this->isSetupOrTearDownMethod($tokens, $i)) { continue; } ++$counter; $visibility = $tokensAnalyzer->getMethodAttributes($i)['visibility']; if (T_PUBLIC === $visibility) { $index = $tokens->getPrevTokenOfKind($i, [[T_PUBLIC]]); $tokens[$index] = new Token([T_PROTECTED, 'protected']); continue; } if (null === $visibility) { $tokens->insertAt($i, [new Token([T_PROTECTED, 'protected']), new Token([T_WHITESPACE, ' '])]); } } } private function isSetupOrTearDownMethod(Tokens $tokens, $index) { $tokensAnalyzer = new TokensAnalyzer($tokens); $isMethod = $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); if (!$isMethod) { return false; } $functionNameIndex = $tokens->getNextMeaningfulToken($index); $functionName = strtolower($tokens[$functionNameIndex]->getContent()); return 'setup' === $functionName || 'teardown' === $functionName; } } ['assertArrayNotHasKey', 'assertArrayHasKey'], 'empty' => ['assertNotEmpty', 'assertEmpty'], 'file_exists' => ['assertFileNotExists', 'assertFileExists'], 'is_array' => true, 'is_bool' => true, 'is_callable' => true, 'is_dir' => ['assertDirectoryNotExists', 'assertDirectoryExists'], 'is_double' => true, 'is_float' => true, 'is_infinite' => ['assertFinite', 'assertInfinite'], 'is_int' => true, 'is_integer' => true, 'is_long' => true, 'is_nan' => [false, 'assertNan'], 'is_null' => ['assertNotNull', 'assertNull'], 'is_numeric' => true, 'is_object' => true, 'is_readable' => ['assertNotIsReadable', 'assertIsReadable'], 'is_real' => true, 'is_resource' => true, 'is_scalar' => true, 'is_string' => true, 'is_writable' => ['assertNotIsWritable', 'assertIsWritable'], ]; private $functions = []; public function configure(array $configuration = null) { parent::configure($configuration); if (isset($this->configuration['functions'])) { $this->functions = $this->configuration['functions']; return; } $this->functions = [ 'array_key_exists', 'file_exists', 'is_null', ]; if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_3_5)) { $this->functions = array_merge($this->functions, [ 'empty', 'is_array', 'is_bool', 'is_boolean', 'is_callable', 'is_double', 'is_float', 'is_int', 'is_integer', 'is_long', 'is_numeric', 'is_object', 'is_real', 'is_resource', 'is_scalar', 'is_string', ]); } if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_0)) { $this->functions = array_merge($this->functions, [ 'is_infinite', 'is_nan', ]); } if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { $this->functions = array_merge($this->functions, [ 'is_dir', 'is_readable', 'is_writable', ]); } } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } public function getDefinition() { return new FixerDefinition( 'PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.', [ new CodeSample( 'assertTrue(is_float( $a), "my message"); $this->assertTrue(is_nan($a)); ' ), new CodeSample( 'assertTrue(is_dir($a)); $this->assertTrue(is_writable($a)); $this->assertTrue(is_readable($a)); ', ['target' => PhpUnitTargetVersion::VERSION_5_6] ), ], null, 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' ); } public function getPriority() { return -15; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($this->getPreviousAssertCall($tokens) as $assertCall) { if ('asserttrue' === $assertCall['loweredName'] || 'assertfalse' === $assertCall['loweredName']) { $this->fixAssertTrueFalse($tokens, $assertCall); continue; } if ( 'assertsame' === $assertCall['loweredName'] || 'assertnotsame' === $assertCall['loweredName'] || 'assertequals' === $assertCall['loweredName'] || 'assertnotequals' === $assertCall['loweredName'] ) { $this->fixAssertSameEquals($tokens, $assertCall); continue; } } } protected function createConfigurationDefinition() { $values = [ 'array_key_exists', 'empty', 'file_exists', 'is_array', 'is_bool', 'is_callable', 'is_double', 'is_float', 'is_infinite', 'is_int', 'is_integer', 'is_long', 'is_nan', 'is_null', 'is_numeric', 'is_object', 'is_real', 'is_resource', 'is_scalar', 'is_string', ]; sort($values); return new FixerConfigurationResolverRootless('functions', [ (new FixerOptionBuilder('functions', 'List of assertions to fix (overrides `target`).')) ->setAllowedTypes(['null', 'array']) ->setAllowedValues([ null, new AllowedValueSubset($values), ]) ->setDefault(null) ->setDeprecationMessage('Use option `target` instead.') ->getOption(), (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) ->setAllowedTypes(['string']) ->setAllowedValues([ PhpUnitTargetVersion::VERSION_3_0, PhpUnitTargetVersion::VERSION_3_5, PhpUnitTargetVersion::VERSION_5_0, PhpUnitTargetVersion::VERSION_5_6, PhpUnitTargetVersion::VERSION_NEWEST, ]) ->setDefault(PhpUnitTargetVersion::VERSION_5_0) ->getOption(), ], $this->getName()); } private function fixAssertTrueFalse(Tokens $tokens, array $assertCall) { $testDefaultNamespaceTokenIndex = false; $testIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']); if (!$tokens[$testIndex]->isGivenKind([T_EMPTY, T_STRING])) { if (!$tokens[$testIndex]->isGivenKind(T_NS_SEPARATOR)) { return; } $testDefaultNamespaceTokenIndex = $testIndex; $testIndex = $tokens->getNextMeaningfulToken($testIndex); } $testOpenIndex = $tokens->getNextMeaningfulToken($testIndex); if (!$tokens[$testOpenIndex]->equals('(')) { return; } $testCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $testOpenIndex); $assertCallCloseIndex = $tokens->getNextMeaningfulToken($testCloseIndex); if (!$tokens[$assertCallCloseIndex]->equalsAny([')', ','])) { return; } $isPositive = 'asserttrue' === $assertCall['loweredName']; $content = strtolower($tokens[$testIndex]->getContent()); if (!\in_array($content, $this->functions, true)) { return; } if (\is_array(self::$fixMap[$content])) { if (false !== self::$fixMap[$content][$isPositive]) { $tokens[$assertCall['index']] = new Token([T_STRING, self::$fixMap[$content][$isPositive]]); $this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex); } return; } $type = substr($content, 3); $tokens[$assertCall['index']] = new Token([T_STRING, $isPositive ? 'assertInternalType' : 'assertNotInternalType']); $tokens[$testIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$type."'"]); $tokens[$testOpenIndex] = new Token(','); $tokens->clearTokenAndMergeSurroundingWhitespace($testCloseIndex); $commaIndex = $tokens->getPrevMeaningfulToken($testCloseIndex); if ($tokens[$commaIndex]->equals(',')) { $tokens->removeTrailingWhitespace($commaIndex); $tokens->clearAt($commaIndex); } if (!$tokens[$testOpenIndex + 1]->isWhitespace()) { $tokens->insertAt($testOpenIndex + 1, new Token([T_WHITESPACE, ' '])); } if (false !== $testDefaultNamespaceTokenIndex) { $tokens->clearTokenAndMergeSurroundingWhitespace($testDefaultNamespaceTokenIndex); } } private function fixAssertSameEquals(Tokens $tokens, array $assertCall) { $expectedIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']); if (!$tokens[$expectedIndex]->isGivenKind(T_LNUMBER)) { return; } $commaIndex = $tokens->getNextMeaningfulToken($expectedIndex); if (!$tokens[$commaIndex]->equals(',')) { return; } $countCallIndex = $tokens->getNextMeaningfulToken($commaIndex); if ($tokens[$countCallIndex]->isGivenKind(T_NS_SEPARATOR)) { $defaultNamespaceTokenIndex = $countCallIndex; $countCallIndex = $tokens->getNextMeaningfulToken($countCallIndex); } else { $defaultNamespaceTokenIndex = false; } if (!$tokens[$countCallIndex]->isGivenKind(T_STRING)) { return; } $lowerContent = strtolower($tokens[$countCallIndex]->getContent()); if ('count' !== $lowerContent && 'sizeof' !== $lowerContent) { return; } $countCallOpenBraceIndex = $tokens->getNextMeaningfulToken($countCallIndex); if (!$tokens[$countCallOpenBraceIndex]->equals('(')) { return; } $countCallCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $countCallOpenBraceIndex); $afterCountCallCloseBraceIndex = $tokens->getNextMeaningfulToken($countCallCloseBraceIndex); if (!$tokens[$afterCountCallCloseBraceIndex]->equalsAny([')', ','])) { return; } $this->removeFunctionCall( $tokens, $defaultNamespaceTokenIndex, $countCallIndex, $countCallOpenBraceIndex, $countCallCloseBraceIndex ); $tokens[$assertCall['index']] = new Token([ T_STRING, false === strpos($assertCall['loweredName'], 'not', 6) ? 'assertCount' : 'assertNotCount', ]); } private function getPreviousAssertCall(Tokens $tokens) { for ($index = $tokens->count(); $index > 0; --$index) { $index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]); if (null === $index) { return; } $loweredContent = strtolower($tokens[$index]->getContent()); if ('assert' !== substr($loweredContent, 0, 6)) { continue; } $openBraceIndex = $tokens->getNextMeaningfulToken($index); if (!$tokens[$openBraceIndex]->equals('(')) { continue; } $operatorIndex = $tokens->getPrevMeaningfulToken($index); $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); if ( !($tokens[$operatorIndex]->equals([T_OBJECT_OPERATOR, '->']) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'])) && !($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STRING, 'self'])) && !($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STATIC, 'static'])) ) { continue; } yield [ 'index' => $index, 'loweredName' => $loweredContent, 'openBraceIndex' => $openBraceIndex, 'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex), ]; } } private function removeFunctionCall(Tokens $tokens, $callNSIndex, $callIndex, $openIndex, $closeIndex) { $tokens->clearTokenAndMergeSurroundingWhitespace($callIndex); if (false !== $callNSIndex) { $tokens->clearTokenAndMergeSurroundingWhitespace($callNSIndex); } $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); if ($tokens[$commaIndex]->equals(',')) { $tokens->removeTrailingWhitespace($commaIndex); $tokens->clearAt($commaIndex); } $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); } } methodMap = [ 'setExpectedException' => 'expectExceptionMessage', ]; if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp'; } } public function getDefinition() { return new FixerDefinition( 'Usages of `->setExpectedException*` methods MUST be replaced by `->expectException*` methods.', [ new CodeSample( 'setExpectedException("RuntimeException", "Msg", 123); foo(); } public function testBar() { $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); bar(); } } ' ), new CodeSample( 'setExpectedException("RuntimeException", null, 123); foo(); } public function testBar() { $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); bar(); } } ', ['target' => PhpUnitTargetVersion::VERSION_5_6] ), new CodeSample( 'setExpectedException("RuntimeException", "Msg", 123); foo(); } public function testBar() { $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); bar(); } } ', ['target' => PhpUnitTargetVersion::VERSION_5_2] ), ], null, 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_CLASS); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { $this->fixExpectation($tokens, $indexes[0], $indexes[1]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) ->setAllowedTypes(['string']) ->setAllowedValues([PhpUnitTargetVersion::VERSION_5_2, PhpUnitTargetVersion::VERSION_5_6, PhpUnitTargetVersion::VERSION_NEWEST]) ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) ->getOption(), ]); } private function fixExpectation(Tokens $tokens, $startIndex, $endIndex) { $argumentsAnalyzer = new ArgumentsAnalyzer(); $oldMethodSequence = [ new Token([T_VARIABLE, '$this']), new Token([T_OBJECT_OPERATOR, '->']), [T_STRING], ]; for ($index = $startIndex; $startIndex < $endIndex; ++$index) { $match = $tokens->findSequence($oldMethodSequence, $index); if (null === $match) { return; } list($thisIndex, , $index) = array_keys($match); if (!isset($this->methodMap[$tokens[$index]->getContent()])) { continue; } $openIndex = $tokens->getNextTokenOfKind($index, ['(']); $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); if ($tokens[$commaIndex]->equals(',')) { $tokens->removeTrailingWhitespace($commaIndex); $tokens->clearAt($commaIndex); } $arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex); $argumentsCnt = \count($arguments); $argumentsReplacements = ['expectException', $this->methodMap[$tokens[$index]->getContent()], 'expectExceptionCode']; $indent = $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $thisIndex); $isMultilineWhitespace = false; for ($cnt = $argumentsCnt - 1; $cnt >= 1; --$cnt) { $argStart = array_keys($arguments)[$cnt]; $argBefore = $tokens->getPrevMeaningfulToken($argStart); if ('expectExceptionMessage' === $argumentsReplacements[$cnt]) { $paramIndicatorIndex = $tokens->getNextMeaningfulToken($argBefore); $afterParamIndicatorIndex = $tokens->getNextMeaningfulToken($paramIndicatorIndex); if ( $tokens[$paramIndicatorIndex]->equals([T_STRING, 'null'], false) && $tokens[$afterParamIndicatorIndex]->equals(')') ) { if ($tokens[$argBefore + 1]->isWhitespace()) { $tokens->clearTokenAndMergeSurroundingWhitespace($argBefore + 1); } $tokens->clearTokenAndMergeSurroundingWhitespace($argBefore); $tokens->clearTokenAndMergeSurroundingWhitespace($paramIndicatorIndex); continue; } } $isMultilineWhitespace = $isMultilineWhitespace || ($tokens[$argStart]->isWhitespace() && !$tokens[$argStart]->isWhitespace(" \t")); $tokensOverrideArgStart = [ new Token([T_WHITESPACE, $indent]), new Token([T_VARIABLE, '$this']), new Token([T_OBJECT_OPERATOR, '->']), new Token([T_STRING, $argumentsReplacements[$cnt]]), new Token('('), ]; $tokensOverrideArgBefore = [ new Token(')'), new Token(';'), ]; if ($isMultilineWhitespace) { array_push($tokensOverrideArgStart, new Token([T_WHITESPACE, $indent.$this->whitespacesConfig->getIndent()])); array_unshift($tokensOverrideArgBefore, new Token([T_WHITESPACE, $indent])); } if ($tokens[$argStart]->isWhitespace()) { $tokens->overrideRange($argStart, $argStart, $tokensOverrideArgStart); } else { $tokens->insertAt($argStart, $tokensOverrideArgStart); } $tokens->overrideRange($argBefore, $argBefore, $tokensOverrideArgBefore); } $tokens[$index] = new Token([T_STRING, 'expectException']); } } private function detectIndent(Tokens $tokens, $index) { if (!$tokens[$index - 1]->isWhitespace()) { return ''; } $explodedContent = explode("\n", $tokens[$index - 1]->getContent()); return end($explodedContent); } } isTokenKindFound(T_STRING); } public function isRisky() { return true; } public function configure(array $configuration = null) { parent::configure($configuration); if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_6_0)) { $this->originalClassRegEx = '/^PHPUnit_\w+$/i'; } elseif (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_7)) { $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase|PHPUnit_Framework_Assert|PHPUnit_Framework_BaseTestListener|PHPUnit_Framework_TestListener$/i'; } else { $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase$/i'; } } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $importedOriginalClassesMap = []; $currIndex = 0; while (null !== $currIndex) { $currIndex = $tokens->getNextTokenOfKind($currIndex, [[T_STRING]]); if (null === $currIndex) { break; } $originalClass = $tokens[$currIndex]->getContent(); if (1 !== Preg::match($this->originalClassRegEx, $originalClass)) { ++$currIndex; continue; } $substituteTokens = $this->generateReplacement($originalClass); $tokens->clearAt($currIndex); $tokens->insertAt( $currIndex, isset($importedOriginalClassesMap[$originalClass]) ? $substituteTokens[$substituteTokens->getSize() - 1] : $substituteTokens ); $prevIndex = $tokens->getPrevMeaningfulToken($currIndex); if ($tokens[$prevIndex]->isGivenKind(T_USE)) { $importedOriginalClassesMap[$originalClass] = true; } elseif ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if ($tokens[$prevIndex]->isGivenKind(T_USE)) { $importedOriginalClassesMap[$originalClass] = true; } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) ->setAllowedTypes(['string']) ->setAllowedValues([PhpUnitTargetVersion::VERSION_4_8, PhpUnitTargetVersion::VERSION_5_7, PhpUnitTargetVersion::VERSION_6_0, PhpUnitTargetVersion::VERSION_NEWEST]) ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) ->getOption(), ]); } private function generateReplacement($originalClassName) { $parts = explode('_', $originalClassName); $tokensArray = []; while (!empty($parts)) { $tokensArray[] = new Token([T_STRING, array_shift($parts)]); if (!empty($parts)) { $tokensArray[] = new Token([T_NS_SEPARATOR, '\\']); } } return Tokens::fromArray($tokensArray); } } 'fixAssertPositive', 'assertEquals' => 'fixAssertPositive', 'assertNotEquals' => 'fixAssertNegative', 'assertNotSame' => 'fixAssertNegative', ]; public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } public function getDefinition() { return new FixerDefinition( 'PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`.', [ new CodeSample( 'assertEquals(false, $b); $this->assertSame(true, $a); $this->assertNotEquals(null, $c); $this->assertNotSame(null, $d); ' ), new CodeSample( 'assertEquals(false, $b); $this->assertSame(true, $a); $this->assertNotEquals(null, $c); $this->assertNotSame(null, $d); ', ['assertions' => ['assertSame', 'assertNotSame']] ), ], null, 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' ); } public function getPriority() { return -10; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if (empty($this->configuration['assertions'])) { return; } foreach ($this->configuration['assertions'] as $assertionMethod) { $assertionFixer = self::$assertionFixers[$assertionMethod]; for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $index = $this->{$assertionFixer}($tokens, $index, $assertionMethod); if (null === $index) { break; } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('assertions', [ (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionFixers))]) ->setDefault([ 'assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame', ]) ->getOption(), ], $this->getName()); } private function fixAssertNegative(Tokens $tokens, $index, $method) { static $map = [ 'false' => 'assertNotFalse', 'null' => 'assertNotNull', 'true' => 'assertNotTrue', ]; return $this->fixAssert($map, $tokens, $index, $method); } private function fixAssertPositive(Tokens $tokens, $index, $method) { static $map = [ 'false' => 'assertFalse', 'null' => 'assertNull', 'true' => 'assertTrue', ]; return $this->fixAssert($map, $tokens, $index, $method); } private function fixAssert(array $map, Tokens $tokens, $index, $method) { $sequence = $tokens->findSequence( [ [T_STRING, $method], '(', ], $index ); if (null === $sequence) { return null; } $sequenceIndexes = array_keys($sequence); $operatorIndex = $tokens->getPrevMeaningfulToken($sequenceIndexes[0]); $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); if ( !($tokens[$operatorIndex]->equals([T_OBJECT_OPERATOR, '->']) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'])) && !($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STRING, 'self'])) && !($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STATIC, 'static'])) ) { return null; } $sequenceIndexes[2] = $tokens->getNextMeaningfulToken($sequenceIndexes[1]); $firstParameterToken = $tokens[$sequenceIndexes[2]]; if (!$firstParameterToken->isNativeConstant()) { return $sequenceIndexes[2]; } $sequenceIndexes[3] = $tokens->getNextMeaningfulToken($sequenceIndexes[2]); if (!$tokens[$sequenceIndexes[3]]->equals(',')) { return $sequenceIndexes[3]; } $tokens[$sequenceIndexes[0]] = new Token([T_STRING, $map[$firstParameterToken->getContent()]]); $tokens->clearRange($sequenceIndexes[2], $tokens->getNextNonWhitespace($sequenceIndexes[3]) - 1); return $sequenceIndexes[3]; } } assertSame(a(), b()); } } ' ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_CLASS); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { $this->addRequiresCover($tokens, $indexes[0]); } } private function addRequiresCover(Tokens $tokens, $startIndex) { $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); $prevIndex = $tokens->getPrevMeaningfulToken($classIndex); if ($tokens[$prevIndex]->isGivenKind(T_ABSTRACT)) { return; } $index = $tokens[$prevIndex]->isGivenKind(T_FINAL) ? $prevIndex : $classIndex; $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE) ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent()) : ''; $prevIndex = $tokens->getPrevNonWhitespace($index); $doc = null; $docIndex = null; if ($tokens[$prevIndex]->isGivenKind(T_DOC_COMMENT)) { $docIndex = $prevIndex; $docContent = $tokens[$docIndex]->getContent(); if (false === strpos($docContent, "\n")) { return; } $doc = new DocBlock($docContent); if (!empty($doc->getAnnotationsOfType([ 'covers', 'coversDefaultClass', 'coversNothing', ]))) { return; } } else { $docIndex = $index; $tokens->insertAt($docIndex, [ new Token([T_DOC_COMMENT, sprintf('/**%s%s */', $this->whitespacesConfig->getLineEnding(), $indent)]), new Token([T_WHITESPACE, sprintf('%s%s', $this->whitespacesConfig->getLineEnding(), $indent)]), ]); if (!$tokens[$docIndex - 1]->isGivenKind(T_WHITESPACE)) { $extraNewLines = $this->whitespacesConfig->getLineEnding(); if (!$tokens[$docIndex - 1]->isGivenKind(T_OPEN_TAG)) { $extraNewLines .= $this->whitespacesConfig->getLineEnding(); } $tokens->insertAt($docIndex, [ new Token([T_WHITESPACE, $extraNewLines.$indent]), ]); ++$docIndex; } $doc = new DocBlock($tokens[$docIndex]->getContent()); } $lines = $doc->getLines(); array_splice( $lines, \count($lines) - 1, 0, [ new Line(sprintf( '%s * @coversNothing%s', $indent, $this->whitespacesConfig->getLineEnding() )), ] ); $tokens[$docIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); } } self::SNAKE_CASE] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { $this->applyCasing($tokens, $indexes[0], $indexes[1]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('case', 'Apply camel or snake case to test methods')) ->setAllowedValues([self::CAMEL_CASE, self::SNAKE_CASE]) ->setDefault(self::CAMEL_CASE) ->getOption(), ]); } private function applyCasing(Tokens $tokens, $startIndex, $endIndex) { for ($index = $endIndex - 1; $index > $startIndex; --$index) { if (!$this->isTestMethod($tokens, $index)) { continue; } $functionNameIndex = $tokens->getNextMeaningfulToken($index); $functionName = $tokens[$functionNameIndex]->getContent(); $newFunctionName = $this->updateMethodCasing($functionName); if ($newFunctionName !== $functionName) { $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); } $docBlockIndex = $this->getDocBlockIndex($tokens, $index); if ($this->hasDocBlock($tokens, $index)) { $this->updateDocBlock($tokens, $docBlockIndex); } } } private function updateMethodCasing($functionName) { if (self::CAMEL_CASE === $this->configuration['case']) { $newFunctionName = $functionName; $newFunctionName = ucwords($newFunctionName, '_'); $newFunctionName = str_replace('_', '', $newFunctionName); $newFunctionName = lcfirst($newFunctionName); } else { $newFunctionName = Utils::camelCaseToUnderscore($functionName); } return $newFunctionName; } private function isTestMethod(Tokens $tokens, $index) { if (!$this->isMethod($tokens, $index)) { return false; } $functionNameIndex = $tokens->getNextMeaningfulToken($index); $functionName = $tokens[$functionNameIndex]->getContent(); if ($this->startsWith('test', $functionName)) { return true; } if (!$this->hasDocBlock($tokens, $index)) { return false; } $docBlockIndex = $this->getDocBlockIndex($tokens, $index); $doc = $tokens[$docBlockIndex]->getContent(); if (false === strpos($doc, '@test')) { return false; } return true; } private function isMethod(Tokens $tokens, $index) { $tokensAnalyzer = new TokensAnalyzer($tokens); return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); } private function startsWith($needle, $haystack) { return substr($haystack, 0, \strlen($needle)) === $needle; } private function hasDocBlock(Tokens $tokens, $index) { $docBlockIndex = $this->getDocBlockIndex($tokens, $index); return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); } private function getDocBlockIndex(Tokens $tokens, $index) { do { $index = $tokens->getPrevNonWhitespace($index); } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); return $index; } private function updateDocBlock(Tokens $tokens, $docBlockIndex) { $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); $lines = $doc->getLines(); $docBlockNeesUpdate = false; for ($inc = 0; $inc < \count($lines); ++$inc) { $lineContent = $lines[$inc]->getContent(); if (false === strpos($lineContent, '@depends')) { continue; } $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', function (array $matches) { return sprintf( '%s%s%s', $matches[1], $this->updateMethodCasing($matches[2]), $matches[3] ); }, $lineContent); if ($newLineContent !== $lineContent) { $lines[$inc] = new Line($newLineContent); $docBlockNeesUpdate = true; } } if ($docBlockNeesUpdate) { $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); } } } isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { $startIndex = $indexes[0]; $prevDocCommentIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_DOC_COMMENT]]); if (null !== $prevDocCommentIndex) { $startIndex = $prevDocCommentIndex; } $this->fixPhpUnitClass($tokens, $startIndex, $indexes[1]); } } private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) { for ($index = $startIndex; $index < $endIndex; ++$index) { if ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace( '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(?!(?:self|static)::)(\w.*)$~m', '$1\\\\$2', $tokens[$index]->getContent() )]); } } } } whitespacesConfig->getLineEnding()), new CodeSample('whitespacesConfig->getLineEnding(), ['style' => 'annotation']), ], null, 'This fixer may change the name of your tests, and could cause incompatibility with'. ' abstract classes or interfaces.' ); } public function getPriority() { return 10; } public function isCandidate(Tokens $tokens) { return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { if ('annotation' === $this->configuration['style']) { $this->applyTestAnnotation($tokens, $indexes[0], $indexes[1]); } else { $this->applyTestPrefix($tokens, $indexes[0], $indexes[1]); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('style', 'Whether to use the @test annotation or not.')) ->setAllowedValues(['prefix', 'annotation']) ->setDefault('prefix') ->getOption(), (new FixerOptionBuilder('case', 'Whether to camel or snake case when adding the test prefix')) ->setAllowedValues(['camel', 'snake']) ->setDefault('camel') ->setDeprecationMessage('Use `php_unit_method_casing` fixer instead.') ->getOption(), ]); } private function applyTestAnnotation(Tokens $tokens, $startIndex, $endIndex) { for ($i = $endIndex - 1; $i > $startIndex; --$i) { if (!$this->isTestMethod($tokens, $i)) { continue; } $functionNameIndex = $tokens->getNextMeaningfulToken($i); $functionName = $tokens[$functionNameIndex]->getContent(); if ($this->hasTestPrefix($functionName)) { $newFunctionName = $this->removeTestPrefix($functionName); $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); } $docBlockIndex = $this->getDocBlockIndex($tokens, $i); if (!$this->hasDocBlock($tokens, $i)) { $this->createDocBlock($tokens, $docBlockIndex); continue; } $lines = $this->updateDocBlock($tokens, $docBlockIndex); $lines = $this->addTestAnnotation($lines, $tokens, $docBlockIndex); $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); } } private function applyTestPrefix(Tokens $tokens, $startIndex, $endIndex) { for ($i = $endIndex - 1; $i > $startIndex; --$i) { if (!$this->isTestMethod($tokens, $i) || !$this->hasDocBlock($tokens, $i)) { continue; } $docBlockIndex = $this->getDocBlockIndex($tokens, $i); $lines = $this->updateDocBlock($tokens, $docBlockIndex); $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); $functionNameIndex = $tokens->getNextMeaningfulToken($i); $functionName = $tokens[$functionNameIndex]->getContent(); if ($this->hasTestPrefix($functionName)) { continue; } $newFunctionName = $this->addTestPrefix($functionName); $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); } } private function isTestMethod(Tokens $tokens, $index) { if (!$this->isMethod($tokens, $index)) { return false; } $functionNameIndex = $tokens->getNextMeaningfulToken($index); $functionName = $tokens[$functionNameIndex]->getContent(); if ($this->startsWith('test', $functionName)) { return true; } if (!$this->hasDocBlock($tokens, $index)) { return false; } $docBlockIndex = $this->getDocBlockIndex($tokens, $index); $doc = $tokens[$docBlockIndex]->getContent(); if (false === strpos($doc, '@test')) { return false; } return true; } private function isMethod(Tokens $tokens, $index) { $tokensAnalyzer = new TokensAnalyzer($tokens); return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); } private function startsWith($needle, $haystack) { $len = \strlen($needle); return substr($haystack, 0, $len) === $needle; } private function hasDocBlock(Tokens $tokens, $index) { $docBlockIndex = $this->getDocBlockIndex($tokens, $index); return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); } private function getDocBlockIndex(Tokens $tokens, $index) { do { $index = $tokens->getPrevNonWhitespace($index); } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); return $index; } private function hasTestPrefix($functionName) { if (!$this->startsWith('test', $functionName)) { return false; } if ('test' === $functionName) { return true; } $nextCharacter = $functionName[4]; return $nextCharacter === strtoupper($nextCharacter); } private function removeTestPrefix($functionName) { $remainder = Preg::replace('/^test_?/', '', $functionName); if ('' === $remainder || is_numeric($remainder[0])) { return $functionName; } return lcfirst($remainder); } private function addTestPrefix($functionName) { if ('camel' !== $this->configuration['case']) { return 'test_'.$functionName; } return'test'.ucfirst($functionName); } private function detectIndent(Tokens $tokens, $index) { if (!$tokens[$index - 1]->isWhitespace()) { return ''; } $explodedContent = explode($this->whitespacesConfig->getLineEnding(), $tokens[$index - 1]->getContent()); return end($explodedContent); } private function createDocBlock(Tokens $tokens, $docBlockIndex) { $lineEnd = $this->whitespacesConfig->getLineEnding(); $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); $toInsert = [ new Token([T_DOC_COMMENT, '/**'.$lineEnd."${originalIndent} * @test".$lineEnd."${originalIndent} */"]), new Token([T_WHITESPACE, $lineEnd.$originalIndent]), ]; $index = $tokens->getNextMeaningfulToken($docBlockIndex); $tokens->insertAt($index, $toInsert); } private function updateDocBlock(Tokens $tokens, $docBlockIndex) { $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); $lines = $doc->getLines(); return $this->updateLines($lines, $tokens, $docBlockIndex); } private function updateLines($lines, Tokens $tokens, $docBlockIndex) { $needsAnnotation = 'annotation' === $this->configuration['style']; $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); for ($i = 0; $i < \count($lines); ++$i) { if ($needsAnnotation && ($lines[$i]->isTheStart() && $lines[$i]->isTheEnd())) { if (!$this->doesDocBlockContainTest($doc)) { $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); return $this->updateLines($lines, $tokens, $docBlockIndex); } } if (!$needsAnnotation && false !== strpos($lines[$i]->getContent(), ' @test') && false === strpos($lines[$i]->getContent(), '@testWith') && false === strpos($lines[$i]->getContent(), '@testdox') ) { $lines[$i] = new Line(str_replace(' @test', '', $lines[$i]->getContent())); } if (false === strpos($lines[$i]->getContent(), '@depends')) { continue; } $lines[$i] = $this->updateDependsAnnotation($lines[$i]); } return $lines; } private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) { $lineContent = $this->getSingleLineDocBlockEntry($lines); $lineEnd = $this->whitespacesConfig->getLineEnding(); $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); return [ new Line('/**'.$lineEnd), new Line($originalIndent.' * '.$lineContent.$lineEnd), new Line($originalIndent.' */'), ]; } private function getSingleLineDocBlockEntry($line) { $line = $line[0]; $line = str_replace('*/', '', $line); $line = trim($line); $line = str_split($line); $i = \count($line); do { --$i; } while ('*' !== $line[$i] && '*' !== $line[$i - 1] && '/' !== $line[$i - 2]); if (' ' === $line[$i]) { ++$i; } $line = \array_slice($line, $i); return implode('', $line); } private function updateDependsAnnotation(Line $line) { if ('annotation' === $this->configuration['style']) { return $this->removeTestPrefixFromDependsAnnotation($line); } return $this->addTestPrefixToDependsAnnotation($line); } private function removeTestPrefixFromDependsAnnotation(Line $line) { $line = str_split($line->getContent()); $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); if ($this->startsWith('test', $dependsFunctionName)) { $dependsFunctionName = $this->removeTestPrefix($dependsFunctionName); } array_splice($line, $dependsIndex); return new Line(implode('', $line).$dependsFunctionName); } private function addTestPrefixToDependsAnnotation(Line $line) { $line = str_split($line->getContent()); $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); if (!$this->startsWith('test', $dependsFunctionName)) { $dependsFunctionName = $this->addTestPrefix($dependsFunctionName); } array_splice($line, $dependsIndex); return new Line(implode('', $line).$dependsFunctionName); } private function findWhereDependsFunctionNameStarts(array $line) { $counter = \count($line); do { --$counter; } while (' ' !== $line[$counter]); return $counter + 1; } private function addTestAnnotation($lines, Tokens $tokens, $docBlockIndex) { $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); if (!$this->doesDocBlockContainTest($doc)) { $originalIndent = $this->detectIndent($tokens, $docBlockIndex); $lineEnd = $this->whitespacesConfig->getLineEnding(); array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @test'.$lineEnd); } return $lines; } private function doesDocBlockContainTest(DocBlock $doc) { return !empty($doc->getAnnotationsOfType('test')); } } isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index > 0; --$index) { if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT) || 0 === Preg::match('/@covers\s.+@covers\s/s', $tokens[$index]->getContent())) { continue; } $docBlock = new DocBlock($tokens[$index]->getContent()); $covers = $docBlock->getAnnotationsOfType('covers'); $coversMap = []; foreach ($covers as $annotation) { $rawContent = $annotation->getContent(); $comparableContent = Preg::replace('/\*\s*@covers\s+(.+)/', '\1', strtolower(trim($rawContent))); $coversMap[$comparableContent] = $rawContent; } $orderedCoversMap = $coversMap; ksort($orderedCoversMap, SORT_STRING); if ($orderedCoversMap === $coversMap) { continue; } $lines = $docBlock->getLines(); foreach (array_reverse($covers) as $annotation) { array_splice( $lines, $annotation->getStart(), $annotation->getEnd() - $annotation->getStart() + 1, array_pop($orderedCoversMap) ); } $tokens[$index] = new Token([T_DOC_COMMENT, implode('', $lines)]); } } } 'assertAttributeSame', 'assertAttributeNotEquals' => 'assertAttributeNotSame', 'assertEquals' => 'assertSame', 'assertNotEquals' => 'assertNotSame', ]; public function getDefinition() { return new FixerDefinition( 'PHPUnit methods like `assertSame` should be used instead of `assertEquals`.', [ new CodeSample( 'assertAttributeEquals(a(), b()); $this->assertAttributeNotEquals(a(), b()); $this->assertEquals(a(), b()); $this->assertNotEquals(a(), b()); } } ' ), new CodeSample( 'assertAttributeEquals(a(), b()); $this->assertAttributeNotEquals(a(), b()); $this->assertEquals(a(), b()); $this->assertNotEquals(a(), b()); } } ', ['assertions' => ['assertEquals']] ), ], null, 'Risky when any of the functions are overridden or when testing object equality.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach ($this->configuration['assertions'] as $methodBefore) { $methodAfter = self::$assertionMap[$methodBefore]; for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $methodIndex = $tokens->getNextTokenOfKind($index, [[T_STRING, $methodBefore]]); if (null === $methodIndex) { break; } $operatorIndex = $tokens->getPrevMeaningfulToken($methodIndex); $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); if ( !($tokens[$operatorIndex]->equals([T_OBJECT_OPERATOR, '->']) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'])) && !($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STRING, 'self'])) && !($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STATIC, 'static'])) ) { continue; } $openingParenthesisIndex = $tokens->getNextMeaningfulToken($methodIndex); $argumentsCount = $argumentsAnalyzer->countArguments( $tokens, $openingParenthesisIndex, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex) ); if (2 === $argumentsCount || 3 === $argumentsCount) { $tokens[$methodIndex] = new Token([T_STRING, $methodAfter]); } $index = $methodIndex; } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolverRootless('assertions', [ (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionMap))]) ->setDefault([ 'assertAttributeEquals', 'assertAttributeNotEquals', 'assertEquals', 'assertNotEquals', ]) ->getOption(), ], $this->getName()); } } getMock` and `->getMockWithoutInvokingTheOriginalConstructor` methods MUST be replaced by `->createMock` or `->createPartialMock` methods.', [ new CodeSample( 'getMockWithoutInvokingTheOriginalConstructor("Foo"); $mock1 = $this->getMock("Foo"); $mock1 = $this->getMock("Bar", ["aaa"]); $mock1 = $this->getMock("Baz", ["aaa"], ["argument"]); // version with more than 2 params is not supported } } ' ), new CodeSample( 'getMock("Foo"); $mock1 = $this->getMock("Bar", ["aaa"]); // version with multiple params is not supported } } ', ['target' => PhpUnitTargetVersion::VERSION_5_4] ), ], null, 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_CLASS); } public function isRisky() { return true; } public function configure(array $configuration = null) { parent::configure($configuration); $this->fixCreatePartialMock = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_5); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { for ($index = $indexes[0]; $index < $indexes[1]; ++$index) { if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { continue; } $index = $tokens->getNextMeaningfulToken($index); if ($tokens[$index]->equals([T_STRING, 'getMockWithoutInvokingTheOriginalConstructor'], false)) { $tokens[$index] = new Token([T_STRING, 'createMock']); } elseif ($tokens[$index]->equals([T_STRING, 'getMock'], false)) { $openingParenthesis = $tokens->getNextMeaningfulToken($index); $closingParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesis); $argumentsCount = $argumentsAnalyzer->countArguments($tokens, $openingParenthesis, $closingParenthesis); if (1 === $argumentsCount) { $tokens[$index] = new Token([T_STRING, 'createMock']); } elseif (2 === $argumentsCount && true === $this->fixCreatePartialMock) { $tokens[$index] = new Token([T_STRING, 'createPartialMock']); } } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) ->setAllowedTypes(['string']) ->setAllowedValues([PhpUnitTargetVersion::VERSION_5_4, PhpUnitTargetVersion::VERSION_5_5, PhpUnitTargetVersion::VERSION_NEWEST]) ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) ->getOption(), ]); } } true, self::CALL_TYPE_SELF => true, self::CALL_TYPE_STATIC => true, ]; private $staticMethods = [ 'anything' => true, 'arrayHasKey' => true, 'assertArrayHasKey' => true, 'assertArrayNotHasKey' => true, 'assertArraySubset' => true, 'assertAttributeContains' => true, 'assertAttributeContainsOnly' => true, 'assertAttributeCount' => true, 'assertAttributeEmpty' => true, 'assertAttributeEquals' => true, 'assertAttributeGreaterThan' => true, 'assertAttributeGreaterThanOrEqual' => true, 'assertAttributeInstanceOf' => true, 'assertAttributeInternalType' => true, 'assertAttributeLessThan' => true, 'assertAttributeLessThanOrEqual' => true, 'assertAttributeNotContains' => true, 'assertAttributeNotContainsOnly' => true, 'assertAttributeNotCount' => true, 'assertAttributeNotEmpty' => true, 'assertAttributeNotEquals' => true, 'assertAttributeNotInstanceOf' => true, 'assertAttributeNotInternalType' => true, 'assertAttributeNotSame' => true, 'assertAttributeSame' => true, 'assertClassHasAttribute' => true, 'assertClassHasStaticAttribute' => true, 'assertClassNotHasAttribute' => true, 'assertClassNotHasStaticAttribute' => true, 'assertContains' => true, 'assertContainsOnly' => true, 'assertContainsOnlyInstancesOf' => true, 'assertCount' => true, 'assertDirectoryExists' => true, 'assertDirectoryIsReadable' => true, 'assertDirectoryIsWritable' => true, 'assertDirectoryNotExists' => true, 'assertDirectoryNotIsReadable' => true, 'assertDirectoryNotIsWritable' => true, 'assertEmpty' => true, 'assertEqualXMLStructure' => true, 'assertEquals' => true, 'assertEqualsCanonicalizing' => true, 'assertEqualsIgnoringCase' => true, 'assertEqualsWithDelta' => true, 'assertFalse' => true, 'assertFileEquals' => true, 'assertFileExists' => true, 'assertFileIsReadable' => true, 'assertFileIsWritable' => true, 'assertFileNotEquals' => true, 'assertFileNotExists' => true, 'assertFileNotIsReadable' => true, 'assertFileNotIsWritable' => true, 'assertFinite' => true, 'assertGreaterThan' => true, 'assertGreaterThanOrEqual' => true, 'assertInfinite' => true, 'assertInstanceOf' => true, 'assertInternalType' => true, 'assertIsArray' => true, 'assertIsBool' => true, 'assertIsCallable' => true, 'assertIsFloat' => true, 'assertIsInt' => true, 'assertIsIterable' => true, 'assertIsNotArray' => true, 'assertIsNotBool' => true, 'assertIsNotCallable' => true, 'assertIsNotFloat' => true, 'assertIsNotInt' => true, 'assertIsNotIterable' => true, 'assertIsNotNumeric' => true, 'assertIsNotObject' => true, 'assertIsNotResource' => true, 'assertIsNotScalar' => true, 'assertIsNotString' => true, 'assertIsNumeric' => true, 'assertIsObject' => true, 'assertIsReadable' => true, 'assertIsResource' => true, 'assertIsScalar' => true, 'assertIsString' => true, 'assertIsWritable' => true, 'assertJson' => true, 'assertJsonFileEqualsJsonFile' => true, 'assertJsonFileNotEqualsJsonFile' => true, 'assertJsonStringEqualsJsonFile' => true, 'assertJsonStringEqualsJsonString' => true, 'assertJsonStringNotEqualsJsonFile' => true, 'assertJsonStringNotEqualsJsonString' => true, 'assertLessThan' => true, 'assertLessThanOrEqual' => true, 'assertNan' => true, 'assertNotContains' => true, 'assertNotContainsOnly' => true, 'assertNotCount' => true, 'assertNotEmpty' => true, 'assertNotEquals' => true, 'assertNotEqualsCanonicalizing' => true, 'assertNotEqualsIgnoringCase' => true, 'assertNotEqualsWithDelta' => true, 'assertNotFalse' => true, 'assertNotInstanceOf' => true, 'assertNotInternalType' => true, 'assertNotIsReadable' => true, 'assertNotIsWritable' => true, 'assertNotNull' => true, 'assertNotRegExp' => true, 'assertNotSame' => true, 'assertNotSameSize' => true, 'assertNotTrue' => true, 'assertNull' => true, 'assertObjectHasAttribute' => true, 'assertObjectNotHasAttribute' => true, 'assertRegExp' => true, 'assertSame' => true, 'assertSameSize' => true, 'assertStringContainsString' => true, 'assertStringContainsStringIgnoringCase' => true, 'assertStringEndsNotWith' => true, 'assertStringEndsWith' => true, 'assertStringEqualsFile' => true, 'assertStringMatchesFormat' => true, 'assertStringMatchesFormatFile' => true, 'assertStringNotContainsString' => true, 'assertStringNotContainsStringIgnoringCase' => true, 'assertStringNotEqualsFile' => true, 'assertStringNotMatchesFormat' => true, 'assertStringNotMatchesFormatFile' => true, 'assertStringStartsNotWith' => true, 'assertStringStartsWith' => true, 'assertThat' => true, 'assertTrue' => true, 'assertXmlFileEqualsXmlFile' => true, 'assertXmlFileNotEqualsXmlFile' => true, 'assertXmlStringEqualsXmlFile' => true, 'assertXmlStringEqualsXmlString' => true, 'assertXmlStringNotEqualsXmlFile' => true, 'assertXmlStringNotEqualsXmlString' => true, 'attribute' => true, 'attributeEqualTo' => true, 'callback' => true, 'classHasAttribute' => true, 'classHasStaticAttribute' => true, 'contains' => true, 'containsOnly' => true, 'containsOnlyInstancesOf' => true, 'countOf' => true, 'directoryExists' => true, 'equalTo' => true, 'fail' => true, 'fileExists' => true, 'getCount' => true, 'getObjectAttribute' => true, 'getStaticAttribute' => true, 'greaterThan' => true, 'greaterThanOrEqual' => true, 'identicalTo' => true, 'isEmpty' => true, 'isFalse' => true, 'isFinite' => true, 'isInfinite' => true, 'isInstanceOf' => true, 'isJson' => true, 'isNan' => true, 'isNull' => true, 'isReadable' => true, 'isTrue' => true, 'isType' => true, 'isWritable' => true, 'lessThan' => true, 'lessThanOrEqual' => true, 'logicalAnd' => true, 'logicalNot' => true, 'logicalOr' => true, 'logicalXor' => true, 'markTestIncomplete' => true, 'markTestSkipped' => true, 'matches' => true, 'matchesRegularExpression' => true, 'objectHasAttribute' => true, 'readAttribute' => true, 'resetCount' => true, 'stringContains' => true, 'stringEndsWith' => true, 'stringStartsWith' => true, 'any' => true, 'at' => true, 'atLeast' => true, 'atLeastOnce' => true, 'atMost' => true, 'exactly' => true, 'never' => true, 'onConsecutiveCalls' => true, 'once' => true, 'returnArgument' => true, 'returnCallback' => true, 'returnSelf' => true, 'returnValue' => true, 'returnValueMap' => true, 'setUpBeforeClass' => true, 'tearDownAfterClass' => true, 'throwException' => true, ]; private $conversionMap = [ self::CALL_TYPE_THIS => [[T_OBJECT_OPERATOR, '->'], [T_VARIABLE, '$this']], self::CALL_TYPE_SELF => [[T_DOUBLE_COLON, '::'], [T_STRING, 'self']], self::CALL_TYPE_STATIC => [[T_DOUBLE_COLON, '::'], [T_STATIC, 'static']], ]; public function getDefinition() { return new FixerDefinition( 'Calls to `PHPUnit\Framework\TestCase` static methods must all be of the same type, either `$this->`, `self::` or `static::`.', [ new CodeSample( 'assertSame(1, 2); self::assertSame(1, 2); static::assertSame(1, 2); } } ' ), ], null, 'Risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach (array_reverse(iterator_to_array($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens))) as $indexes) { $this->fixPhpUnitClass($tokens, $indexes[0], $indexes[1]); } } protected function createConfigurationDefinition() { $thisFixer = $this; return new FixerConfigurationResolver([ (new FixerOptionBuilder('call_type', 'The call type to use for referring to PHPUnit methods.')) ->setAllowedTypes(['string']) ->setAllowedValues(array_keys($this->allowedValues)) ->setDefault('static') ->getOption(), (new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.')) ->setAllowedTypes(['array']) ->setAllowedValues([static function ($option) use ($thisFixer) { foreach ($option as $method => $value) { if (!isset($thisFixer->staticMethods[$method])) { throw new InvalidOptionsException( sprintf( 'Unexpected "methods" key, expected any of "%s", got "%s".', implode('", "', array_keys($thisFixer->staticMethods)), \is_object($method) ? \get_class($method) : \gettype($method).'#'.$method ) ); } if (!isset($thisFixer->allowedValues[$value])) { throw new InvalidOptionsException( sprintf( 'Unexpected value for method "%s", expected any of "%s", got "%s".', $method, implode('", "', array_keys($thisFixer->allowedValues)), \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) ) ); } } return true; }]) ->setDefault([]) ->getOption(), ]); } private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) { $analyzer = new TokensAnalyzer($tokens); for ($index = $endIndex - 1; $index > $startIndex; --$index) { if (!$tokens[$index]->isGivenKind(T_STRING) || !isset($this->staticMethods[$tokens[$index]->getContent()])) { continue; } $methodName = $tokens[$index]->getContent(); $callType = $this->configuration['call_type']; if (isset($this->configuration['methods'][$methodName])) { $callType = $this->configuration['methods'][$methodName]; } $operatorIndex = $tokens->getPrevMeaningfulToken($index); $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); if (!$this->needsConversion($tokens, $operatorIndex, $referenceIndex, $callType)) { continue; } if (self::CALL_TYPE_THIS === $callType && $this->isInsideStaticFunction($tokens, $analyzer, $index)) { continue; } $tokens[$operatorIndex] = new Token($this->conversionMap[$callType][0]); $tokens[$referenceIndex] = new Token($this->conversionMap[$callType][1]); } } private function needsConversion(Tokens $tokens, $operatorIndex, $referenceIndex, $callType) { if ($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STATIC, 'static'])) { return self::CALL_TYPE_STATIC !== $callType; } if ($tokens[$operatorIndex]->equals([T_OBJECT_OPERATOR, '->']) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'])) { return self::CALL_TYPE_THIS !== $callType; } if ($tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STRING, 'self'])) { return self::CALL_TYPE_SELF !== $callType; } return false; } private function isInsideStaticFunction(Tokens $tokens, TokensAnalyzer $analyzer, $index) { $functionIndex = $tokens->getPrevTokenOfKind($index, [[T_FUNCTION]]); while ($analyzer->isLambda($functionIndex)) { $openingCurlyBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['{']); $closingCurlyBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingCurlyBraceIndex); if ($closingCurlyBraceIndex > $index) { $prev = $tokens->getPrevMeaningfulToken($functionIndex); if ($tokens[$prev]->isGivenKind(T_STATIC)) { return true; } } $functionIndex = $tokens->getPrevTokenOfKind($functionIndex, [[T_FUNCTION]]); } $prev = $functionIndex; do { $prev = $tokens->getPrevMeaningfulToken($prev); } while ($tokens[$prev]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL])); return $tokens[$prev]->isGivenKind(T_STATIC); } } fixMessageRegExp = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_4_3); } public function getDefinition() { return new FixerDefinition( 'Usages of `@expectedException*` annotations MUST be replaced by `->setExpectedException*` methods.', [ new CodeSample( ' PhpUnitTargetVersion::VERSION_3_2] ), ], null, 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' ); } public function getPriority() { return 10; } public function isCandidate(Tokens $tokens) { return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { $this->fixPhpUnitClass($tokens, $indexes[0], $indexes[1]); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) ->setAllowedTypes(['string']) ->setAllowedValues([PhpUnitTargetVersion::VERSION_3_2, PhpUnitTargetVersion::VERSION_4_3, PhpUnitTargetVersion::VERSION_NEWEST]) ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) ->getOption(), (new FixerOptionBuilder('use_class_const', 'Use ::class notation.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), ]); } private function detectIndent(Tokens $tokens, $index) { if (!$tokens[$index - 1]->isWhitespace()) { return ''; } $explodedContent = explode("\n", $tokens[$index - 1]->getContent()); return end($explodedContent); } private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($i = $endIndex - 1; $i > $startIndex; --$i) { if (!$tokens[$i]->isGivenKind(T_FUNCTION) || $tokensAnalyzer->isLambda($i)) { continue; } $functionIndex = $i; $docBlockIndex = $i; $braceIndex = $tokens->getNextTokenOfKind($functionIndex, [';', '{']); if (!$tokens[$braceIndex]->equals('{')) { continue; } do { $docBlockIndex = $tokens->getPrevNonWhitespace($docBlockIndex); } while ($tokens[$docBlockIndex]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); if (!$tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); $annotations = []; foreach ($doc->getAnnotationsOfType([ 'expectedException', 'expectedExceptionCode', 'expectedExceptionMessage', 'expectedExceptionMessageRegExp', ]) as $annotation) { $tag = $annotation->getTag()->getName(); $content = $this->extractContentFromAnnotation($annotation); $annotations[$tag] = $content; $annotation->remove(); } if (!isset($annotations['expectedException'])) { continue; } if (!$this->fixMessageRegExp && isset($annotations['expectedExceptionMessageRegExp'])) { continue; } $originalIndent = $this->detectIndent($tokens, $docBlockIndex); $paramList = $this->annotationsToParamList($annotations); $newMethodsCode = '' .(isset($annotations['expectedExceptionMessageRegExp']) ? 'setExpectedExceptionRegExp' : 'setExpectedException') .'(' .implode(', ', $paramList) .');'; $newMethods = Tokens::fromCode($newMethodsCode); $newMethods[0] = new Token([ T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$originalIndent.$this->whitespacesConfig->getIndent(), ]); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $doc->getContent()]); $tokens->insertAt($braceIndex + 1, $newMethods); $tokens[$braceIndex + $newMethods->getSize() + 1] = new Token([ T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$tokens[$braceIndex + $newMethods->getSize() + 1]->getContent(), ]); $i = $docBlockIndex; } } private function extractContentFromAnnotation(Annotation $annotation) { $tag = $annotation->getTag()->getName(); Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches); $content = $matches[1]; if (Preg::match('/\R/u', $content)) { $content = Preg::replace('/\s*\R+\s*\*\s*/u', ' ', $content); } return rtrim($content); } private function annotationsToParamList(array $annotations) { $params = []; $exceptionClass = '\\'.ltrim($annotations['expectedException'], '\\'); if ($this->configuration['use_class_const']) { $params[] = $exceptionClass.'::class'; } else { $params[] = "'${exceptionClass}'"; } if (isset($annotations['expectedExceptionMessage'])) { $params[] = var_export($annotations['expectedExceptionMessage'], true); } elseif (isset($annotations['expectedExceptionMessageRegExp'])) { $params[] = var_export($annotations['expectedExceptionMessageRegExp'], true); } elseif (isset($annotations['expectedExceptionCode'])) { $params[] = 'null'; } if (isset($annotations['expectedExceptionCode'])) { $params[] = $annotations['expectedExceptionCode']; } return $params; } } true, ]), ] ); } public function getPriority() { return -1; } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(';'); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('remove_in_empty_for_expressions', 'Whether spaces should be removed for empty `for` expressions.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $insideForParenthesesUntil = null; for ($index = 0, $max = \count($tokens) - 1; $index < $max; ++$index) { if ($this->configuration['remove_in_empty_for_expressions']) { if ($tokens[$index]->isGivenKind(T_FOR)) { $index = $tokens->getNextMeaningfulToken($index); $insideForParenthesesUntil = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($index === $insideForParenthesesUntil) { $insideForParenthesesUntil = null; continue; } } if (!$tokens[$index]->equals(';')) { continue; } if (!$tokens[$index + 1]->isWhitespace()) { if ( !$tokens[$index + 1]->equalsAny([')', [T_INLINE_HTML]]) && ( !$this->configuration['remove_in_empty_for_expressions'] || !$tokens[$index + 1]->equals(';') ) ) { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); ++$max; } continue; } if ( null !== $insideForParenthesesUntil && ($tokens[$index + 2]->equals(';') || $index + 2 === $insideForParenthesesUntil) && !Preg::match('/\R/', $tokens[$index + 1]->getContent()) ) { $tokens->clearAt($index + 1); continue; } if ( isset($tokens[$index + 2]) && !$tokens[$index + 1]->equals([T_WHITESPACE, ' ']) && $tokens[$index + 1]->isWhitespace(" \t") && !$tokens[$index + 2]->isComment() && !$tokens[$index + 2]->equals(')') ) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } } } } isTokenKindFound(';'); } public function getDefinition() { return new FixerDefinition( 'Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls.', [ new CodeSample( 'method1() ->method2() ->method(3); ?> ', ['strategy' => self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS] ), ] ); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder( 'strategy', 'Forbid multi-line whitespace or move the semicolon to the new line for chained calls.' )) ->setAllowedValues([self::STRATEGY_NO_MULTI_LINE, self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS]) ->setDefault(self::STRATEGY_NO_MULTI_LINE) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if (self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS === $this->configuration['strategy']) { $this->applyChainedCallsFix($tokens); return; } if (self::STRATEGY_NO_MULTI_LINE === $this->configuration['strategy']) { $this->applyNoMultiLineFix($tokens); } } private function applyNoMultiLineFix(Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); foreach ($tokens as $index => $token) { if (!$token->equals(';')) { continue; } $previousIndex = $index - 1; $previous = $tokens[$previousIndex]; if (!$previous->isWhitespace() || false === strpos($previous->getContent(), "\n")) { continue; } $content = $previous->getContent(); if (0 === strpos($content, $lineEnding) && $tokens[$index - 2]->isComment()) { $tokens->ensureWhitespaceAtIndex($previousIndex, 0, $lineEnding); } else { $tokens->clearAt($previousIndex); } } } private function applyChainedCallsFix(Tokens $tokens) { for ($index = \count($tokens) - 1; $index >= 0; --$index) { if (!$tokens[$index]->equals(';')) { continue; } $indent = $this->findWhitespaceBeforeFirstCall($index - 1, $tokens); if (null === $indent) { continue; } $tokens->clearAt($index); $index = $this->getNewLineIndex($index, $tokens); $lineEnding = $this->whitespacesConfig->getLineEnding(); $newline = new Token([T_WHITESPACE, $lineEnding.$indent]); $tokens->insertAt($index, [$newline, new Token(';')]); } } private function getNewLineIndex($index, Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($index, $count = \count($tokens); $index < $count; ++$index) { if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { return $index; } } return $index; } private function findWhitespaceBeforeFirstCall($index, Tokens $tokens) { if (!$tokens[$index]->equals(')')) { return null; } $openingBrackets = 1; for (--$index; $index > 0; --$index) { if ($tokens[$index]->equals(')')) { ++$openingBrackets; continue; } if ($tokens[$index]->equals('(')) { if (1 === $openingBrackets) { break; } --$openingBrackets; } } if (!$tokens[--$index]->isGivenKind(T_STRING)) { return null; } if (!$tokens[--$index]->isGivenKind([T_OBJECT_OPERATOR, T_DOUBLE_COLON])) { return null; } if (!$tokens[--$index]->isGivenKind(T_WHITESPACE)) { return null; } $closingBrackets = 0; for ($index; $index >= 0; --$index) { if ($tokens[$index]->equals(')')) { ++$closingBrackets; } if ($tokens[$index]->equals('(')) { --$closingBrackets; } if ($tokens[$index]->isGivenKind([T_VARIABLE, T_RETURN, T_STRING]) && 0 === $closingBrackets) { if ($tokens[--$index]->isGivenKind(T_WHITESPACE) || $tokens[$index]->isGivenKind(T_OPEN_TAG)) { return $this->getIndentAt($tokens, $index); } } } return null; } private function getIndentAt(Tokens $tokens, $index) { $content = ''; $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($index; $index > 0; --$index) { if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { break; } } if ($tokens[$index]->isWhitespace()) { $content = $tokens[$index]->getContent(); --$index; } if ($tokens[$index]->isGivenKind(T_OPEN_TAG)) { $content = $tokens[$index]->getContent().$content; } if (1 === Preg::match('/\R{1}([ \t]*)$/', $content, $matches)) { return $matches[1]; } return null; } } foo() ;\n")] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(';'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->equals(';') || !$tokens[$index - 1]->isWhitespace(" \t")) { continue; } if ($tokens[$index - 2]->equals(';')) { if (!$tokens[$index - 1]->equals(' ')) { $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); } } elseif (!$tokens[$index - 2]->isComment()) { $tokens->clearAt($index - 1); } } } } proxyFixers); } protected function createProxyFixers() { $fixer = new MultilineWhitespaceBeforeSemicolonsFixer(); $fixer->configure(['strategy' => MultilineWhitespaceBeforeSemicolonsFixer::STRATEGY_NO_MULTI_LINE]); return [$fixer]; } } \n")] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_CLOSE_TAG); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = \count($tokens) - 1; $index > 1; --$index) { if (!$tokens[$index]->isGivenKind(T_CLOSE_TAG)) { continue; } $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) { continue; } $tokens->insertAt($prev + 1, new Token(';')); } } } isTokenKindFound(';'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { if ($tokens[$index]->isGivenKind(T_FOR)) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($index)) + 1; continue; } if (!$tokens[$index]->equals(';')) { continue; } $previousMeaningfulIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$previousMeaningfulIndex]->equalsAny(['{', ';', [T_OPEN_TAG]])) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); continue; } if ($tokens[$previousMeaningfulIndex]->equals('}')) { $this->fixSemicolonAfterCurlyBraceClose($tokens, $index, $previousMeaningfulIndex); } } } private function fixSemicolonAfterCurlyBraceClose(Tokens $tokens, $index, $curlyCloseIndex) { static $beforeCurlyOpeningKinds = null; if (null === $beforeCurlyOpeningKinds) { $beforeCurlyOpeningKinds = [T_ELSE, T_FINALLY, T_NAMESPACE, T_OPEN_TAG]; } $curlyOpeningIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $curlyCloseIndex); $beforeCurlyOpening = $tokens->getPrevMeaningfulToken($curlyOpeningIndex); if ($tokens[$beforeCurlyOpening]->isGivenKind($beforeCurlyOpeningKinds) || $tokens[$beforeCurlyOpening]->equalsAny([';', '{', '}'])) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); return; } if ($tokens[$beforeCurlyOpening]->isGivenKind(T_STRING)) { $classyTest = $tokens->getPrevMeaningfulToken($beforeCurlyOpening); while ($tokens[$classyTest]->equals(',') || $tokens[$classyTest]->isGivenKind([T_STRING, T_NS_SEPARATOR, T_EXTENDS, T_IMPLEMENTS])) { $classyTest = $tokens->getPrevMeaningfulToken($classyTest); } $tokensAnalyzer = new TokensAnalyzer($tokens); if ( $tokens[$classyTest]->isGivenKind(T_NAMESPACE) || ($tokens[$classyTest]->isClassy() && !$tokensAnalyzer->isAnonymousClass($classyTest)) ) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } return; } if (!$tokens[$beforeCurlyOpening]->equals(')')) { return; } $openingBrace = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $beforeCurlyOpening); $beforeOpeningBrace = $tokens->getPrevMeaningfulToken($openingBrace); if ($tokens[$beforeOpeningBrace]->isGivenKind([T_IF, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_SWITCH, T_CATCH, T_DECLARE])) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); return; } if ($tokens[$beforeOpeningBrace]->isGivenKind(T_STRING)) { $beforeString = $tokens->getPrevMeaningfulToken($beforeOpeningBrace); if ($tokens[$beforeString]->isGivenKind(T_FUNCTION)) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } } } isTokenKindFound('!'); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = \count($tokens) - 1; $index > 1; --$index) { if ($tokens[$index]->equals('!')) { $index = $this->fixShortCast($tokens, $index); } } } private function fixShortCast(Tokens $tokens, $index) { for ($i = $index - 1; $i > 1; --$i) { if ($tokens[$i]->equals('!')) { $this->fixShortCastToBoolCast($tokens, $i, $index); break; } if (!$tokens[$i]->isComment() && !$tokens[$i]->isWhitespace()) { break; } } return $i; } private function fixShortCastToBoolCast(Tokens $tokens, $start, $end) { for (; $start <= $end; ++$start) { if ( !$tokens[$start]->isComment() && !($tokens[$start]->isWhitespace() && $tokens[$start - 1]->isComment()) ) { $tokens->clearAt($start); } } $tokens->insertAt($start, new Token([T_BOOL_CAST, '(bool)'])); } } isTokenKindFound(T_UNSET_CAST); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = \count($tokens) - 1; $index > 0; --$index) { if ($tokens[$index]->isGivenKind(T_UNSET_CAST)) { $this->fixUnsetCast($tokens, $index); } } } private function fixUnsetCast(Tokens $tokens, $index) { $assignmentIndex = $tokens->getPrevMeaningfulToken($index); if (null === $assignmentIndex || !$tokens[$assignmentIndex]->equals('=')) { return; } $varIndex = $tokens->getNextMeaningfulToken($index); if (null === $varIndex || !$tokens[$varIndex]->isGivenKind(T_VARIABLE)) { return; } $nextIsWhiteSpace = $tokens[$assignmentIndex + 1]->isWhitespace(); $tokens->clearTokenAndMergeSurroundingWhitespace($index); $tokens->clearTokenAndMergeSurroundingWhitespace($varIndex); ++$assignmentIndex; if (!$nextIsWhiteSpace) { $tokens->insertAt($assignmentIndex, new Token([T_WHITESPACE, ' '])); } ++$assignmentIndex; $tokens->insertAt($assignmentIndex, new Token([T_STRING, 'null'])); } } isAnyTokenKindsFound(Token::getCastTokenKinds()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $castMap = [ 'boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'real' => 'float', 'binary' => 'string', ]; for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { if (!$tokens[$index]->isCast()) { continue; } $castFrom = trim(substr($tokens[$index]->getContent(), 1, -1)); $castFromLowered = strtolower($castFrom); if (!\array_key_exists($castFromLowered, $castMap)) { continue; } $tokens[$index] = new Token([ $tokens[$index]->getId(), str_replace($castFrom, $castMap[$castFromLowered], $tokens[$index]->getContent()), ]); } } } '', "\t" => '', "\n" => '', "\r" => '', "\0" => '', "\x0B" => '', ]; public function getDefinition() { return new FixerDefinition( 'A single space or none should be between cast and variable.', [ new CodeSample( " 'single'] ), new CodeSample( " 'none'] ), ] ); } public function getPriority() { return -10; } public function isCandidate(Tokens $tokens) { return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isCast()) { continue; } $tokens[$index] = new Token([ $token->getId(), strtr($token->getContent(), self::INSIDE_CAST_SPACE_REPLACE_MAP), ]); if ('single' === $this->configuration['space']) { if ($tokens[$index + 1]->isWhitespace(" \t")) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } elseif (!$tokens[$index + 1]->isWhitespace()) { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } continue; } if ($tokens[$index + 1]->isWhitespace()) { $tokens->clearAt($index + 1); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('space', 'spacing to apply between cast and variable.')) ->setAllowedValues(['none', 'single']) ->setDefault('single') ->getOption(), ]); } } isTokenKindFound(T_STRING); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $replacement = [ 'intval' => [T_INT_CAST, '(int)'], 'floatval' => [T_DOUBLE_CAST, '(float)'], 'doubleval' => [T_DOUBLE_CAST, '(float)'], 'strval' => [T_STRING_CAST, '(string)'], 'boolval' => [T_BOOL_CAST, '(bool)'], ]; $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach ($replacement as $functionIdentity => $newToken) { $currIndex = 0; while (null !== $currIndex) { $boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1); if (null === $boundaries) { continue 2; } list($functionName, $openParenthesis, $closeParenthesis) = $boundaries; $currIndex = $openParenthesis; if (1 !== $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis)) { continue; } $paramContentEnd = $closeParenthesis; $commaCandidate = $tokens->getPrevMeaningfulToken($paramContentEnd); if ($tokens[$commaCandidate]->equals(',')) { $tokens->removeTrailingWhitespace($commaCandidate); $tokens->clearAt($commaCandidate); $paramContentEnd = $commaCandidate; } $countParamTokens = 0; for ($paramContentIndex = $openParenthesis + 1; $paramContentIndex < $paramContentEnd; ++$paramContentIndex) { if (!$tokens[$paramContentIndex]->isGivenKind(T_WHITESPACE)) { ++$countParamTokens; } } $preserveParenthesises = $countParamTokens > 1; $afterCloseParenthesisIndex = $tokens->getNextMeaningfulToken($closeParenthesis); $afterCloseParenthesisToken = $tokens[$afterCloseParenthesisIndex]; $wrapInParenthesises = $afterCloseParenthesisToken->equals('[') || $afterCloseParenthesisToken->isGivenKind(T_POW); $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionName); if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { $tokens->removeTrailingWhitespace($prevTokenIndex); $tokens->clearAt($prevTokenIndex); } $replacementSequence = [ new Token($newToken), new Token([T_WHITESPACE, ' ']), ]; if ($wrapInParenthesises) { array_unshift($replacementSequence, new Token('(')); } if (!$preserveParenthesises) { $tokens->removeLeadingWhitespace($closeParenthesis); $tokens->clearAt($closeParenthesis); $tokens->removeLeadingWhitespace($openParenthesis); $tokens->removeTrailingWhitespace($openParenthesis); $tokens->clearAt($openParenthesis); } else { $tokens->removeTrailingWhitespace($functionName); } if ($wrapInParenthesises) { $tokens->insertAt($closeParenthesis, new Token(')')); } $tokens->overrideRange($functionName, $functionName, $replacementSequence); $currIndex = $functionName; } } } } isAnyTokenKindsFound(Token::getCastTokenKinds()); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { if (!$tokens[$index]->isCast()) { continue; } $tokens[$index] = new Token([$tokens[$index]->getId(), strtolower($tokens[$index]->getContent())]); } } } = 0; }; $negative = function ($item) { return $item < 0; }; ', ['allow_single_line_closure' => true] ), new CodeSample( ' self::LINE_SAME] ), ] ); } public function getPriority() { return -25; } public function isCandidate(Tokens $tokens) { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->fixCommentBeforeBrace($tokens); $this->fixMissingControlBraces($tokens); $this->fixIndents($tokens); $this->fixControlContinuationBraces($tokens); $this->fixSpaceAroundToken($tokens); $this->fixDoWhile($tokens); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('allow_single_line_closure', 'Whether single line lambda notation should be allowed.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_NEXT) ->getOption(), (new FixerOptionBuilder('position_after_control_structures', 'whether the opening brace should be placed on "next" or "same" line after control structures.')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_SAME) ->getOption(), (new FixerOptionBuilder('position_after_anonymous_constructs', 'whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_SAME) ->getOption(), ]); } private function fixCommentBeforeBrace(Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $controlTokens = $this->getControlTokens(); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if ($token->isGivenKind($controlTokens)) { $prevIndex = $this->findParenthesisEnd($tokens, $index); } elseif ( ($token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index)) || ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) ) { $prevIndex = $tokens->getNextTokenOfKind($index, ['{']); $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } else { continue; } $commentIndex = $tokens->getNextNonWhitespace($prevIndex); $commentToken = $tokens[$commentIndex]; if (!$commentToken->isGivenKind(T_COMMENT) || 0 === strpos($commentToken->getContent(), '/*')) { continue; } $braceIndex = $tokens->getNextMeaningfulToken($commentIndex); $braceToken = $tokens[$braceIndex]; if (!$braceToken->equals('{')) { continue; } $tokenTmp = $tokens[$braceIndex]; $newBraceIndex = $prevIndex + 1; for ($i = $braceIndex; $i > $newBraceIndex; --$i) { $tokens[$i] = $tokens[$i - 1]; if ($tokens[$i]->isWhitespace() && $tokens[$i + 1]->isWhitespace()) { $tokens[$i] = new Token([T_WHITESPACE, $tokens[$i]->getContent().$tokens[$i + 1]->getContent()]); $tokens->clearAt($i + 1); } } $tokens[$newBraceIndex] = $tokenTmp; $c = $tokens[$braceIndex]->getContent(); if (substr_count($c, "\n") > 1) { $tokens[$braceIndex] = new Token([T_WHITESPACE, substr($c, strrpos($c, "\n"))]); } } } private function fixControlContinuationBraces(Tokens $tokens) { $controlContinuationTokens = $this->getControlContinuationTokens(); for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind($controlContinuationTokens)) { continue; } $prevIndex = $tokens->getPrevNonWhitespace($index); $prevToken = $tokens[$prevIndex]; if (!$prevToken->equals('}')) { continue; } $tokens->ensureWhitespaceAtIndex( $index - 1, 1, self::LINE_NEXT === $this->configuration['position_after_control_structures'] ? $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $index) : ' ' ); } } private function fixDoWhile(Tokens $tokens) { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_DO)) { continue; } $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($endBraceIndex); $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; if (!$nextNonWhitespaceToken->isGivenKind(T_WHILE)) { continue; } $tokens->ensureWhitespaceAtIndex($nextNonWhitespaceIndex - 1, 1, ' '); } } private function fixIndents(Tokens $tokens) { $classyTokens = Token::getClassyTokenKinds(); $classyAndFunctionTokens = array_merge([T_FUNCTION], $classyTokens); $controlTokens = $this->getControlTokens(); $indentTokens = array_filter( array_merge($classyAndFunctionTokens, $controlTokens), static function ($item) { return T_SWITCH !== $item; } ); $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind($indentTokens)) { continue; } if ( $token->isGivenKind(T_WHILE) && $tokensAnalyzer->isWhilePartOfDoWhile($index) ) { continue; } if ( $this->configuration['allow_single_line_closure'] && $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index) ) { $braceEndIndex = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($index, ['{']) ); if (!$this->isMultilined($tokens, $index, $braceEndIndex)) { $index = $braceEndIndex; continue; } } if ($token->isGivenKind($classyAndFunctionTokens)) { $startBraceIndex = $tokens->getNextTokenOfKind($index, [';', '{']); $startBraceToken = $tokens[$startBraceIndex]; } else { $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); $startBraceToken = $tokens[$startBraceIndex]; } if (!$startBraceToken->equals('{')) { continue; } $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); $indent = $this->detectIndent($tokens, $index); $tokens->ensureWhitespaceAtIndex($endBraceIndex - 1, 1, $this->whitespacesConfig->getLineEnding().$indent); $lastCommaIndex = $tokens->getPrevTokenOfKind($endBraceIndex - 1, [';', '}']); $nestLevel = 1; for ($nestIndex = $lastCommaIndex; $nestIndex >= $startBraceIndex; --$nestIndex) { $nestToken = $tokens[$nestIndex]; if ($nestToken->equalsAny([')', [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]])) { $nestIndex = $tokens->findBlockStart( $nestToken->equals(')') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, $nestIndex ); continue; } if (1 === $nestLevel) { $nextLineCanBeIndented = false; if ($nestToken->equalsAny([';', '}'])) { $nextLineCanBeIndented = true; } elseif ($this->isCommentWithFixableIndentation($tokens, $nestIndex)) { for ($i = $nestIndex; $i > $startBraceIndex; --$i) { if ($tokens[$i]->equalsAny([';', '}'])) { $nextLineCanBeIndented = true; break; } if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { break; } } if ($nextLineCanBeIndented || $i === $startBraceIndex) { $nextToken = $tokens[$nestIndex + 1]; $nextLineCanBeIndented = $nextToken->isWhitespace() && 1 === Preg::match('/\R/', $nextToken->getContent()); } } if (!$nextLineCanBeIndented) { continue; } $nextNonWhitespaceNestIndex = $tokens->getNextNonWhitespace($nestIndex); $nextNonWhitespaceNestToken = $tokens[$nextNonWhitespaceNestIndex]; if ( !($nextNonWhitespaceNestToken->isComment() && ( !$tokens[$nextNonWhitespaceNestIndex - 1]->isWhitespace() || !Preg::match('/\R/', $tokens[$nextNonWhitespaceNestIndex - 1]->getContent()) )) && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equalsAny([';', ',', ']', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equals('(')) && !($nestToken->equals('}') && $tokens[$nestIndex - 1]->equalsAny(['"', "'", [T_CONSTANT_ENCAPSED_STRING]])) && !($tokens[$nestIndex - 1]->isGivenKind(T_END_HEREDOC) && $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG)) ) { if ( ( self::LINE_NEXT !== $this->configuration['position_after_control_structures'] && $nextNonWhitespaceNestToken->isGivenKind($this->getControlContinuationTokens()) && !$tokens[$tokens->getPrevNonWhitespace($nextNonWhitespaceNestIndex)]->isComment() ) || $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG) || ( self::LINE_NEXT !== $this->configuration['position_after_control_structures'] && $nextNonWhitespaceNestToken->isGivenKind(T_WHILE) && $tokensAnalyzer->isWhilePartOfDoWhile($nextNonWhitespaceNestIndex) ) ) { $whitespace = ' '; } else { $nextToken = $tokens[$nestIndex + 1]; $nextWhitespace = ''; if ($nextToken->isWhitespace()) { $nextWhitespace = rtrim($nextToken->getContent(), " \t"); if ('' !== $nextWhitespace) { $nextWhitespace = Preg::replace( sprintf('/%s$/', $this->whitespacesConfig->getLineEnding()), '', $nextWhitespace, 1 ); } } $whitespace = $nextWhitespace.$this->whitespacesConfig->getLineEnding().$indent; if (!$nextNonWhitespaceNestToken->equals('}')) { $determineIsIndentableBlockContent = function ($contentIndex) use ($tokens) { if (!$tokens[$contentIndex]->isComment()) { return true; } if (!$tokens[$tokens->getPrevMeaningfulToken($contentIndex)]->equals(';')) { return true; } $nextIndex = $tokens->getNextMeaningfulToken($contentIndex); if (!$tokens[$nextIndex]->equals('}')) { return true; } $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); if (null === $nextNextIndex) { return true; } if ($tokens[$nextNextIndex]->equalsAny([ [T_ELSE], [T_ELSEIF], ',', ])) { return false; } return true; }; if ($determineIsIndentableBlockContent($nestIndex + 2)) { $whitespace .= $this->whitespacesConfig->getIndent(); } } } $this->ensureWhitespaceAtIndexAndIndentMultilineComment($tokens, $nestIndex + 1, $whitespace); } } if ($nestToken->equals('}')) { ++$nestLevel; continue; } if ($nestToken->equals('{')) { --$nestLevel; continue; } } if (isset($tokens[$startBraceIndex + 2]) && $tokens[$startBraceIndex + 2]->equals('}')) { $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, $this->whitespacesConfig->getLineEnding().$indent); } else { $nextToken = $tokens[$startBraceIndex + 1]; $nextNonWhitespaceToken = $tokens[$tokens->getNextNonWhitespace($startBraceIndex)]; if ( !$nextNonWhitespaceToken->isComment() || ($nextToken->isWhitespace() && 1 === substr_count($nextToken->getContent(), "\n")) ) { $this->ensureWhitespaceAtIndexAndIndentMultilineComment( $tokens, $startBraceIndex + 1, $this->whitespacesConfig->getLineEnding().$indent.$this->whitespacesConfig->getIndent() ); } } if ($token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index)) { if (self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment()) { $ensuredWhitespace = ' '; } else { $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; } $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); } elseif ( $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) || ( self::LINE_NEXT === $this->configuration['position_after_control_structures'] && $token->isGivenKind($controlTokens) || ( self::LINE_NEXT === $this->configuration['position_after_anonymous_constructs'] && ( $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index) || $token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index) ) ) ) ) { $isAnonymousClass = $token->isGivenKind($classyTokens) && $tokensAnalyzer->isAnonymousClass($index); $closingParenthesisIndex = $tokens->getPrevTokenOfKind($startBraceIndex, [')']); if (null === $closingParenthesisIndex && !$isAnonymousClass) { continue; } if (!$isAnonymousClass) { $prevToken = $tokens[$closingParenthesisIndex - 1]; } if (!$isAnonymousClass && $prevToken->isWhitespace() && false !== strpos($prevToken->getContent(), "\n")) { if (!$tokens[$startBraceIndex - 2]->isComment()) { $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); } } else { if ( self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && ( $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) || $token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index) ) && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment() ) { $ensuredWhitespace = ' '; } else { $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; } $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); } } else { $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); } $limit = \count($tokens); } } private function fixMissingControlBraces(Tokens $tokens) { $controlTokens = $this->getControlTokens(); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind($controlTokens)) { continue; } $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $tokenAfterParenthesis = $tokens[$tokens->getNextMeaningfulToken($parenthesisEndIndex)]; if ($tokenAfterParenthesis->equals('{') && self::LINE_SAME === $this->configuration['position_after_control_structures']) { $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); continue; } if ($tokenAfterParenthesis->equalsAny([';', '{', ':'])) { continue; } $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); $tokens->insertAt($statementEndIndex + 1, [new Token([T_WHITESPACE, ' ']), new Token('}')]); if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) { $tokens->insertAt($statementEndIndex + 1, new Token(';')); } $tokens->insertAt($parenthesisEndIndex + 1, new Token('{')); $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); } } private function fixSpaceAroundToken(Tokens $tokens) { $controlTokens = $this->getControlTokens(); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_DECLARE)) { $this->fixDeclareStatement($tokens, $index); } elseif ($token->isGivenKind($controlTokens) || $token->isGivenKind(CT::T_USE_LAMBDA)) { $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); if (!$tokens[$nextNonWhitespaceIndex]->equals(':')) { $tokens->ensureWhitespaceAtIndex( $index + 1, 0, self::LINE_NEXT === $this->configuration['position_after_control_structures'] && !$tokens[$nextNonWhitespaceIndex]->equals('(') ? $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $index) : ' ' ); } $prevToken = $tokens[$index - 1]; if (!$prevToken->isWhitespace() && !$prevToken->isComment() && !$prevToken->isGivenKind(T_OPEN_TAG)) { $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); } } } } private function detectIndent(Tokens $tokens, $index) { while (true) { $whitespaceIndex = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE]]); if (null === $whitespaceIndex) { return ''; } $whitespaceToken = $tokens[$whitespaceIndex]; if (false !== strpos($whitespaceToken->getContent(), "\n")) { break; } $prevToken = $tokens[$whitespaceIndex - 1]; if ($prevToken->isGivenKind([T_OPEN_TAG, T_COMMENT]) && "\n" === substr($prevToken->getContent(), -1)) { break; } $index = $whitespaceIndex; } $explodedContent = explode("\n", $whitespaceToken->getContent()); return end($explodedContent); } private function findParenthesisEnd(Tokens $tokens, $structureTokenIndex) { $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); $nextToken = $tokens[$nextIndex]; if (!$nextToken->equals('(')) { return $structureTokenIndex; } return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); } private function findStatementEnd(Tokens $tokens, $parenthesisEndIndex) { $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); $nextToken = $tokens[$nextIndex]; if (!$nextToken) { return $parenthesisEndIndex; } if ($nextToken->equals('{')) { return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex); } if ($nextToken->isGivenKind($this->getControlTokens())) { $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) { $openingTokenKind = $nextToken->getId(); while (true) { $nextIndex = $tokens->getNextMeaningfulToken($endIndex); $nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null; if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) { $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); if ($nextToken->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) { return $endIndex; } } else { break; } } } return $endIndex; } $index = $parenthesisEndIndex; while (true) { $token = $tokens[++$index]; if ($token->equals('{')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); continue; } if ($token->equals(';')) { return $index; } if ($token->isGivenKind(T_CLOSE_TAG)) { return $tokens->getPrevNonWhitespace($index); } } throw new \RuntimeException('Statement end not found.'); } private function getControlTokens() { static $tokens = [ T_DECLARE, T_DO, T_ELSE, T_ELSEIF, T_FINALLY, T_FOR, T_FOREACH, T_IF, T_WHILE, T_TRY, T_CATCH, T_SWITCH, ]; return $tokens; } private function getControlContinuationTokens() { static $tokens = [ T_CATCH, T_ELSE, T_ELSEIF, T_FINALLY, ]; return $tokens; } private function getControlContinuationTokensForOpeningToken($openingTokenKind) { if (T_IF === $openingTokenKind) { return [ T_ELSE, T_ELSEIF, ]; } if (T_DO === $openingTokenKind) { return [T_WHILE]; } if (T_TRY === $openingTokenKind) { return [ T_CATCH, T_FINALLY, ]; } return []; } private function getFinalControlContinuationTokensForOpeningToken($openingTokenKind) { if (T_IF === $openingTokenKind) { return [T_ELSE]; } if (T_TRY === $openingTokenKind) { return [T_FINALLY]; } return []; } private function fixDeclareStatement(Tokens $tokens, $index) { $tokens->removeTrailingWhitespace($index); $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(']); $tokens->removeTrailingWhitespace($startParenthesisIndex); $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); $tokens->removeLeadingWhitespace($endParenthesisIndex); $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{']); $startBraceToken = $tokens[$startBraceIndex]; if ($startBraceToken->equals('{')) { $this->fixSingleLineWhitespaceForDeclare($tokens, $startBraceIndex); } } private function fixSingleLineWhitespaceForDeclare(Tokens $tokens, $startBraceIndex) { if ( !$tokens[$startBraceIndex - 1]->isWhitespace() || $tokens[$startBraceIndex - 1]->isWhitespace(" \t") ) { $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); } } private function ensureWhitespaceAtIndexAndIndentMultilineComment(Tokens $tokens, $index, $whitespace) { if ($tokens[$index]->isWhitespace()) { $nextTokenIndex = $tokens->getNextNonWhitespace($index); } else { $nextTokenIndex = $index; } $nextToken = $tokens[$nextTokenIndex]; if ($nextToken->isComment()) { $previousToken = $tokens[$nextTokenIndex - 1]; if ( (0 === strpos($nextToken->getContent(), '//'.$this->whitespacesConfig->getIndent()) || '//' === $nextToken->getContent()) && $previousToken->isWhitespace() && 1 === Preg::match('/\R$/', $previousToken->getContent()) ) { return; } $tokens[$nextTokenIndex] = new Token([ $nextToken->getId(), Preg::replace( '/(\R)'.$this->detectIndent($tokens, $nextTokenIndex).'/', '$1'.Preg::replace('/^.*\R([ \t]*)$/s', '$1', $whitespace), $nextToken->getContent() ), ]); } $tokens->ensureWhitespaceAtIndex($index, 0, $whitespace); } private function isMultilined(Tokens $tokens, $startParenthesisIndex, $endParenthesisIndex) { for ($i = $startParenthesisIndex; $i < $endParenthesisIndex; ++$i) { if (false !== strpos($tokens[$i]->getContent(), "\n")) { return true; } } return false; } private function isCommentWithFixableIndentation(Tokens $tokens, $index) { if (!$tokens[$index]->isComment()) { return false; } if (0 === strpos($tokens[$index]->getContent(), '/*')) { return true; } $firstCommentIndex = $index; while (true) { $i = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false); if (null === $i) { break; } $firstCommentIndex = $i; } $lastCommentIndex = $index; while (true) { $i = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true); if (null === $i) { break; } $lastCommentIndex = $i; } if ($firstCommentIndex === $lastCommentIndex) { return true; } for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) { if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { return false; } } return true; } private function getSiblingContinuousSingleLineComment(Tokens $tokens, $index, $after) { $siblingIndex = $index; do { if ($after) { $siblingIndex = $tokens->getNextTokenOfKind($siblingIndex, [[T_COMMENT]]); } else { $siblingIndex = $tokens->getPrevTokenOfKind($siblingIndex, [[T_COMMENT]]); } if (null === $siblingIndex) { return null; } } while (0 === strpos($tokens[$siblingIndex]->getContent(), '/*')); $newLines = 0; for ($i = min($siblingIndex, $index) + 1, $max = max($siblingIndex, $index); $i < $max; ++$i) { if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) { if (1 === $newLines || Preg::match('/\R.*\R/', $tokens[$i]->getContent())) { return null; } ++$newLines; } } return $siblingIndex; } } symbolsReplace = [ pack('H*', 'e2808b') => ['', '200b'], pack('H*', 'e28087') => [' ', '2007'], pack('H*', 'e280af') => [' ', '202f'], pack('H*', 'e281a0') => ['', '2060'], pack('H*', 'c2a0') => [' ', 'a0'], ]; } public function getDefinition() { return new FixerDefinition( 'Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.', [ new CodeSample( ' true] ), ], null, 'Risky when strings contain intended invisible characters.' ); } public function isRisky() { return true; } public function isCandidate(Tokens $tokens) { return \count($tokens) > 1 && $tokens->isAnyTokenKindsFound(self::$tokens); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('use_escape_sequences_in_strings', 'Whether characters should be replaced with escape sequences in strings.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->setNormalizer(static function (Options $options, $value) { if (\PHP_VERSION_ID < 70000 && $value) { throw new InvalidOptionsForEnvException('Escape sequences require PHP 7.0+.'); } return $value; }) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $replacements = []; $escapeSequences = []; foreach ($this->symbolsReplace as $character => list($replacement, $codepoint)) { $replacements[$character] = $replacement; $escapeSequences[$character] = '\u{'.$codepoint.'}'; } foreach ($tokens as $index => $token) { $content = $token->getContent(); if ( $this->configuration['use_escape_sequences_in_strings'] && $token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE]) ) { if (!Preg::match('/'.implode('|', array_keys($escapeSequences)).'/', $content)) { continue; } $previousToken = $tokens[$index - 1]; $stringTypeChanged = false; if ($previousToken->isGivenKind(T_START_HEREDOC)) { $previousTokenContent = $previousToken->getContent(); if (false !== strpos($previousTokenContent, '\'')) { $tokens[$index - 1] = new Token([T_START_HEREDOC, str_replace('\'', '', $previousTokenContent)]); $stringTypeChanged = true; } } elseif ("'" === $content[0]) { $content = Preg::replace('/^\'(.*)\'$/', '"$1"', $content); $stringTypeChanged = true; } if ($stringTypeChanged) { $content = Preg::replace('/([\\\\$])/', '\\\\$1', $content); } $tokens[$index] = new Token([$token->getId(), strtr($content, $escapeSequences)]); continue; } if ($token->isGivenKind(self::$tokens)) { $tokens[$index] = new Token([$token->getId(), strtr($content, $replacements)]); } } } } $token) { if ($token->isGivenKind(T_NAMESPACE)) { if ($isNamespaceFound) { return; } $isNamespaceFound = true; } elseif ($token->isClassy()) { $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind(T_NEW)) { continue; } if (null !== $classyName) { return; } $classyIndex = $tokens->getNextMeaningfulToken($index); $classyName = $tokens[$classyIndex]->getContent(); } } if (null === $classyName) { return; } if ($isNamespaceFound) { $filename = basename(str_replace('\\', '/', $file->getRealPath()), '.php'); if ($classyName !== $filename) { $tokens[$classyIndex] = new Token([T_STRING, $filename]); } } else { $normClass = str_replace('_', '/', $classyName); $filename = substr(str_replace('\\', '/', $file->getRealPath()), -\strlen($normClass) - 4, -4); if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]); } } } } realpath(__DIR__.'/../..')] ), ], null, 'This fixer may change your class name, which will break the code that is depended on old name.' ); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $namespace = false; $namespaceIndex = 0; $namespaceEndIndex = 0; $classyName = null; $classyIndex = 0; foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_NAMESPACE)) { if (false !== $namespace) { return; } $namespaceIndex = $tokens->getNextMeaningfulToken($index); $namespaceEndIndex = $tokens->getNextTokenOfKind($index, [';']); $namespace = trim($tokens->generatePartialCode($namespaceIndex, $namespaceEndIndex - 1)); } elseif ($token->isClassy()) { $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind(T_NEW)) { continue; } if (null !== $classyName) { return; } $classyIndex = $tokens->getNextMeaningfulToken($index); $classyName = $tokens[$classyIndex]->getContent(); } } if (null === $classyName) { return; } if (false !== $namespace) { $normNamespace = str_replace('\\', '/', $namespace); $path = str_replace('\\', '/', $file->getRealPath()); $dir = \dirname($path); if ('' !== $this->configuration['dir']) { $dir = substr($dir, \strlen(realpath($this->configuration['dir'])) + 1); if (false === $dir) { $dir = ''; } if (\strlen($normNamespace) > \strlen($dir)) { if ('' !== $dir) { $normNamespace = substr($normNamespace, -\strlen($dir)); } else { $normNamespace = ''; } } } $dir = substr($dir, -\strlen($normNamespace)); if (false === $dir) { $dir = ''; } $filename = basename($path, '.php'); if ($classyName !== $filename) { $tokens[$classyIndex] = new Token([T_STRING, $filename]); } if ($normNamespace !== $dir && strtolower($normNamespace) === strtolower($dir)) { for ($i = $namespaceIndex; $i <= $namespaceEndIndex; ++$i) { $tokens->clearAt($i); } $namespace = substr($namespace, 0, -\strlen($dir)).str_replace('/', '\\', $dir); $newNamespace = Tokens::fromCode('clearRange(0, 2); $newNamespace->clearEmptyTokens(); $tokens->insertAt($namespaceIndex, $newNamespace); } } else { $normClass = str_replace('_', '/', $classyName); $path = str_replace('\\', '/', $file->getRealPath()); $filename = substr($path, -\strlen($normClass) - 4, -4); if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('dir', 'The directory where the project code is placed.')) ->setAllowedTypes(['string']) ->setDefault('') ->getOption(), ]); } } BOM = pack('CCC', 0xef, 0xbb, 0xbf); } public function getDefinition() { return new FixerDefinition( 'PHP code MUST use only UTF-8 without BOM (remove BOM).', [ new CodeSample( $this->BOM.'getContent(); if (0 === strncmp($content, $this->BOM, 3)) { $newContent = substr($content, 3); if (false === $newContent) { $newContent = ''; } if ('' === $newContent) { $tokens->clearAt(0); } else { $tokens[0] = new Token([$tokens[0]->getId(), $newContent]); } } } } isTokenKindFound(T_FUNCTION); } public function getDefinition() { return new FixerDefinition( 'Spaces should be properly placed in a function declaration.', [ new CodeSample( ' self::SPACING_NONE] ), ] ); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_FUNCTION)) { continue; } $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', [T_CLOSE_TAG]]); if (!$tokens[$startParenthesisIndex]->equals('(')) { continue; } $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{']); if ( $tokens[$startBraceIndex]->equals('{') && ( !$tokens[$startBraceIndex - 1]->isWhitespace() || $tokens[$startBraceIndex - 1]->isWhitespace($this->singleLineWhitespaceOptions) ) ) { $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); } $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex); $afterParenthesisToken = $tokens[$afterParenthesisIndex]; if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) { $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex + 1, 0, ' '); $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']); $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex); $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex); $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex - 1, 1, ' '); } $this->fixParenthesisInnerEdge($tokens, $startParenthesisIndex, $endParenthesisIndex); $isLambda = $tokensAnalyzer->isLambda($index); if (!$isLambda && $tokens[$startParenthesisIndex - 1]->isWhitespace() && !$tokens[$tokens->getPrevNonWhitespace($startParenthesisIndex - 1)]->isComment()) { $tokens->clearAt($startParenthesisIndex - 1); } if ($isLambda && self::SPACING_NONE === $this->configuration['closure_function_spacing']) { if ($tokens[$index + 1]->isWhitespace()) { $tokens->clearAt($index + 1); } } else { $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' '); } if ($isLambda) { $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->isGivenKind(T_STATIC)) { $tokens->ensureWhitespaceAtIndex($prev + 1, 0, ' '); } } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('closure_function_spacing', 'Spacing to use before open parenthesis for closures.')) ->setDefault(self::SPACING_ONE) ->setAllowedValues($this->supportedSpacings) ->getOption(), ]); } private function fixParenthesisInnerEdge(Tokens $tokens, $start, $end) { if ($tokens[$end - 1]->isWhitespace($this->singleLineWhitespaceOptions)) { $tokens->clearAt($end - 1); } if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) { $tokens->clearAt($start + 1); } } } = 7.0.', [ new VersionSpecificCodeSample( "= 70000 && $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } public function getPriority() { return 3; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->equals([T_STRING, 'dirname'], false)) { continue; } $dirnameInfo = $this->getDirnameInfo($tokens, $index); if (!$dirnameInfo) { continue; } $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); if (!$tokens[$prev]->equals('(')) { continue; } $prev = $tokens->getPrevMeaningfulToken($prev); $firstArgumentEnd = $dirnameInfo['end']; $dirnameInfoArray = [$dirnameInfo]; while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) { $dirnameInfoArray[] = $dirnameInfo; $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); if (!$tokens[$prev]->equals('(')) { break; } $prev = $tokens->getPrevMeaningfulToken($prev); $firstArgumentEnd = $dirnameInfo['end']; } if (\count($dirnameInfoArray) > 1) { $this->combineDirnames($tokens, $dirnameInfoArray); } $index = $prev; } } private function getDirnameInfo(Tokens $tokens, $index, $firstArgumentEndIndex = null) { if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) { return false; } if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) { return false; } $info['indexes'] = []; $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { $info['indexes'][] = $prev; } $info['indexes'][] = $index; $next = $tokens->getNextMeaningfulToken($index); $info['indexes'][] = $next; if (null !== $firstArgumentEndIndex) { $next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex); } else { $next = $tokens->getNextMeaningfulToken($next); if ($tokens[$next]->equals(')')) { return false; } while (!$tokens[$next]->equalsAny([',', ')'])) { $blockType = Tokens::detectBlockType($tokens[$next]); if ($blockType) { $next = $tokens->findBlockEnd($blockType['type'], $next); } $next = $tokens->getNextMeaningfulToken($next); } } $info['indexes'][] = $next; if ($tokens[$next]->equals(',')) { $next = $tokens->getNextMeaningfulToken($next); $info['indexes'][] = $next; } if ($tokens[$next]->equals(')')) { $info['levels'] = 1; $info['end'] = $next; return $info; } if (!$tokens[$next]->isGivenKind(T_LNUMBER)) { return false; } $info['secondArgument'] = $next; $info['levels'] = (int) $tokens[$next]->getContent(); $next = $tokens->getNextMeaningfulToken($next); if ($tokens[$next]->equals(',')) { $info['indexes'][] = $next; $next = $tokens->getNextMeaningfulToken($next); } if (!$tokens[$next]->equals(')')) { return false; } $info['indexes'][] = $next; $info['end'] = $next; return $info; } private function combineDirnames(Tokens $tokens, array $dirnameInfoArray) { $outerDirnameInfo = array_pop($dirnameInfoArray); $levels = $outerDirnameInfo['levels']; foreach ($dirnameInfoArray as $dirnameInfo) { $levels += $dirnameInfo['levels']; foreach ($dirnameInfo['indexes'] as $index) { $tokens->removeLeadingWhitespace($index); $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } $levelsToken = new Token([T_LNUMBER, (string) $levels]); if (isset($outerDirnameInfo['secondArgument'])) { $tokens[$outerDirnameInfo['secondArgument']] = $levelsToken; } else { $prev = $tokens->getPrevMeaningfulToken($outerDirnameInfo['end']); $items = []; if (!$tokens[$prev]->equals(',')) { $items[] = new Token(','); } $items[] = $levelsToken; $tokens->insertAt($outerDirnameInfo['end'], $items); } } } bindTo" on lambdas without referencing to `$this`.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_FUNCTION); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $analyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 4; $index > 0; --$index) { if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$analyzer->isLambda($index)) { continue; } $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->isGivenKind(T_STATIC)) { continue; } $lambdaOpenIndex = $tokens->getNextTokenOfKind($index, ['{']); $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex); if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) { continue; } $tokens->insertAt( $index, [ new Token([T_STATIC, 'static']), new Token([T_WHITESPACE, ' ']), ] ); $index -= 4; } } private function hasPossibleReferenceToThis(Tokens $tokens, $startIndex, $endIndex) { for ($i = $startIndex; $i < $endIndex; ++$i) { if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) { return true; } if ($tokens[$i]->isGivenKind([ T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, T_REQUIRE_ONCE, CT::T_DYNAMIC_VAR_BRACE_OPEN, T_EVAL, ])) { return true; } if ($tokens[$i]->equals('$')) { $nextIndex = $tokens->getNextMeaningfulToken($i); if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { return true; } } } return false; } } fixSpace2($tokens, $index); } public function getDefinition() { return new FixerDefinition( 'In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.', [ new CodeSample( " false] ), new CodeSample( " true] ), new CodeSample( " 'ensure_fully_multiline'] ), new CodeSample( " 'ensure_single_line'] ), new CodeSample( " 'ensure_fully_multiline', 'keep_multiple_spaces_after_comma' => true, ] ), new CodeSample( " 'ensure_fully_multiline', 'keep_multiple_spaces_after_comma' => false, ] ), ] ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound('('); } public function configure(array $configuration = null) { parent::configure($configuration); if ($this->configuration['ensure_fully_multiline'] && 'ignore' === $this->configuration['on_multiline']) { $this->configuration['on_multiline'] = 'ensure_fully_multiline'; } } public function getPriority() { return -2; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index > 0; --$index) { $token = $tokens[$index]; if (!$token->equals('(')) { continue; } $meaningfulTokenBeforeParenthesis = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ( $meaningfulTokenBeforeParenthesis->isKeyword() && !$meaningfulTokenBeforeParenthesis->isGivenKind([T_LIST, T_FUNCTION]) ) { continue; } $isMultiline = $this->fixFunction($tokens, $index); if ( $isMultiline && 'ensure_fully_multiline' === $this->configuration['on_multiline'] && !$meaningfulTokenBeforeParenthesis->isGivenKind(T_LIST) ) { $this->ensureFunctionFullyMultiline($tokens, $index); } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('keep_multiple_spaces_after_comma', 'Whether keep multiple spaces after comma.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder( 'ensure_fully_multiline', 'ensure every argument of a multiline argument list is on its own line' )) ->setAllowedTypes(['bool']) ->setDefault(false) ->setDeprecationMessage('Use option `on_multiline` instead.') ->getOption(), (new FixerOptionBuilder( 'on_multiline', 'Defines how to handle function arguments lists that contain newlines.' )) ->setAllowedValues(['ignore', 'ensure_single_line', 'ensure_fully_multiline']) ->setDefault('ignore') ->getOption(), ]); } private function fixFunction(Tokens $tokens, $startFunctionIndex) { $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex); $isMultiline = false; $firstWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $startFunctionIndex, $endFunctionIndex); $lastWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $endFunctionIndex, $startFunctionIndex); foreach ([$firstWhitespaceIndex, $lastWhitespaceIndex] as $index) { if (null === $index || !Preg::match('/\R/', $tokens[$index]->getContent())) { continue; } if ('ensure_single_line' !== $this->configuration['on_multiline']) { $isMultiline = true; continue; } $newLinesRemoved = $this->ensureSingleLine($tokens, $index); if (!$newLinesRemoved) { $isMultiline = true; } } for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) { $token = $tokens[$index]; if ($token->equals(')')) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); continue; } if ($token->equals('}')) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); continue; } if ($token->equals(',')) { $this->fixSpace2($tokens, $index); if (!$isMultiline && $this->isNewline($tokens[$index + 1])) { $isMultiline = true; break; } } } return $isMultiline; } private function findWhitespaceIndexAfterParenthesis(Tokens $tokens, $startParenthesisIndex, $endParenthesisIndex) { $direction = $endParenthesisIndex > $startParenthesisIndex ? 1 : -1; $startIndex = $startParenthesisIndex + $direction; $endIndex = $endParenthesisIndex - $direction; for ($index = $startIndex; $index !== $endIndex; $index += $direction) { $token = $tokens[$index]; if ($token->isWhitespace()) { return $index; } if (!$token->isComment()) { break; } } return null; } private function ensureSingleLine(Tokens $tokens, $index) { $previousToken = $tokens[$index - 1]; if ($previousToken->isComment() && 0 !== strpos($previousToken->getContent(), '/*')) { return false; } $content = Preg::replace('/\R[ \t]*/', '', $tokens[$index]->getContent()); if ('' !== $content) { $tokens[$index] = new Token([T_WHITESPACE, $content]); } else { $tokens->clearAt($index); } return true; } private function ensureFunctionFullyMultiline(Tokens $tokens, $startFunctionIndex) { $searchIndex = $startFunctionIndex; do { $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind( $searchIndex, [[T_WHITESPACE]] ); $searchIndex = $prevWhitespaceTokenIndex; } while (null !== $prevWhitespaceTokenIndex && false === strpos($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n") ); if (null === $prevWhitespaceTokenIndex) { $existingIndentation = ''; } else { $existingIndentation = $tokens[$prevWhitespaceTokenIndex]->getContent(); $lastLineIndex = strrpos($existingIndentation, "\n"); $existingIndentation = false === $lastLineIndex ? $existingIndentation : substr($existingIndentation, $lastLineIndex + 1) ; } $indentation = $existingIndentation.$this->whitespacesConfig->getIndent(); $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex); if (!$this->isNewline($tokens[$endFunctionIndex - 1])) { $tokens->ensureWhitespaceAtIndex( $endFunctionIndex, 0, $this->whitespacesConfig->getLineEnding().$existingIndentation ); ++$endFunctionIndex; } for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) { $token = $tokens[$index]; if ($token->equals(')')) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); continue; } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); continue; } if ($token->equals('}')) { $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); continue; } if ($token->equals(',')) { $this->fixNewline($tokens, $index, $indentation); } } $this->fixNewline($tokens, $startFunctionIndex, $indentation, false); } private function fixNewline(Tokens $tokens, $index, $indentation, $override = true) { if ($tokens[$index + 1]->isComment()) { return; } if ($tokens[$index + 2]->isComment()) { $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index + 2); if (!$this->isNewline($tokens[$nextMeaningfulTokenIndex - 1])) { $tokens->ensureWhitespaceAtIndex($nextMeaningfulTokenIndex, 0, $this->whitespacesConfig->getLineEnding().$indentation); } return; } $tokens->ensureWhitespaceAtIndex($index + 1, 0, $this->whitespacesConfig->getLineEnding().$indentation); } private function fixSpace2(Tokens $tokens, $index) { if ($tokens[$index - 1]->isWhitespace()) { $prevIndex = $tokens->getPrevNonWhitespace($index - 1); if (!$tokens[$prevIndex]->equalsAny([',', [T_END_HEREDOC]]) && !$tokens[$prevIndex]->isComment()) { $tokens->clearAt($index - 1); } } $nextIndex = $index + 1; $nextToken = $tokens[$nextIndex]; if ($nextToken->isWhitespace()) { $newContent = $nextToken->getContent(); if ('ensure_single_line' === $this->configuration['on_multiline']) { $newContent = Preg::replace('/\R/', '', $newContent); } if ( (!$this->configuration['keep_multiple_spaces_after_comma'] || Preg::match('/\R/', $newContent)) && !$this->isCommentLastLineToken($tokens, $index + 2) ) { $newContent = ltrim($newContent, " \t"); } $tokens[$nextIndex] = new Token([T_WHITESPACE, '' === $newContent ? ' ' : $newContent]); return; } if (!$this->isCommentLastLineToken($tokens, $index + 1)) { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); } } private function isCommentLastLineToken(Tokens $tokens, $index) { if (!$tokens[$index]->isComment() || !$tokens[$index + 1]->isWhitespace()) { return false; } $content = $tokens[$index + 1]->getContent(); return $content !== ltrim($content, "\r\n"); } private function isNewline(Token $token) { return $token->isWhitespace() && false !== strpos($token->getContent(), "\n"); } } 'none'] ), new VersionSpecificCodeSample( " 'one'] ), ], 'Rule is applied only in a PHP 7+ environment.' ); } public function isCandidate(Tokens $tokens) { return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(CT::T_TYPE_COLON); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $oneSpaceBefore = 'one' === $this->configuration['space_before']; for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { if (!$tokens[$index]->isGivenKind(CT::T_TYPE_COLON)) { continue; } $previousIndex = $index - 1; $previousToken = $tokens[$previousIndex]; if ($previousToken->isWhitespace()) { if (!$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { if ($oneSpaceBefore) { $tokens[$previousIndex] = new Token([T_WHITESPACE, ' ']); } else { $tokens->clearAt($previousIndex); } } } elseif ($oneSpaceBefore) { $tokenWasAdded = $tokens->ensureWhitespaceAtIndex($index, 0, ' '); if ($tokenWasAdded) { ++$limit; } ++$index; } ++$index; $tokenWasAdded = $tokens->ensureWhitespaceAtIndex($index, 0, ' '); if ($tokenWasAdded) { ++$limit; } } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('space_before', 'Spacing to apply before colon.')) ->setAllowedValues(['one', 'none']) ->setDefault('none') ->getOption(), ]); } } isAnyTokenKindsFound(array_merge($this->getFunctionyTokenKinds(), [T_STRING])); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $functionyTokens = $this->getFunctionyTokenKinds(); $languageConstructionTokens = $this->getLanguageConstructionTokenKinds(); $braceTypes = $this->getBraceAfterVariableKinds(); foreach ($tokens as $index => $token) { if (!$token->equals('(')) { continue; } $lastTokenIndex = $tokens->getPrevNonWhitespace($index); $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $nextNonWhiteSpace = $tokens->getNextMeaningfulToken($endParenthesisIndex); if ( null !== $nextNonWhiteSpace && $tokens[$nextNonWhiteSpace]->equals('?') && $tokens[$lastTokenIndex]->isGivenKind($languageConstructionTokens) ) { continue; } if ($tokens[$lastTokenIndex]->isGivenKind($functionyTokens)) { $this->fixFunctionCall($tokens, $index); } elseif ($tokens[$lastTokenIndex]->isGivenKind(T_STRING)) { $possibleDefinitionIndex = $tokens->getPrevMeaningfulToken($lastTokenIndex); if (!$tokens[$possibleDefinitionIndex]->isGivenKind(T_FUNCTION)) { $this->fixFunctionCall($tokens, $index); } } elseif ($tokens[$lastTokenIndex]->equalsAny($braceTypes)) { $block = Tokens::detectBlockType($tokens[$lastTokenIndex]); if ( Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type'] || Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type'] || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type'] || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type'] ) { $this->fixFunctionCall($tokens, $index); } } } } private function fixFunctionCall(Tokens $tokens, $index) { if ($tokens[$index - 1]->isWhitespace()) { $tokens->clearAt($index - 1); } } private function getBraceAfterVariableKinds() { static $tokens = [ ')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; return $tokens; } private function getFunctionyTokenKinds() { static $tokens = [ T_ARRAY, T_ECHO, T_EMPTY, T_EVAL, T_EXIT, T_INCLUDE, T_INCLUDE_ONCE, T_ISSET, T_LIST, T_PRINT, T_REQUIRE, T_REQUIRE_ONCE, T_UNSET, T_VARIABLE, ]; return $tokens; } private function getLanguageConstructionTokenKinds() { static $languageConstructionTokens = [ T_ECHO, T_PRINT, T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, T_REQUIRE_ONCE, ]; return $languageConstructionTokens; } } isTokenKindFound(T_FUNCTION); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($i = 0, $l = $tokens->count(); $i < $l; ++$i) { if (!$tokens[$i]->isGivenKind(T_FUNCTION)) { continue; } $startIndex = $tokens->getNextTokenOfKind($i, ['(']); $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); $this->fixFunctionDefinition($tokens, $startIndex, $i); } } private function fixFunctionDefinition(Tokens $tokens, $startIndex, $endIndex) { $lastArgumentIndex = $this->getLastNonDefaultArgumentIndex($tokens, $startIndex, $endIndex); if (!$lastArgumentIndex) { return; } for ($i = $lastArgumentIndex; $i > $startIndex; --$i) { $token = $tokens[$i]; if ($token->isGivenKind(T_VARIABLE)) { $lastArgumentIndex = $i; continue; } if (!$token->equals('=') || $this->isNonNullableTypehintedNullableVariable($tokens, $i)) { continue; } $endIndex = $tokens->getPrevTokenOfKind($lastArgumentIndex, [',']); $endIndex = $tokens->getPrevMeaningfulToken($endIndex); $this->removeDefaultArgument($tokens, $i, $endIndex); } } private function getLastNonDefaultArgumentIndex(Tokens $tokens, $startIndex, $endIndex) { for ($i = $endIndex - 1; $i > $startIndex; --$i) { $token = $tokens[$i]; if ($token->equals('=')) { $i = $tokens->getPrevMeaningfulToken($i); continue; } if ($token->isGivenKind(T_VARIABLE) && !$this->isEllipsis($tokens, $i)) { return $i; } } } private function isEllipsis(Tokens $tokens, $variableIndex) { return $tokens[$tokens->getPrevMeaningfulToken($variableIndex)]->isGivenKind(T_ELLIPSIS); } private function removeDefaultArgument(Tokens $tokens, $startIndex, $endIndex) { for ($i = $startIndex; $i <= $endIndex;) { $tokens->clearTokenAndMergeSurroundingWhitespace($i); $this->clearWhitespacesBeforeIndex($tokens, $i); $i = $tokens->getNextMeaningfulToken($i); } } private function isNonNullableTypehintedNullableVariable(Tokens $tokens, $index) { $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if (!$nextToken->equals([T_STRING, 'null'], false)) { return false; } $variableIndex = $tokens->getPrevMeaningfulToken($index); $searchTokens = [',', '(', [T_STRING], [CT::T_ARRAY_TYPEHINT], [T_CALLABLE]]; $typehintKinds = [T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE]; $prevIndex = $tokens->getPrevTokenOfKind($variableIndex, $searchTokens); if (!$tokens[$prevIndex]->isGivenKind($typehintKinds)) { return false; } return !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(CT::T_NULLABLE_TYPE); } private function clearWhitespacesBeforeIndex(Tokens $tokens, $index) { $prevIndex = $tokens->getNonEmptySibling($index, -1); if (!$tokens[$prevIndex]->isWhitespace()) { return; } $prevNonWhiteIndex = $tokens->getPrevNonWhitespace($prevIndex); if (null === $prevNonWhiteIndex || !$tokens[$prevNonWhiteIndex]->isComment()) { $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); } } } 70100, 'iterable' => 70100, 'object' => 70200, ]; private $scalarTypes = [ 'bool' => true, 'float' => true, 'int' => true, 'string' => true, ]; private $skippedTypes = [ 'mixed' => true, 'resource' => true, 'null' => true, ]; private $classRegex = '/^\\\\?[a-zA-Z_\\x7f-\\xff](?:\\\\?[a-zA-Z0-9_\\x7f-\\xff]+)*(?\[\])*$/'; public function getDefinition() { return new FixerDefinition( 'EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', [ new VersionSpecificCodeSample( '= 70000 && $tokens->isTokenKindFound(T_FUNCTION); } public function getPriority() { return 8; } public function isRisky() { return true; } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('scalar_types', 'Fix also scalar types; may have unexpected behaviour due to PHP bad type coercion system.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; 0 < $index; --$index) { if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { continue; } $funcName = $tokens->getNextMeaningfulToken($index); if ($tokens[$funcName]->equalsAny($this->blacklistFuncNames, false)) { continue; } $returnTypeAnnotation = $this->findReturnAnnotations($tokens, $index); if (1 !== \count($returnTypeAnnotation)) { continue; } $returnTypeAnnotation = current($returnTypeAnnotation); $types = array_values($returnTypeAnnotation->getTypes()); $typesCount = \count($types); if (1 > $typesCount || 2 < $typesCount) { continue; } $isNullable = false; $returnType = current($types); if (2 === $typesCount) { $null = $types[0]; $returnType = $types[1]; if ('null' !== $null) { $null = $types[1]; $returnType = $types[0]; } if ('null' !== $null) { continue; } $isNullable = true; if (\PHP_VERSION_ID < 70100) { continue; } if ('void' === $returnType) { continue; } } if ('static' === $returnType) { $returnType = 'self'; } if (isset($this->skippedTypes[$returnType])) { continue; } if (isset($this->versionSpecificTypes[$returnType]) && \PHP_VERSION_ID < $this->versionSpecificTypes[$returnType]) { continue; } if (isset($this->scalarTypes[$returnType]) && false === $this->configuration['scalar_types']) { continue; } if (1 !== Preg::match($this->classRegex, $returnType, $matches)) { continue; } if (isset($matches['array'])) { $returnType = 'array'; } $startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); if ($this->hasReturnTypeHint($tokens, $startIndex)) { continue; } $this->fixFunctionDefinition($tokens, $startIndex, $isNullable, $returnType); } } private function hasReturnTypeHint(Tokens $tokens, $index) { $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); $nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex); return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON); } private function fixFunctionDefinition(Tokens $tokens, $index, $isNullable, $returnType) { static $specialTypes = [ 'array' => [CT::T_ARRAY_TYPEHINT, 'array'], 'callable' => [T_CALLABLE, 'callable'], ]; $newTokens = [ new Token([CT::T_TYPE_COLON, ':']), new Token([T_WHITESPACE, ' ']), ]; if (true === $isNullable) { $newTokens[] = new Token([CT::T_NULLABLE_TYPE, '?']); } if (isset($specialTypes[$returnType])) { $newTokens[] = new Token($specialTypes[$returnType]); } else { foreach (explode('\\', $returnType) as $nsIndex => $value) { if (0 === $nsIndex && '' === $value) { continue; } if (0 < $nsIndex) { $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); } $newTokens[] = new Token([T_STRING, $value]); } } $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); $tokens->insertAt($endFuncIndex + 1, $newTokens); } private function findReturnAnnotations(Tokens $tokens, $index) { do { $index = $tokens->getPrevNonWhitespace($index); } while ($tokens[$index]->isGivenKind([ T_COMMENT, T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, ])); if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { return []; } $doc = new DocBlock($tokens[$index]->getContent()); return $doc->getAnnotationsOfType('return'); } } = 7.1.', [ new VersionSpecificCodeSample( "= 70100 && $tokens->isTokenKindFound(T_FUNCTION); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $blacklistFuncNames = [ [T_STRING, '__construct'], [T_STRING, '__destruct'], [T_STRING, '__clone'], ]; for ($index = $tokens->count() - 1; 0 <= $index; --$index) { if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { continue; } $funcName = $tokens->getNextMeaningfulToken($index); if ($tokens[$funcName]->equalsAny($blacklistFuncNames, false)) { continue; } $startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); if ($this->hasReturnTypeHint($tokens, $startIndex)) { continue; } if ($tokens[$startIndex]->equals(';')) { if ($this->hasVoidReturnAnnotation($tokens, $index)) { $this->fixFunctionDefinition($tokens, $startIndex); } continue; } if ($this->hasReturnAnnotation($tokens, $index)) { continue; } $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); if ($this->hasVoidReturn($tokens, $startIndex, $endIndex)) { $this->fixFunctionDefinition($tokens, $startIndex); } } } private function hasReturnAnnotation(Tokens $tokens, $index) { foreach ($this->findReturnAnnotations($tokens, $index) as $return) { if (['void'] !== $return->getTypes()) { return true; } } return false; } private function hasVoidReturnAnnotation(Tokens $tokens, $index) { foreach ($this->findReturnAnnotations($tokens, $index) as $return) { if (['void'] === $return->getTypes()) { return true; } } return false; } private function hasReturnTypeHint(Tokens $tokens, $index) { $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); $nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex); return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON); } private function hasVoidReturn(Tokens $tokens, $startIndex, $endIndex) { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($i = $startIndex; $i < $endIndex; ++$i) { if ( ($tokens[$i]->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($i)) || ($tokens[$i]->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($i)) ) { $i = $tokens->getNextTokenOfKind($i, ['{']); $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); continue; } if ($tokens[$i]->isGivenKind(T_YIELD)) { return false; } if (!$tokens[$i]->isGivenKind(T_RETURN)) { continue; } $i = $tokens->getNextMeaningfulToken($i); if (!$tokens[$i]->equals(';')) { return false; } } return true; } private function fixFunctionDefinition(Tokens $tokens, $index) { $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); $tokens->insertAt($endFuncIndex + 1, [ new Token([CT::T_TYPE_COLON, ':']), new Token([T_WHITESPACE, ' ']), new Token([T_STRING, 'void']), ]); } private function findReturnAnnotations(Tokens $tokens, $index) { do { $index = $tokens->getPrevNonWhitespace($index); } while ($tokens[$index]->isGivenKind([ T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, ])); if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { return []; } $doc = new DocBlock($tokens[$index]->getContent()); return $doc->getAnnotationsOfType('return'); } } functionFilter = $this->getFunctionFilter(); } public function getDefinition() { return new FixerDefinition( 'Add leading `\` before function invocation to speed up resolving.', [ new CodeSample( ' [ 'json_encode', ], ] ), new CodeSample( ' 'all'] ), new CodeSample( ' 'namespaced'] ), new CodeSample( ' ['myGlobalFunction']] ), new CodeSample( ' ['@all']] ), new CodeSample( ' ['@internal']] ), new CodeSample( ' ['@compiler_optimized']] ), ], null, 'Risky when any of the functions are overridden.' ); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_STRING); } public function isRisky() { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { if ('all' === $this->configuration['scope']) { $this->fixFunctionCalls($tokens, $this->functionFilter, 0, \count($tokens) - 1, false); return; } $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); foreach (array_reverse($namespaces) as $namespace) { $this->fixFunctionCalls($tokens, $this->functionFilter, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), '' === $namespace->getFullName()); } } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('exclude', 'List of functions to ignore.')) ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $value) { foreach ($value as $functionName) { if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { throw new InvalidOptionsException(sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', \is_object($functionName) ? \get_class($functionName) : \gettype($functionName) )); } } return true; }]) ->setDefault([]) ->getOption(), (new FixerOptionBuilder('include', 'List of function names or sets to fix. Defined sets are `@internal` (all native functions), `@all` (all global functions) and `@compiler_optimized` (functions that are specially optimized by Zend).')) ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $value) { foreach ($value as $functionName) { if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { throw new InvalidOptionsException(sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', \is_object($functionName) ? \get_class($functionName) : \gettype($functionName) )); } $sets = [ self::SET_ALL, self::SET_INTERNAL, self::SET_COMPILER_OPTIMIZED, ]; if ('@' === $functionName[0] && !\in_array($functionName, $sets, true)) { throw new InvalidOptionsException(sprintf('Unknown set "%s", known sets are "%s".', $functionName, implode('", "', $sets))); } } return true; }]) ->setDefault([self::SET_INTERNAL]) ->getOption(), (new FixerOptionBuilder('scope', 'Only fix function calls that are made within a namespace or fix all.')) ->setAllowedValues(['all', 'namespaced']) ->setDefault('all') ->getOption(), (new FixerOptionBuilder('strict', 'Whether leading `\` of function call not meant to have it should be removed.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } private function fixFunctionCalls(Tokens $tokens, callable $functionFilter, $start, $end, $tryToRemove) { $functionsAnalyzer = new FunctionsAnalyzer(); $insertAtIndexes = []; for ($index = $start; $index < $end; ++$index) { if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { continue; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$functionFilter($tokens[$index]->getContent()) || $tryToRemove) { if (!$this->configuration['strict']) { continue; } if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); } continue; } if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { continue; } $insertAtIndexes[] = $index; } foreach (array_reverse($insertAtIndexes) as $index) { $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); } } private function getFunctionFilter() { $exclude = $this->normalizeFunctionNames($this->configuration['exclude']); if (\in_array(self::SET_ALL, $this->configuration['include'], true)) { if (\count($exclude) > 0) { return static function ($functionName) use ($exclude) { return !isset($exclude[strtolower($functionName)]); }; } return static function () { return true; }; } $include = []; if (\in_array(self::SET_INTERNAL, $this->configuration['include'], true)) { $include = $this->getAllInternalFunctionsNormalized(); } elseif (\in_array(self::SET_COMPILER_OPTIMIZED, $this->configuration['include'], true)) { $include = $this->getAllCompilerOptimizedFunctionsNormalized(); } foreach ($this->configuration['include'] as $additional) { if ('@' !== $additional[0]) { $include[strtolower($additional)] = true; } } if (\count($exclude) > 0) { return static function ($functionName) use ($include, $exclude) { return isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]); }; } return static function ($functionName) use ($include) { return isset($include[strtolower($functionName)]); }; } private function getAllCompilerOptimizedFunctionsNormalized() { return $this->normalizeFunctionNames([ 'array_key_exists', 'array_slice', 'assert', 'boolval', 'call_user_func', 'call_user_func_array', 'chr', 'count', 'defined', 'doubleval', 'floatval', 'func_get_args', 'func_num_args', 'get_called_class', 'get_class', 'gettype', 'in_array', 'intval', 'is_array', 'is_bool', 'is_double', 'is_float', 'is_int', 'is_integer', 'is_long', 'is_null', 'is_object', 'is_real', 'is_resource', 'is_string', 'ord', 'strlen', 'strval', 'constant', 'define', 'dirname', 'extension_loaded', 'function_exists', 'is_callable', ]); } private function getAllInternalFunctionsNormalized() { return $this->normalizeFunctionNames(get_defined_functions()['internal']); } private function normalizeFunctionNames(array $functionNames) { foreach ($functionNames as $index => $functionName) { $functionNames[strtolower($functionName)] = true; unset($functionNames[$index]); } return $functionNames; } } isTokenKindFound(T_STRING); } public function getPriority() { return -1; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $functionsAnalyzer = new FunctionsAnalyzer(); foreach ($tokens as $index => $token) { if (!$tokens[$index]->equals([T_STRING, 'implode'], false)) { continue; } if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { continue; } $argumentsIndices = $this->getArgumentIndices($tokens, $index); if (1 === \count($argumentsIndices)) { $firstArgumentIndex = key($argumentsIndices); $tokens->insertAt($firstArgumentIndex, [ new Token([T_CONSTANT_ENCAPSED_STRING, "''"]), new Token(','), new Token([T_WHITESPACE, ' ']), ]); continue; } if (2 === \count($argumentsIndices)) { list($firstArgumentIndex, $secondArgumentIndex) = array_keys($argumentsIndices); if ($tokens[$firstArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { continue; } if (!$tokens[$secondArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { continue; } $firstArgumentEndIndex = $argumentsIndices[key($argumentsIndices)]; $newSecondArgumentTokens = []; for ($i = key($argumentsIndices); $i <= $firstArgumentEndIndex; ++$i) { $newSecondArgumentTokens[] = clone $tokens[$i]; $tokens->clearAt($i); } $tokens->insertAt($firstArgumentIndex, clone $tokens[$secondArgumentIndex]); ++$secondArgumentIndex; $tokens->clearAt($secondArgumentIndex); $tokens->insertAt($secondArgumentIndex, $newSecondArgumentTokens); } } } private function getArgumentIndices(Tokens $tokens, $functionNameIndex) { $argumentsAnalyzer = new ArgumentsAnalyzer(); $openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']); $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); $indices = []; foreach ($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis) as $startIndexCandidate => $endIndex) { $indices[$tokens->getNextMeaningfulToken($startIndexCandidate - 1)] = $tokens->getPrevMeaningfulToken($endIndex + 1); } return $indices; } } setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), ]); } protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex) { $argumentFlagIndex = null; for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) { if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { continue; } if (null !== $argumentFlagIndex) { return; } $argumentFlagIndex = $i; } if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { return; } $content = $tokens[$argumentFlagIndex]->getContent(); $contentQuote = $content[0]; if ('b' === $contentQuote || 'B' === $contentQuote) { $binPrefix = $contentQuote; $contentQuote = $content[1]; $mode = substr($content, 2, -1); } else { $binPrefix = ''; $mode = substr($content, 1, -1); } if (false === $this->isValidModeString($mode)) { return; } $mode = str_replace('t', '', $mode); if ($this->configuration['b_mode']) { if (false === strpos($mode, 'b')) { $mode .= 'b'; } } else { $mode = str_replace('b', '', $mode); } $newContent = $binPrefix.$contentQuote.$mode.$contentQuote; if ($content !== $newContent) { $tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]); } } } isTokenKindFound(T_FUNCTION); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_FUNCTION)) { continue; } $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', [T_CLOSE_TAG]]); if (!$tokens[$startParenthesisIndex]->equals('(')) { continue; } $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); for ($iter = $endParenthesisIndex - 1; $iter > $startParenthesisIndex; --$iter) { if (!$tokens[$iter]->isGivenKind(T_VARIABLE)) { continue; } $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($iter); if ($tokens[$prevNonWhitespaceIndex]->isGivenKind(T_ELLIPSIS)) { $iter = $prevNonWhitespaceIndex; } $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($iter); if ($tokens[$prevNonWhitespaceIndex]->equals('&')) { $iter = $prevNonWhitespaceIndex; } if (!$tokens[$iter - 1]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], '(', ','])) { $tokens->insertAt($iter, new Token([T_WHITESPACE, ' '])); } } } } } isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { continue; } if (null !== $argumentFlagIndex) { return; } $argumentFlagIndex = $i; } if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { return; } $content = $tokens[$argumentFlagIndex]->getContent(); $contentQuote = $content[0]; if ('b' === $contentQuote || 'B' === $contentQuote) { $binPrefix = $contentQuote; $contentQuote = $content[1]; $mode = substr($content, 2, -1); } else { $binPrefix = ''; $mode = substr($content, 1, -1); } $modeLength = \strlen($mode); if ($modeLength < 2) { return; } if (false === $this->isValidModeString($mode)) { return; } $split = $this->sortFlags(Preg::split('#([^\+]\+?)#', $mode, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)); $newContent = $binPrefix.$contentQuote.implode('', $split).$contentQuote; if ($content !== $newContent) { $tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]); } } private function sortFlags(array $flags) { usort( $flags, static function ($flag1, $flag2) { if ($flag1 === $flag2) { return 0; } if ('b' === $flag1) { return 1; } if ('b' === $flag2) { return -1; } if ('t' === $flag1) { return 1; } if ('t' === $flag2) { return -1; } return $flag1 < $flag2 ? -1 : 1; } ); return $flags; } } stdinContent) { $this->stdinContent = $this->readRaw($filePath); } return $this->stdinContent; } return $this->readRaw($filePath); } private function readRaw($realPath) { $content = @file_get_contents($realPath); if (false === $content) { $error = error_get_last(); throw new \RuntimeException(sprintf( 'Failed to read content from "%s".%s', $realPath, $error ? ' '.$error['message'] : '' )); } return $content; } } createProxyFixers()) as $proxyFixer) { $this->proxyFixers[$proxyFixer->getName()] = $proxyFixer; } parent::__construct(); } public function isCandidate(Tokens $tokens) { foreach ($this->proxyFixers as $fixer) { if ($fixer->isCandidate($tokens)) { return true; } } return false; } public function isRisky() { foreach ($this->proxyFixers as $fixer) { if ($fixer->isRisky()) { return true; } } return false; } public function getPriority() { if (\count($this->proxyFixers) > 1) { throw new \LogicException('You need to override this method to provide the priority of combined fixers.'); } return reset($this->proxyFixers)->getPriority(); } public function supports(\SplFileInfo $file) { foreach ($this->proxyFixers as $fixer) { if ($fixer->supports($file)) { return true; } } return false; } public function setWhitespacesConfig(WhitespacesFixerConfig $config) { parent::setWhitespacesConfig($config); foreach ($this->proxyFixers as $fixer) { if ($fixer instanceof WhitespacesAwareFixerInterface) { $fixer->setWhitespacesConfig($config); } } } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($this->proxyFixers as $fixer) { $fixer->fix($file, $tokens); } } abstract protected function createProxyFixers(); } equals('?')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON]])) { $tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']); } } } equals('&') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_FUNCTION) ) { $tokens[$index] = new Token([CT::T_RETURN_REF, '&']); } } } isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) { $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); return; } if (!$token->isGivenKind([T_CLASS, T_TRAIT])) { return; } if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) { return; } $index = $tokens->getNextTokenOfKind($index, ['{']); $innerLimit = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); while ($index < $innerLimit) { $token = $tokens[++$index]; if (!$token->isGivenKind(T_USE)) { continue; } if ($this->isUseForLambda($tokens, $index)) { $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); } else { $tokens[$index] = new Token([CT::T_USE_TRAIT, $token->getContent()]); } } } private function isUseForLambda(Tokens $tokens, $index) { $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; return $nextToken->equals('('); } } isGivenKind(T_NAMESPACE)) { return; } $nextIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextIndex]; if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { $tokens[$index] = new Token([CT::T_NAMESPACE_OPERATOR, $token->getContent()]); } } } equals(':')) { return; } $endIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$endIndex]->equals(')')) { return; } $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); $prevIndex = $tokens->getPrevMeaningfulToken($startIndex); $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind(T_STRING)) { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); $prevToken = $tokens[$prevIndex]; } if ($prevToken->isGivenKind([T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA])) { $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']); } } } isGivenKind(T_ARRAY)) { return; } $nextIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextIndex]; if (!$nextToken->equals('(')) { $tokens[$index] = new Token([CT::T_ARRAY_TYPEHINT, $token->getContent()]); } } } isComment()) { return; } $content = $token->getContent(); $trimmedContent = rtrim($content); if ($content === $trimmedContent) { return; } $whitespaces = substr($content, \strlen($trimmedContent)); $tokens[$index] = new Token([$token->getId(), $trimmedContent]); if (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) { $tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaces.$tokens[$index + 1]->getContent()]); } else { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, $whitespaces])); } } } transformIntoCurlyCloseBrace($tokens, $token, $index); $this->transformIntoDollarCloseBrace($tokens, $token, $index); $this->transformIntoDynamicPropBraces($tokens, $token, $index); $this->transformIntoDynamicVarBraces($tokens, $token, $index); $this->transformIntoCurlyIndexBraces($tokens, $token, $index); if (\PHP_VERSION_ID >= 70000) { $this->transformIntoGroupUseBraces($tokens, $token, $index); } } private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, $index) { if (!$token->isGivenKind(T_CURLY_OPEN)) { return; } $level = 1; $nestIndex = $index; while (0 < $level) { ++$nestIndex; if ($tokens[$nestIndex]->equals('{')) { ++$level; continue; } if ($tokens[$nestIndex]->equals('}')) { --$level; } } $tokens[$nestIndex] = new Token([CT::T_CURLY_CLOSE, '}']); } private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, $index) { if ($token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { $nextIndex = $tokens->getNextTokenOfKind($index, ['}']); $tokens[$nextIndex] = new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']); } } private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, $index) { if (!$token->isGivenKind(T_OBJECT_OPERATOR)) { return; } if (!$tokens[$index + 1]->equals('{')) { return; } $openIndex = $index + 1; $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openIndex); $tokens[$openIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}']); } private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, $index) { if (!$token->equals('$')) { return; } $openIndex = $tokens->getNextMeaningfulToken($index); if (null === $openIndex) { return; } $openToken = $tokens[$openIndex]; if (!$openToken->equals('{')) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openIndex); $tokens[$openIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}']); } private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, $index) { if (!$token->equals('{')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->equalsAny([ [T_STRING], [T_VARIABLE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], ']', ')', ])) { return; } if ( $tokens[$prevIndex]->isGivenKind(T_STRING) && !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_OBJECT_OPERATOR) ) { return; } if ( $tokens[$prevIndex]->equals(')') && !$tokens[$tokens->getPrevMeaningfulToken( $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex) )]->isGivenKind(T_ARRAY) ) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); $tokens[$index] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}']); } private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, $index) { if (!$token->equals('{')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); $tokens[$index] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); } } isArrayDestructing($tokens, $index)) { $this->transformIntoDestructuringSquareBrace($tokens, $index); return; } if ($this->isShortArray($tokens, $index)) { $this->transformIntoArraySquareBrace($tokens, $index); } } private function transformIntoArraySquareBrace(Tokens $tokens, $index) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); $tokens[$index] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']); $tokens[$endIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']); } private function transformIntoDestructuringSquareBrace(Tokens $tokens, $index) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); $tokens[$endIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); $previousMeaningfulIndex = $index; $index = $tokens->getNextMeaningfulToken($index); while ($index < $endIndex) { if ($tokens[$index]->equals('[') && $tokens[$previousMeaningfulIndex]->equalsAny([[CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], ','])) { $tokens[$tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index)] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); } $previousMeaningfulIndex = $index; $index = $tokens->getNextMeaningfulToken($index); } } private function isShortArray(Tokens $tokens, $index) { if (!$tokens[$index]->equals('[')) { return false; } static $disallowedPrevTokens = [ ')', ']', '}', '"', [T_CONSTANT_ENCAPSED_STRING], [T_STRING], [T_STRING_VARNAME], [T_VARIABLE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->equalsAny($disallowedPrevTokens)) { return false; } $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if ($nextToken->equals(']')) { return true; } return !$this->isArrayDestructing($tokens, $index); } private function isArrayDestructing(Tokens $tokens, $index) { if (\PHP_VERSION_ID < 70100 || !$tokens[$index]->equals('[')) { return false; } static $disallowedPrevTokens = [ ')', ']', '"', [T_CONSTANT_ENCAPSED_STRING], [T_STRING], [T_STRING_VARNAME], [T_VARIABLE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->equalsAny($disallowedPrevTokens)) { return false; } $type = Tokens::detectBlockType($tokens[$index]); $end = $tokens->findBlockEnd($type['type'], $index); $nextToken = $tokens[$tokens->getNextMeaningfulToken($end)]; return $nextToken->equals('='); } } equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->equals([T_NEW])) { return; } if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([ ']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [T_ARRAY], [T_CLASS], [T_ELSEIF], [T_FOR], [T_FOREACH], [T_IF], [T_STATIC], [T_STRING], [T_SWITCH], [T_VARIABLE], [T_WHILE], ])) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $tokens[$index] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '(']); $tokens[$closeIndex] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')']); } } equals('|')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if (!$prevToken->isGivenKind(T_STRING)) { return; } do { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if (null === $prevIndex) { break; } $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind([T_NS_SEPARATOR, T_STRING])) { continue; } if ( $prevToken->isGivenKind(CT::T_TYPE_ALTERNATION) || ( $prevToken->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_CATCH) ) ) { $tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']); } break; } while (true); } } equalsAny([ [T_CLASS, 'class'], [T_STRING, 'class'], ], false)) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind(T_DOUBLE_COLON)) { $tokens[$index] = new Token([CT::T_CLASS_CONSTANT, $token->getContent()]); } } } isGivenKind([T_CONST, T_FUNCTION])) { return; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind(T_USE)) { $tokens[$index] = new Token([ $token->isGivenKind(T_FUNCTION) ? CT::T_FUNCTION_IMPORT : CT::T_CONST_IMPORT, $token->getContent(), ]); } } } getArguments($tokens, $openParenthesis, $closeParenthesis)); } public function getArguments(Tokens $tokens, $openParenthesis, $closeParenthesis) { $arguments = []; $firstSensibleToken = $tokens->getNextMeaningfulToken($openParenthesis); if ($tokens[$firstSensibleToken]->equals(')')) { return $arguments; } $paramContentIndex = $openParenthesis + 1; $argumentsStart = $paramContentIndex; for (; $paramContentIndex < $closeParenthesis; ++$paramContentIndex) { $token = $tokens[$paramContentIndex]; $blockDefinitionProbe = Tokens::detectBlockType($token); if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) { $paramContentIndex = $tokens->findBlockEnd($blockDefinitionProbe['type'], $paramContentIndex); continue; } if ($token->equals(',')) { if ($tokens->getNextMeaningfulToken($paramContentIndex) === $closeParenthesis) { break; } $arguments[$argumentsStart] = $paramContentIndex - 1; $argumentsStart = $paramContentIndex + 1; } } $arguments[$argumentsStart] = $paramContentIndex - 1; return $arguments; } public function getArgumentInfo(Tokens $tokens, $argumentStart, $argumentEnd) { $info = [ 'default' => null, 'name' => null, 'name_index' => null, 'type' => null, 'type_index_start' => null, 'type_index_end' => null, ]; $sawName = false; for ($index = $argumentStart; $index <= $argumentEnd; ++$index) { $token = $tokens[$index]; if ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS) || $token->equals('&')) { continue; } if ($token->isGivenKind(T_VARIABLE)) { $sawName = true; $info['name_index'] = $index; $info['name'] = $token->getContent(); continue; } if ($token->equals('=')) { continue; } if ($sawName) { $info['default'] .= $token->getContent(); } else { $info['type_index_start'] = ($info['type_index_start'] > 0) ? $info['type_index_start'] : $index; $info['type_index_end'] = $index; $info['type'] .= $token->getContent(); } } return new ArgumentAnalysis( $info['name'], $info['name_index'], $info['default'], $info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null ); } } fullName = $fullName; $this->shortName = $shortName; $this->isAliased = $isAliased; $this->startIndex = $startIndex; $this->endIndex = $endIndex; $this->type = $type; } public function getFullName() { return $this->fullName; } public function getShortName() { return $this->shortName; } public function isAliased() { return $this->isAliased; } public function getStartIndex() { return $this->startIndex; } public function getEndIndex() { return $this->endIndex; } public function isClass() { return self::TYPE_CLASS === $this->type; } public function isFunction() { return self::TYPE_FUNCTION === $this->type; } public function isConstant() { return self::TYPE_CONSTANT === $this->type; } } fullName = $fullName; $this->shortName = $shortName; $this->startIndex = $startIndex; $this->endIndex = $endIndex; $this->scopeStartIndex = $scopeStartIndex; $this->scopeEndIndex = $scopeEndIndex; } public function getFullName() { return $this->fullName; } public function getShortName() { return $this->shortName; } public function getStartIndex() { return $this->startIndex; } public function getEndIndex() { return $this->endIndex; } public function getScopeStartIndex() { return $this->scopeStartIndex; } public function getScopeEndIndex() { return $this->scopeEndIndex; } } name = $name; $this->nameIndex = $nameIndex; $this->default = $default ?: null; $this->typeAnalysis = $typeAnalysis ?: null; } public function getDefault() { return $this->default; } public function hasDefault() { return null !== $this->default; } public function getName() { return $this->name; } public function getNameIndex() { return $this->nameIndex; } public function getTypeAnalysis() { return $this->typeAnalysis; } public function hasTypeAnalysis() { return null !== $this->typeAnalysis; } } name = $name; $this->startIndex = $startIndex; $this->endIndex = $endIndex; } public function getName() { return $this->name; } public function getStartIndex() { return $this->startIndex; } public function getEndIndex() { return $this->endIndex; } public function isReservedType() { return \in_array($this->name, self::$reservedTypes, true); } } getImportUseIndexes(); return $this->getDeclarations($tokens, $useIndexes); } private function getDeclarations(Tokens $tokens, array $useIndexes) { $uses = []; foreach ($useIndexes as $index) { $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); $analysis = $this->parseDeclaration($tokens, $index, $endIndex); if ($analysis) { $uses[] = $analysis; } } return $uses; } private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) { $fullName = $shortName = ''; $aliased = false; $type = NamespaceUseAnalysis::TYPE_CLASS; for ($i = $startIndex; $i <= $endIndex; ++$i) { $token = $tokens[$i]; if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { return null; } if ($token->isGivenKind(CT::T_FUNCTION_IMPORT)) { $type = NamespaceUseAnalysis::TYPE_FUNCTION; } elseif ($token->isGivenKind(CT::T_CONST_IMPORT)) { $type = NamespaceUseAnalysis::TYPE_CONSTANT; } if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) { continue; } if ($token->isGivenKind(T_STRING)) { $shortName = $token->getContent(); if (!$aliased) { $fullName .= $shortName; } } elseif ($token->isGivenKind(T_NS_SEPARATOR)) { $fullName .= $token->getContent(); } elseif ($token->isGivenKind(T_AS)) { $aliased = true; } } return new NamespaceUseAnalysis( trim($fullName), $shortName, $aliased, $startIndex, $endIndex, $type ); } } isGivenKind([T_COMMENT, T_DOC_COMMENT])) { throw new \InvalidArgumentException('Given index must point to a comment.'); } $prevIndex = $tokens->getPrevMeaningfulToken($index); return $tokens[$prevIndex]->isGivenKind(T_OPEN_TAG) && null !== $tokens->getNextMeaningfulToken($index); } public function isBeforeStructuralElement(Tokens $tokens, $index) { $token = $tokens[$index]; if (!$token->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { throw new \InvalidArgumentException('Given index must point to a comment.'); } $nextIndex = $index; do { $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); } while (null !== $nextIndex && $tokens[$nextIndex]->equals('(')); if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { return false; } $nextToken = $tokens[$nextIndex]; if ($this->isStructuralElement($nextToken)) { return true; } if ($this->isValidControl($tokens, $token, $nextIndex)) { return true; } if ($this->isValidVariable($tokens, $nextIndex)) { return true; } if ($this->isValidLanguageConstruct($tokens, $token, $nextIndex)) { return true; } return false; } public function getCommentBlockIndices(Tokens $tokens, $index) { if (!$tokens[$index]->isGivenKind(T_COMMENT)) { throw new \InvalidArgumentException('Given index must point to a comment.'); } $commentType = $this->getCommentType($tokens[$index]->getContent()); $indices = [$index]; if (self::TYPE_SLASH_ASTERISK === $commentType) { return $indices; } $count = \count($tokens); ++$index; for (; $index < $count; ++$index) { if ($tokens[$index]->isComment()) { if ($commentType === $this->getCommentType($tokens[$index]->getContent())) { $indices[] = $index; continue; } break; } if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) { break; } } return $indices; } private function isStructuralElement(Token $token) { static $skip = [ T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR, T_FUNCTION, T_ABSTRACT, T_CONST, T_NAMESPACE, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE, T_FINAL, T_STATIC, ]; return $token->isClassy() || $token->isGivenKind($skip); } private function isValidControl(Tokens $tokens, Token $docsToken, $controlIndex) { static $controlStructures = [ T_FOR, T_FOREACH, T_IF, T_SWITCH, T_WHILE, ]; if (!$tokens[$controlIndex]->isGivenKind($controlStructures)) { return false; } $index = $tokens->getNextMeaningfulToken($controlIndex); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $docsContent = $docsToken->getContent(); for ($index = $index + 1; $index < $endIndex; ++$index) { $token = $tokens[$index]; if ( $token->isGivenKind(T_VARIABLE) && false !== strpos($docsContent, $token->getContent()) ) { return true; } } return false; } private function isValidLanguageConstruct(Tokens $tokens, Token $docsToken, $languageConstructIndex) { static $languageStructures = [ T_LIST, T_PRINT, T_ECHO, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, ]; if (!$tokens[$languageConstructIndex]->isGivenKind($languageStructures)) { return false; } $endKind = $tokens[$languageConstructIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN) ? [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE] : ')' ; $endIndex = $tokens->getNextTokenOfKind($languageConstructIndex, [$endKind]); $docsContent = $docsToken->getContent(); for ($index = $languageConstructIndex + 1; $index < $endIndex; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_VARIABLE) && false !== strpos($docsContent, $token->getContent())) { return true; } } return false; } private function isValidVariable(Tokens $tokens, $index) { if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { return false; } $nextIndex = $tokens->getNextMeaningfulToken($index); return $tokens[$nextIndex]->equals('='); } private function getCommentType($content) { if ('#' === $content[0]) { return self::TYPE_HASH; } if ('*' === $content[1]) { return self::TYPE_SLASH_ASTERISK; } return self::TYPE_DOUBLE_SLASH; } private function getLineBreakCount(Tokens $tokens, $whiteStart, $whiteEnd) { $lineCount = 0; for ($i = $whiteStart; $i < $whiteEnd; ++$i) { $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent()); } return $lineCount; } } isGivenKind(T_NAMESPACE)) { continue; } $declarationEndIndex = $tokens->getNextTokenOfKind($index, [';', '{']); $namespace = trim($tokens->generatePartialCode($index + 1, $declarationEndIndex - 1)); $declarationParts = explode('\\', $namespace); $shortName = end($declarationParts); if ($tokens[$declarationEndIndex]->equals('{')) { $scopeEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $declarationEndIndex); } else { $scopeEndIndex = $tokens->getNextTokenOfKind($declarationEndIndex, [[T_NAMESPACE]]); if (null === $scopeEndIndex) { $scopeEndIndex = \count($tokens); } --$scopeEndIndex; } $namespaces[] = new NamespaceAnalysis( $namespace, $shortName, $index, $declarationEndIndex, $index, $scopeEndIndex ); $index = $scopeEndIndex; } if (0 === \count($namespaces)) { $namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1); } return $namespaces; } } isGivenKind(T_STRING)) { return false; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } $nextIndex = $tokens->getNextMeaningfulToken($index); return !$tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, T_OBJECT_OPERATOR, CT::T_RETURN_REF, T_STRING]) && $tokens[$nextIndex]->equals('('); } public function getFunctionArguments(Tokens $tokens, $methodIndex) { $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); $argumentAnalyzer = new ArgumentsAnalyzer(); $arguments = []; foreach ($argumentAnalyzer->getArguments($tokens, $argumentsStart, $argumentsEnd) as $start => $end) { $argumentInfo = $argumentAnalyzer->getArgumentInfo($tokens, $start, $end); $arguments[$argumentInfo->getName()] = $argumentInfo; } return $arguments; } public function getFunctionReturnType(Tokens $tokens, $methodIndex) { $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); $typeColonIndex = $tokens->getNextMeaningfulToken($argumentsEnd); if (':' !== $tokens[$typeColonIndex]->getContent()) { return null; } $type = ''; $typeStartIndex = $tokens->getNextNonWhitespace($typeColonIndex); $typeEndIndex = $typeStartIndex; $functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';']); for ($i = $typeStartIndex; $i < $functionBodyStart; ++$i) { if ($tokens[$i]->isWhitespace()) { continue; } $type .= $tokens[$i]->getContent(); $typeEndIndex = $i; } return new TypeAnalysis($type, $typeStartIndex, $typeEndIndex); } } getConstants()); } return $constants; } } tokens = $tokens; } public function getClassyElements() { $this->tokens->rewind(); $elements = []; for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) { if ($this->tokens[$index]->isClassy()) { list($index, $newElements) = $this->findClassyElements($index); $elements += $newElements; } } ksort($elements); return $elements; } public function getImportUseIndexes($perNamespace = false) { $tokens = $this->tokens; $tokens->rewind(); $uses = []; $namespaceIndex = 0; for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_NAMESPACE)) { $nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']); $nextToken = $tokens[$nextTokenIndex]; if ($nextToken->equals('{')) { $index = $nextTokenIndex; } if ($perNamespace) { ++$namespaceIndex; } continue; } if ($token->isGivenKind(T_USE)) { $uses[$namespaceIndex][] = $index; } } if (!$perNamespace && isset($uses[$namespaceIndex])) { return $uses[$namespaceIndex]; } return $uses; } public function isArray($index) { return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } public function isArrayMultiLine($index) { if (!$this->isArray($index)) { throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index)); } $tokens = $this->tokens; if ($tokens[$index]->isGivenKind(T_ARRAY)) { $index = $tokens->getNextMeaningfulToken($index); } $endIndex = $tokens[$index]->equals('(') ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index) ; for (++$index; $index < $endIndex; ++$index) { $token = $tokens[$index]; $blockType = Tokens::detectBlockType($token); if ($blockType && $blockType['isStart']) { $index = $tokens->findBlockEnd($blockType['type'], $index); continue; } if ( $token->isWhitespace() && !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC) && false !== strpos($token->getContent(), "\n") ) { return true; } } return false; } public function getMethodAttributes($index) { $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isGivenKind(T_FUNCTION)) { throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got %s.', $index, $token->getName())); } $attributes = [ 'visibility' => null, 'static' => false, 'abstract' => false, 'final' => false, ]; for ($i = $index; $i >= 0; --$i) { $tokenIndex = $tokens->getPrevMeaningfulToken($i); $i = $tokenIndex; $token = $tokens[$tokenIndex]; if ($token->isGivenKind(T_STATIC)) { $attributes['static'] = true; continue; } if ($token->isGivenKind(T_FINAL)) { $attributes['final'] = true; continue; } if ($token->isGivenKind(T_ABSTRACT)) { $attributes['abstract'] = true; continue; } if ($token->isGivenKind(T_PRIVATE)) { $attributes['visibility'] = T_PRIVATE; continue; } if ($token->isGivenKind(T_PROTECTED)) { $attributes['visibility'] = T_PROTECTED; continue; } if ($token->isGivenKind(T_PUBLIC)) { $attributes['visibility'] = T_PUBLIC; continue; } break; } return $attributes; } public function isAnonymousClass($index) { $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isClassy()) { throw new \LogicException(sprintf('No classy token at given index %d.', $index)); } if (!$token->isGivenKind(T_CLASS)) { return false; } return $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NEW); } public function isLambda($index) { if (!$this->tokens[$index]->isGivenKind(T_FUNCTION)) { throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got %s.', $index, $this->tokens[$index]->getName())); } $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index); $startParenthesisToken = $this->tokens[$startParenthesisIndex]; if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) { $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex); $startParenthesisToken = $this->tokens[$startParenthesisIndex]; } return $startParenthesisToken->equals('('); } public function isConstantInvocation($index) { if (!$this->tokens[$index]->isGivenKind(T_STRING)) { throw new \LogicException(sprintf('No T_STRING at given index %d, got %s.', $index, $this->tokens[$index]->getName())); } $nextIndex = $this->tokens->getNextMeaningfulToken($index); if ( $this->tokens[$nextIndex]->equalsAny(['(', '{']) || $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, T_VARIABLE]) ) { return false; } $prevIndex = $this->tokens->getPrevMeaningfulToken($index); if ($this->tokens[$prevIndex]->isGivenKind([T_AS, T_CLASS, T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_GOTO, CT::T_GROUP_IMPORT_BRACE_OPEN, T_INTERFACE, T_OBJECT_OPERATOR, T_TRAIT])) { return false; } while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) { $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); } if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, T_USE, CT::T_USE_TRAIT])) { return false; } if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) { $checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]); if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) { return false; } } if ($this->tokens[$prevIndex]->equals(',')) { $checkIndex = $prevIndex; while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) { $checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex); } if ($this->tokens[$checkIndex]->isGivenKind([CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, CT::T_USE_TRAIT])) { return false; } } if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) { $checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]; if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) { return false; } } if ($this->tokens[$nextIndex]->equals(':') && $this->tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]])) { return false; } return true; } public function isUnarySuccessorOperator($index) { static $allowedPrevToken = [ ']', [T_STRING], [T_VARIABLE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], ]; $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isGivenKind([T_INC, T_DEC])) { return false; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; return $prevToken->equalsAny($allowedPrevToken); } public function isUnaryPredecessorOperator($index) { static $potentialSuccessorOperator = [T_INC, T_DEC]; static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]]; static $otherOperators; if (null === $otherOperators) { $otherOperators = ['!', '~', '@', [T_ELLIPSIS]]; } static $disallowedPrevTokens; if (null === $disallowedPrevTokens) { $disallowedPrevTokens = [ ']', '}', ')', '"', '`', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [T_CLASS_C], [T_CONSTANT_ENCAPSED_STRING], [T_DEC], [T_DIR], [T_DNUMBER], [T_FILE], [T_FUNC_C], [T_INC], [T_LINE], [T_LNUMBER], [T_METHOD_C], [T_NS_C], [T_STRING], [T_TRAIT_C], [T_VARIABLE], ]; } $tokens = $this->tokens; $token = $tokens[$index]; if ($token->isGivenKind($potentialSuccessorOperator)) { return !$this->isUnarySuccessorOperator($index); } if ($token->equalsAny($otherOperators)) { return true; } if (!$token->equalsAny($potentialBinaryOperator)) { return false; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if (!$prevToken->equalsAny($disallowedPrevTokens)) { return true; } if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) { return false; } static $searchTokens = [ ';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO], ]; $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)]; return $prevToken->isGivenKind(T_FUNCTION); } public function isBinaryOperator($index) { static $nonArrayOperators = [ '=' => true, '*' => true, '/' => true, '%' => true, '<' => true, '>' => true, '|' => true, '^' => true, ]; static $potentialUnaryNonArrayOperators = [ '+' => true, '-' => true, '&' => true, ]; static $arrayOperators; if (null === $arrayOperators) { $arrayOperators = [ T_AND_EQUAL => true, T_BOOLEAN_AND => true, T_BOOLEAN_OR => true, T_CONCAT_EQUAL => true, T_DIV_EQUAL => true, T_DOUBLE_ARROW => true, T_IS_EQUAL => true, T_IS_GREATER_OR_EQUAL => true, T_IS_IDENTICAL => true, T_IS_NOT_EQUAL => true, T_IS_NOT_IDENTICAL => true, T_IS_SMALLER_OR_EQUAL => true, T_LOGICAL_AND => true, T_LOGICAL_OR => true, T_LOGICAL_XOR => true, T_MINUS_EQUAL => true, T_MOD_EQUAL => true, T_MUL_EQUAL => true, T_OR_EQUAL => true, T_PLUS_EQUAL => true, T_POW => true, T_POW_EQUAL => true, T_SL => true, T_SL_EQUAL => true, T_SR => true, T_SR_EQUAL => true, T_XOR_EQUAL => true, CT::T_TYPE_ALTERNATION => true, ]; if (\defined('T_SPACESHIP')) { $arrayOperators[T_SPACESHIP] = true; } if (\defined('T_COALESCE')) { $arrayOperators[T_COALESCE] = true; } } $tokens = $this->tokens; $token = $tokens[$index]; if ($token->isArray()) { return isset($arrayOperators[$token->getId()]); } if (isset($nonArrayOperators[$token->getContent()])) { return true; } if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) { return !$this->isUnaryPredecessorOperator($index); } return false; } public function isWhilePartOfDoWhile($index) { $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isGivenKind(T_WHILE)) { throw new \LogicException(sprintf('No T_WHILE at given index %d, got %s.', $index, $token->getName())); } $endIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$endIndex]->equals('}')) { return false; } $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); $beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex); return $tokens[$beforeStartIndex]->isGivenKind(T_DO); } private function findClassyElements($index) { $elements = []; $curlyBracesLevel = 0; $bracesLevel = 0; $classIndex = $index; ++$index; for ($count = \count($this->tokens); $index < $count; ++$index) { $token = $this->tokens[$index]; if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { continue; } if ($token->isClassy()) { list($index, $newElements) = $this->findClassyElements($index); $elements += $newElements; continue; } if ($token->equals('(')) { ++$bracesLevel; continue; } if ($token->equals(')')) { --$bracesLevel; continue; } if ($token->equals('{')) { ++$curlyBracesLevel; continue; } if ($token->equals('}')) { --$curlyBracesLevel; if (0 === $curlyBracesLevel) { break; } continue; } if (1 !== $curlyBracesLevel || !$token->isArray()) { continue; } if (0 === $bracesLevel && $token->isGivenKind(T_VARIABLE)) { $elements[$index] = [ 'token' => $token, 'type' => 'property', 'classIndex' => $classIndex, ]; continue; } if ($token->isGivenKind(T_FUNCTION)) { $elements[$index] = [ 'token' => $token, 'type' => 'method', 'classIndex' => $classIndex, ]; } elseif ($token->isGivenKind(T_CONST)) { $elements[$index] = [ 'token' => $token, 'type' => 'const', 'classIndex' => $classIndex, ]; } } return [$index, $elements]; } } isArray = true; $this->id = $token[0]; $this->content = $token[1]; if ($token[0] && '' === $token[1]) { throw new \InvalidArgumentException('Cannot set empty content for id-based Token.'); } } elseif (\is_string($token)) { $this->isArray = false; $this->content = $token; } else { throw new \InvalidArgumentException(sprintf( 'Cannot recognize input value as valid Token prototype, got "%s".', \is_object($token) ? \get_class($token) : \gettype($token) )); } } public static function getCastTokenKinds() { static $castTokens = [T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST]; return $castTokens; } public static function getClassyTokenKinds() { static $classTokens = [T_CLASS, T_TRAIT, T_INTERFACE]; return $classTokens; } public function clear() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); $this->content = ''; $this->id = null; $this->isArray = false; } public function clearChanged() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); $this->changed = false; } public function equals($other, $caseSensitive = true) { if ($other instanceof self) { if (!$other->isArray) { $otherPrototype = $other->content; } else { $otherPrototype = [ $other->id, $other->content, ]; } } else { $otherPrototype = $other; } if ($this->isArray !== \is_array($otherPrototype)) { return false; } if (!$this->isArray) { return $this->content === $otherPrototype; } if ($this->id !== $otherPrototype[0]) { return false; } if (isset($otherPrototype[1])) { if ($caseSensitive) { if ($this->content !== $otherPrototype[1]) { return false; } } elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) { return false; } } unset($otherPrototype[0], $otherPrototype[1]); return empty($otherPrototype); } public function equalsAny(array $others, $caseSensitive = true) { foreach ($others as $other) { if ($this->equals($other, $caseSensitive)) { return true; } } return false; } public static function isKeyCaseSensitive($caseSensitive, $key) { if (\is_array($caseSensitive)) { return isset($caseSensitive[$key]) ? $caseSensitive[$key] : true; } return $caseSensitive; } public function getPrototype() { if (!$this->isArray) { return $this->content; } return [ $this->id, $this->content, ]; } public function getContent() { return $this->content; } public function getId() { return $this->id; } public function getName() { if (null === $this->id) { return null; } return self::getNameForId($this->id); } public static function getNameForId($id) { if (CT::has($id)) { return CT::getName($id); } $name = token_name($id); return 'UNKNOWN' === $name ? null : $name; } public static function getKeywords() { static $keywords = null; if (null === $keywords) { $keywords = self::getTokenKindsForNames(['T_ABSTRACT', 'T_ARRAY', 'T_AS', 'T_BREAK', 'T_CALLABLE', 'T_CASE', 'T_CATCH', 'T_CLASS', 'T_CLONE', 'T_CONST', 'T_CONTINUE', 'T_DECLARE', 'T_DEFAULT', 'T_DO', 'T_ECHO', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENDDECLARE', 'T_ENDFOR', 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_EVAL', 'T_EXIT', 'T_EXTENDS', 'T_FINAL', 'T_FINALLY', 'T_FOR', 'T_FOREACH', 'T_FUNCTION', 'T_GLOBAL', 'T_GOTO', 'T_HALT_COMPILER', 'T_IF', 'T_IMPLEMENTS', 'T_INCLUDE', 'T_INCLUDE_ONCE', 'T_INSTANCEOF', 'T_INSTEADOF', 'T_INTERFACE', 'T_ISSET', 'T_LIST', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR', 'T_NAMESPACE', 'T_NEW', 'T_PRINT', 'T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC', 'T_REQUIRE', 'T_REQUIRE_ONCE', 'T_RETURN', 'T_STATIC', 'T_SWITCH', 'T_THROW', 'T_TRAIT', 'T_TRY', 'T_UNSET', 'T_USE', 'T_VAR', 'T_WHILE', 'T_YIELD', 'T_YIELD_FROM', ]) + [ CT::T_ARRAY_TYPEHINT => CT::T_ARRAY_TYPEHINT, CT::T_CLASS_CONSTANT => CT::T_CLASS_CONSTANT, CT::T_CONST_IMPORT => CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT, CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR, CT::T_USE_TRAIT => CT::T_USE_TRAIT, CT::T_USE_LAMBDA => CT::T_USE_LAMBDA, ]; } return $keywords; } public static function getMagicConstants() { static $magicConstants = null; if (null === $magicConstants) { $magicConstants = self::getTokenKindsForNames(['T_CLASS_C', 'T_DIR', 'T_FILE', 'T_FUNC_C', 'T_LINE', 'T_METHOD_C', 'T_NS_C', 'T_TRAIT_C']); } return $magicConstants; } public function isArray() { return $this->isArray; } public function isCast() { return $this->isGivenKind(self::getCastTokenKinds()); } public function isChanged() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); return $this->changed; } public function isClassy() { return $this->isGivenKind(self::getClassyTokenKinds()); } public function isComment() { static $commentTokens = [T_COMMENT, T_DOC_COMMENT]; return $this->isGivenKind($commentTokens); } public function isEmpty() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); return null === $this->id && ('' === $this->content || null === $this->content); } public function isGivenKind($possibleKind) { return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind); } public function isKeyword() { $keywords = static::getKeywords(); return $this->isArray && isset($keywords[$this->id]); } public function isNativeConstant() { static $nativeConstantStrings = ['true', 'false', 'null']; return $this->isArray && \in_array(strtolower($this->content), $nativeConstantStrings, true); } public function isMagicConstant() { $magicConstants = static::getMagicConstants(); return $this->isArray && isset($magicConstants[$this->id]); } public function isWhitespace($whitespaces = " \t\n\r\0\x0B") { if (null === $whitespaces) { $whitespaces = " \t\n\r\0\x0B"; } if ($this->isArray && !$this->isGivenKind(T_WHITESPACE)) { return false; } return '' === trim($this->content, $whitespaces); } public function override($other) { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); $prototype = $other instanceof self ? $other->getPrototype() : $other; if ($this->equals($prototype)) { return; } $this->changed = true; if (\is_array($prototype)) { $this->isArray = true; $this->id = $prototype[0]; $this->content = $prototype[1]; return; } $this->isArray = false; $this->id = null; $this->content = $prototype; } public function setContent($content) { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); if ($this->content === $content) { return; } $this->changed = true; $this->content = $content; if ('' === $content) { @trigger_error(__METHOD__.' shall not be used to clear token, use Tokens::clearAt instead.', E_USER_DEPRECATED); $this->id = null; $this->isArray = false; } } public function toArray() { return [ 'id' => $this->id, 'name' => $this->getName(), 'content' => $this->content, 'isArray' => $this->isArray, 'changed' => $this->changed, ]; } public function toJson(array $options = null) { static $defaultOptions = null; if (null === $options) { if (null === $defaultOptions) { $defaultOptions = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); } $options = $defaultOptions; } else { $options = Utils::calculateBitmask($options); } return json_encode($this->toArray(), $options); } private static function getTokenKindsForNames(array $tokenNames) { $keywords = []; foreach ($tokenNames as $keywordName) { if (\defined($keywordName)) { $keyword = \constant($keywordName); $keywords[$keyword] = $keyword; } } return $keywords; } } $val) { $this[$key] = clone $val; } } public static function isLegacyMode() { return self::$isLegacyMode; } public static function setLegacyMode($isLegacy) { if (getenv('PHP_CS_FIXER_FUTURE_MODE') && $isLegacy) { throw new \RuntimeException('Cannot enable `legacy mode` when using `future mode`. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.'); } self::$isLegacyMode = $isLegacy; } public static function clearCache($key = null) { if (null === $key) { self::$cache = []; return; } if (self::hasCache($key)) { unset(self::$cache[$key]); } } public static function detectBlockType(Token $token) { foreach (self::getBlockEdgeDefinitions() as $type => $definition) { if ($token->equals($definition['start'])) { return ['type' => $type, 'isStart' => true]; } if ($token->equals($definition['end'])) { return ['type' => $type, 'isStart' => false]; } } } public static function fromArray($array, $saveIndexes = null) { $tokens = new self(\count($array)); if (null === $saveIndexes || $saveIndexes) { foreach ($array as $key => $val) { $tokens[$key] = $val; } } else { $index = 0; foreach ($array as $val) { $tokens[$index++] = $val; } } $tokens->generateCode(); return $tokens; } public static function fromCode($code) { $codeHash = self::calculateCodeHash($code); if (self::hasCache($codeHash)) { $tokens = self::getCache($codeHash); $tokens->generateCode(); if ($codeHash === $tokens->codeHash) { $tokens->clearEmptyTokens(); $tokens->clearChanged(); return $tokens; } } $tokens = new self(); $tokens->setCode($code); $tokens->clearChanged(); return $tokens; } public static function getBlockEdgeDefinitions() { return [ self::BLOCK_TYPE_CURLY_BRACE => [ 'start' => '{', 'end' => '}', ], self::BLOCK_TYPE_PARENTHESIS_BRACE => [ 'start' => '(', 'end' => ')', ], self::BLOCK_TYPE_INDEX_SQUARE_BRACE => [ 'start' => '[', 'end' => ']', ], self::BLOCK_TYPE_ARRAY_SQUARE_BRACE => [ 'start' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, '['], 'end' => [CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']'], ], self::BLOCK_TYPE_DYNAMIC_PROP_BRACE => [ 'start' => [CT::T_DYNAMIC_PROP_BRACE_OPEN, '{'], 'end' => [CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_DYNAMIC_VAR_BRACE => [ 'start' => [CT::T_DYNAMIC_VAR_BRACE_OPEN, '{'], 'end' => [CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE => [ 'start' => [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{'], 'end' => [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_GROUP_IMPORT_BRACE => [ 'start' => [CT::T_GROUP_IMPORT_BRACE_OPEN, '{'], 'end' => [CT::T_GROUP_IMPORT_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE => [ 'start' => [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '['], 'end' => [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']'], ], self::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION => [ 'start' => [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('], 'end' => [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'], ], ]; } public function setSize($size) { if ($this->getSize() !== $size) { $this->changed = true; parent::setSize($size); } } public function offsetUnset($index) { $this->changed = true; $this->unregisterFoundToken($this[$index]); parent::offsetUnset($index); } public function offsetSet($index, $newval) { $this->blockEndCache = []; if (!$this[$index] || !$this[$index]->equals($newval)) { $this->changed = true; if (isset($this[$index])) { $this->unregisterFoundToken($this[$index]); } $this->registerFoundToken($newval); } parent::offsetSet($index, $newval); } public function clearChanged() { $this->changed = false; if (self::isLegacyMode()) { foreach ($this as $token) { $token->clearChanged(); } } } public function clearEmptyTokens() { $limit = $this->count(); $index = 0; for (; $index < $limit; ++$index) { if ($this->isEmptyAt($index)) { break; } } if ($limit === $index) { return; } for ($count = $index; $index < $limit; ++$index) { if (!$this->isEmptyAt($index)) { $this[$count++] = $this[$index]; } } $this->setSize($count); } public function ensureWhitespaceAtIndex($index, $indexOffset, $whitespace) { $removeLastCommentLine = static function (self $tokens, $index, $indexOffset, $whitespace) { $token = $tokens[$index]; if (1 === $indexOffset && $token->isGivenKind(T_OPEN_TAG)) { if (0 === strpos($whitespace, "\r\n")) { $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent())."\r\n"]); return \strlen($whitespace) > 2 ? substr($whitespace, 2) : '' ; } $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent()).$whitespace[0]]); return \strlen($whitespace) > 1 ? substr($whitespace, 1) : '' ; } return $whitespace; }; if ($this[$index]->isWhitespace()) { $whitespace = $removeLastCommentLine($this, $index - 1, $indexOffset, $whitespace); if ('' === $whitespace) { $this->clearAt($index); } else { $this[$index] = new Token([T_WHITESPACE, $whitespace]); } return false; } $whitespace = $removeLastCommentLine($this, $index, $indexOffset, $whitespace); if ('' === $whitespace) { return false; } $this->insertAt( $index + $indexOffset, [ new Token([T_WHITESPACE, $whitespace]), ] ); return true; } public function findBlockEnd($type, $searchIndex, $findEnd = true) { if (3 === \func_num_args()) { if ($findEnd) { @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, you can safely drop the argument.', E_USER_DEPRECATED); } else { @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, use Tokens::findBlockStart instead.', E_USER_DEPRECATED); } } return $this->findOppositeBlockEdge($type, $searchIndex, $findEnd); } public function findBlockStart($type, $searchIndex) { return $this->findOppositeBlockEdge($type, $searchIndex, false); } public function findGivenKind($possibleKind, $start = 0, $end = null) { $this->rewind(); if (null === $end) { $end = $this->count(); } $elements = []; $possibleKinds = (array) $possibleKind; foreach ($possibleKinds as $kind) { $elements[$kind] = []; } if (!self::isLegacyMode()) { $possibleKinds = array_filter($possibleKinds, function ($kind) { return $this->isTokenKindFound($kind); }); } if (\count($possibleKinds)) { for ($i = $start; $i < $end; ++$i) { $token = $this[$i]; if ($token->isGivenKind($possibleKinds)) { $elements[$token->getId()][$i] = $token; } } } return \is_array($possibleKind) ? $elements : $elements[$possibleKind]; } public function generateCode() { $code = $this->generatePartialCode(0, \count($this) - 1); $this->changeCodeHash(self::calculateCodeHash($code)); return $code; } public function generatePartialCode($start, $end) { $code = ''; for ($i = $start; $i <= $end; ++$i) { $code .= $this[$i]->getContent(); } return $code; } public function getCodeHash() { return $this->codeHash; } public function getNextNonWhitespace($index, $whitespaces = null) { return $this->getNonWhitespaceSibling($index, 1, $whitespaces); } public function getNextTokenOfKind($index, array $tokens = [], $caseSensitive = true) { return $this->getTokenOfKindSibling($index, 1, $tokens, $caseSensitive); } public function getNonWhitespaceSibling($index, $direction, $whitespaces = null) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } $token = $this[$index]; if (!$token->isWhitespace($whitespaces)) { return $index; } } } public function getPrevNonWhitespace($index, $whitespaces = null) { return $this->getNonWhitespaceSibling($index, -1, $whitespaces); } public function getPrevTokenOfKind($index, array $tokens = [], $caseSensitive = true) { return $this->getTokenOfKindSibling($index, -1, $tokens, $caseSensitive); } public function getTokenOfKindSibling($index, $direction, array $tokens = [], $caseSensitive = true) { if (!self::isLegacyMode()) { $tokens = array_filter($tokens, function ($token) { return $this->isTokenKindFound($this->extractTokenKind($token)); }); } if (!\count($tokens)) { return null; } while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } $token = $this[$index]; if ($token->equalsAny($tokens, $caseSensitive)) { return $index; } } } public function getTokenNotOfKindSibling($index, $direction, array $tokens = []) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } if ($this->isEmptyAt($index)) { continue; } if ($this[$index]->equalsAny($tokens)) { continue; } return $index; } } public function getMeaningfulTokenSibling($index, $direction) { return $this->getTokenNotOfKindSibling( $index, $direction, [[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT]] ); } public function getNonEmptySibling($index, $direction) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } if (!$this->isEmptyAt($index)) { return $index; } } } public function getNextMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, 1); } public function getPrevMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, -1); } public function findSequence(array $sequence, $start = 0, $end = null, $caseSensitive = true) { $sequenceCount = \count($sequence); if (0 === $sequenceCount) { throw new \InvalidArgumentException('Invalid sequence.'); } $end = null === $end ? \count($this) - 1 : min($end, \count($this) - 1); if ($start + $sequenceCount - 1 > $end) { return null; } foreach ($sequence as $key => $token) { if (!$token instanceof Token) { if (\is_array($token) && !isset($token[1])) { $token[1] = 'DUMMY'; } $token = new Token($token); } if ($token->isWhitespace() || $token->isComment() || '' === $token->getContent()) { throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: %s.', $key)); } } if (!self::isLegacyMode()) { foreach ($sequence as $token) { if (!$this->isTokenKindFound($this->extractTokenKind($token))) { return null; } } } $key = key($sequence); $firstCs = Token::isKeyCaseSensitive($caseSensitive, $key); $firstToken = $sequence[$key]; unset($sequence[$key]); $index = $start - 1; while (null !== $index && $index <= $end) { $index = $this->getNextTokenOfKind($index, [$firstToken], $firstCs); if (null === $index || $index > $end) { return null; } $result = [$index => $this[$index]]; $currIdx = $index; foreach ($sequence as $key => $token) { $currIdx = $this->getNextMeaningfulToken($currIdx); if (null === $currIdx || $currIdx > $end) { return null; } if (!$this[$currIdx]->equals($token, Token::isKeyCaseSensitive($caseSensitive, $key))) { continue 2; } $result[$currIdx] = $this[$currIdx]; } if (\count($sequence) < \count($result)) { return $result; } } } public function insertAt($index, $items) { $items = \is_array($items) || $items instanceof self ? $items : [$items]; $itemsCnt = \count($items); if (0 === $itemsCnt) { return; } $oldSize = \count($this); $this->changed = true; $this->blockEndCache = []; $this->setSize($oldSize + $itemsCnt); for ($i = $oldSize + $itemsCnt - 1; $i >= $index; --$i) { $oldItem = parent::offsetExists($i - $itemsCnt) ? parent::offsetGet($i - $itemsCnt) : new Token(''); parent::offsetSet($i, $oldItem); } for ($i = 0; $i < $itemsCnt; ++$i) { if ('' === $items[$i]->getContent()) { throw new \InvalidArgumentException('Must not add empty token to collection.'); } $this->registerFoundToken($items[$i]); parent::offsetSet($i + $index, $items[$i]); } } public function isChanged() { if ($this->changed) { return true; } if (self::isLegacyMode()) { foreach ($this as $token) { if ($token->isChanged()) { return true; } } } return false; } public function isEmptyAt($index) { $token = $this[$index]; return null === $token->getId() && '' === $token->getContent(); } public function clearAt($index) { $this[$index] = new Token(''); } public function overrideAt($index, $token) { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0, use offsetSet instead.', E_USER_DEPRECATED); self::$isLegacyMode = true; $this[$index]->override($token); $this->registerFoundToken($token); } public function overrideRange($indexStart, $indexEnd, $items) { $oldCode = $this->generatePartialCode($indexStart, $indexEnd); $newCode = ''; foreach ($items as $item) { $newCode .= $item->getContent(); } if ($oldCode === $newCode) { return; } $indexToChange = $indexEnd - $indexStart + 1; $itemsCount = \count($items); if ($itemsCount > $indexToChange) { $placeholders = []; while ($itemsCount > $indexToChange) { $placeholders[] = new Token('__PLACEHOLDER__'); ++$indexToChange; } $this->insertAt($indexEnd + 1, $placeholders); } foreach ($items as $itemIndex => $item) { $this[$indexStart + $itemIndex] = $item; } if ($itemsCount < $indexToChange) { $this->clearRange($indexStart + $itemsCount, $indexEnd); } } public function removeLeadingWhitespace($index, $whitespaces = null) { $this->removeWhitespaceSafely($index, -1, $whitespaces); } public function removeTrailingWhitespace($index, $whitespaces = null) { $this->removeWhitespaceSafely($index, 1, $whitespaces); } public function setCode($code) { if ($code === $this->generateCode()) { return; } $this->setSize(0); $tokens = \defined('TOKEN_PARSE') ? token_get_all($code, TOKEN_PARSE) : token_get_all($code); $this->setSize(\count($tokens)); foreach ($tokens as $index => $token) { $this[$index] = new Token($token); } $transformers = Transformers::create(); $transformers->transform($this); $this->foundTokenKinds = []; foreach ($this as $token) { $this->registerFoundToken($token); } $this->rewind(); $this->changeCodeHash(self::calculateCodeHash($code)); $this->changed = true; } public function toJson() { static $options = null; if (null === $options) { $options = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); } $output = new \SplFixedArray(\count($this)); foreach ($this as $index => $token) { $output[$index] = $token->toArray(); } $this->rewind(); return json_encode($output, $options); } public function isAllTokenKindsFound(array $tokenKinds) { foreach ($tokenKinds as $tokenKind) { if (empty($this->foundTokenKinds[$tokenKind])) { return false; } } return true; } public function isAnyTokenKindsFound(array $tokenKinds) { foreach ($tokenKinds as $tokenKind) { if (!empty($this->foundTokenKinds[$tokenKind])) { return true; } } return false; } public function isTokenKindFound($tokenKind) { return !empty($this->foundTokenKinds[$tokenKind]); } public function countTokenKind($tokenKind) { if (self::isLegacyMode()) { throw new \RuntimeException(sprintf('%s is not available in legacy mode.', __METHOD__)); } return isset($this->foundTokenKinds[$tokenKind]) ? $this->foundTokenKinds[$tokenKind] : 0; } public function clearRange($indexStart, $indexEnd) { for ($i = $indexStart; $i <= $indexEnd; ++$i) { $this->clearAt($i); } } public function isMonolithicPhp() { $size = $this->count(); if (0 === $size) { return false; } if (self::isLegacyMode()) { if ($this[0]->isGivenKind(T_INLINE_HTML) || $this[$size - 1]->isGivenKind(T_INLINE_HTML)) { return false; } for ($index = 1; $index < $size; ++$index) { if ($this[$index]->isGivenKind([T_INLINE_HTML, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO])) { return false; } } return true; } if ($this->isTokenKindFound(T_INLINE_HTML)) { return false; } return 1 >= ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO)); } public function isPartialCodeMultiline($start, $end) { for ($i = $start; $i <= $end; ++$i) { if (false !== strpos($this[$i]->getContent(), "\n")) { return true; } } return false; } public function clearTokenAndMergeSurroundingWhitespace($index) { $count = \count($this); $this->clearAt($index); if ($index === $count - 1) { return; } $nextIndex = $this->getNonEmptySibling($index, 1); if (null === $nextIndex || !$this[$nextIndex]->isWhitespace()) { return; } $prevIndex = $this->getNonEmptySibling($index, -1); if ($this[$prevIndex]->isWhitespace()) { $this[$prevIndex] = new Token([T_WHITESPACE, $this[$prevIndex]->getContent().$this[$nextIndex]->getContent()]); } elseif ($this->isEmptyAt($prevIndex + 1)) { $this[$prevIndex + 1] = new Token([T_WHITESPACE, $this[$nextIndex]->getContent()]); } $this->clearAt($nextIndex); } private function removeWhitespaceSafely($index, $direction, $whitespaces = null) { $whitespaceIndex = $this->getNonEmptySibling($index, $direction); if (isset($this[$whitespaceIndex]) && $this[$whitespaceIndex]->isWhitespace()) { $newContent = ''; $tokenToCheck = $this[$whitespaceIndex]; if (isset($this[$whitespaceIndex - 1]) && $this[$whitespaceIndex - 1]->isComment() && '/*' !== substr($this[$whitespaceIndex - 1]->getContent(), 0, 2)) { list($emptyString, $newContent, $whitespacesToCheck) = Preg::split('/^(\R)/', $this[$whitespaceIndex]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); if ('' === $whitespacesToCheck) { return; } $tokenToCheck = new Token([T_WHITESPACE, $whitespacesToCheck]); } if (!$tokenToCheck->isWhitespace($whitespaces)) { return; } if ('' === $newContent) { $this->clearAt($whitespaceIndex); } else { $this[$whitespaceIndex] = new Token([T_WHITESPACE, $newContent]); } } } private function findOppositeBlockEdge($type, $searchIndex, $findEnd) { $blockEdgeDefinitions = self::getBlockEdgeDefinitions(); if (!isset($blockEdgeDefinitions[$type])) { throw new \InvalidArgumentException(sprintf('Invalid param type: %s.', $type)); } if (!self::isLegacyMode() && isset($this->blockEndCache[$searchIndex])) { return $this->blockEndCache[$searchIndex]; } $startEdge = $blockEdgeDefinitions[$type]['start']; $endEdge = $blockEdgeDefinitions[$type]['end']; $startIndex = $searchIndex; $endIndex = $this->count() - 1; $indexOffset = 1; if (!$findEnd) { list($startEdge, $endEdge) = [$endEdge, $startEdge]; $indexOffset = -1; $endIndex = 0; } if (!$this[$startIndex]->equals($startEdge)) { throw new \InvalidArgumentException(sprintf('Invalid param $startIndex - not a proper block %s.', $findEnd ? 'start' : 'end')); } $blockLevel = 0; for ($index = $startIndex; $index !== $endIndex; $index += $indexOffset) { $token = $this[$index]; if ($token->equals($startEdge)) { ++$blockLevel; continue; } if ($token->equals($endEdge)) { --$blockLevel; if (0 === $blockLevel) { break; } continue; } } if (!$this[$index]->equals($endEdge)) { throw new \UnexpectedValueException(sprintf('Missing block %s.', $findEnd ? 'end' : 'start')); } $this->blockEndCache[$startIndex] = $index; $this->blockEndCache[$index] = $startIndex; return $index; } private static function calculateCodeHash($code) { return CodeHasher::calculateCodeHash($code); } private static function getCache($key) { if (!self::hasCache($key)) { throw new \OutOfBoundsException(sprintf('Unknown cache key: "%s".', $key)); } return self::$cache[$key]; } private static function hasCache($key) { return isset(self::$cache[$key]); } private static function setCache($key, self $value) { self::$cache[$key] = $value; } private function changeCodeHash($codeHash) { if (null !== $this->codeHash) { self::clearCache($this->codeHash); } $this->codeHash = $codeHash; self::setCache($this->codeHash, $this); } private function registerFoundToken($token) { $tokenKind = $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) : (\is_array($token) ? $token[0] : $token) ; if (!isset($this->foundTokenKinds[$tokenKind])) { $this->foundTokenKinds[$tokenKind] = 0; } ++$this->foundTokenKinds[$tokenKind]; } private function unregisterFoundToken($token) { $tokenKind = $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) : (\is_array($token) ? $token[0] : $token) ; if (!isset($this->foundTokenKinds[$tokenKind])) { return; } --$this->foundTokenKinds[$tokenKind]; } private function extractTokenKind($token) { return $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) : (\is_array($token) ? $token[0] : $token) ; } } registerBuiltInTransformers(); usort($this->items, static function (TransformerInterface $a, TransformerInterface $b) { return Utils::cmpInt($b->getPriority(), $a->getPriority()); }); } public static function create() { static $instance = null; if (!$instance) { $instance = new self(); } return $instance; } public function transform(Tokens $tokens) { foreach ($this->items as $transformer) { foreach ($tokens as $index => $token) { $transformer->process($tokens, $token, $index); } } } private function registerTransformer(TransformerInterface $transformer) { if (\PHP_VERSION_ID >= $transformer->getRequiredPhpVersionId()) { $this->items[] = $transformer; } } private function registerBuiltInTransformers() { static $registered = false; if ($registered) { return; } $registered = true; foreach ($this->findBuiltInTransformers() as $transformer) { $this->registerTransformer($transformer); } } private function findBuiltInTransformers() { foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) { $relativeNamespace = $file->getRelativePath(); $class = __NAMESPACE__.'\\Transformer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); yield new $class(); } } } getUseMapFromTokens($tokens); foreach ($useMap as $shortName => $fullName) { $regex = '/^\\\\?'.preg_quote($fullName, '/').'$/'; if (Preg::match($regex, $typeName)) { return $shortName; } } $namespaces = $this->getNamespacesFromTokens($tokens); if (1 === \count($namespaces)) { foreach ($namespaces as $fullName) { $matches = []; $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; if (Preg::match($regex, $typeName, $matches)) { return $matches['className']; } } } foreach ($useMap as $shortName => $fullName) { $matches = []; $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; if (Preg::match($regex, $typeName, $matches)) { return $shortName.'\\'.$matches['className']; } } return $typeName; } private function getNamespacesFromTokens(Tokens $tokens) { return array_map(function (NamespaceAnalysis $info) { return $info->getFullName(); }, (new NamespacesAnalyzer())->getDeclarations($tokens)); } private function getUseMapFromTokens(Tokens $tokens) { $map = []; foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $useDeclaration) { $map[$useDeclaration->getShortName()] = $useDeclaration->getFullName(); } return $map; } } $part) { $tokens[] = new Token([T_STRING, $part]); if ($index !== \count($parts) - 1) { $tokens[] = new Token([T_NS_SEPARATOR, '\\']); } } return $tokens; } } isWhitespace()) { throw new \InvalidArgumentException(sprintf('The given token must be whitespace, got "%s".', $token->getName())); } $str = strrchr( str_replace(["\r\n", "\r"], "\n", $token->getContent()), "\n" ); if (false === $str) { return ''; } return ltrim($str, "\n"); } public static function stableSort(array $elements, callable $getComparedValue, callable $compareValues) { array_walk($elements, static function (&$element, $index) use ($getComparedValue) { $element = [$element, $index, $getComparedValue($element)]; }); usort($elements, static function ($a, $b) use ($compareValues) { $comparison = $compareValues($a[2], $b[2]); if (0 !== $comparison) { return $comparison; } return self::cmpInt($a[1], $b[1]); }); return array_map(static function (array $item) { return $item[0]; }, $elements); } public static function sortFixers(array $fixers) { return self::stableSort( $fixers, static function (FixerInterface $fixer) { return $fixer->getPriority(); }, static function ($a, $b) { return self::cmpInt($b, $a); } ); } public static function naturalLanguageJoinWithBackticks(array $names) { if (empty($names)) { throw new \InvalidArgumentException('Array of names cannot be empty'); } $names = array_map(static function ($name) { return sprintf('`%s`', $name); }, $names); $last = array_pop($names); if ($names) { return implode(', ', $names).' and '.$last; } return $last; } } createElement('report'); $dom->appendChild($root); $filesXML = $dom->createElement('files'); $root->appendChild($filesXML); $i = 1; foreach ($reportSummary->getChanged() as $file => $fixResult) { $fileXML = $dom->createElement('file'); $fileXML->setAttribute('id', (string) $i++); $fileXML->setAttribute('name', $file); $filesXML->appendChild($fileXML); if ($reportSummary->shouldAddAppliedFixers()) { $fileXML->appendChild($this->createAppliedFixersElement($dom, $fixResult)); } if (!empty($fixResult['diff'])) { $fileXML->appendChild($this->createDiffElement($dom, $fixResult)); } } if (0 !== $reportSummary->getTime()) { $root->appendChild($this->createTimeElement($reportSummary->getTime(), $dom)); } if (0 !== $reportSummary->getMemory()) { $root->appendChild($this->createMemoryElement($reportSummary->getMemory(), $dom)); } $dom->formatOutput = true; return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } private function createAppliedFixersElement($dom, array $fixResult) { $appliedFixersXML = $dom->createElement('applied_fixers'); foreach ($fixResult['appliedFixers'] as $appliedFixer) { $appliedFixerXML = $dom->createElement('applied_fixer'); $appliedFixerXML->setAttribute('name', $appliedFixer); $appliedFixersXML->appendChild($appliedFixerXML); } return $appliedFixersXML; } private function createDiffElement(\DOMDocument $dom, array $fixResult) { $diffXML = $dom->createElement('diff'); $diffXML->appendChild($dom->createCDATASection($fixResult['diff'])); return $diffXML; } private function createTimeElement($time, \DOMDocument $dom) { $time = round($time / 1000, 3); $timeXML = $dom->createElement('time'); $timeXML->setAttribute('unit', 's'); $timeTotalXML = $dom->createElement('total'); $timeTotalXML->setAttribute('value', (string) $time); $timeXML->appendChild($timeTotalXML); return $timeXML; } private function createMemoryElement($memory, \DOMDocument $dom) { $memory = round($memory / 1024 / 1024, 3); $memoryXML = $dom->createElement('memory'); $memoryXML->setAttribute('value', (string) $memory); $memoryXML->setAttribute('unit', 'MB'); return $memoryXML; } } appendChild($dom->createElement('checkstyle')); foreach ($reportSummary->getChanged() as $filePath => $fixResult) { $file = $checkstyles->appendChild($dom->createElement('file')); $file->setAttribute('name', $filePath); foreach ($fixResult['appliedFixers'] as $appliedFixer) { $error = $this->createError($dom, $appliedFixer); $file->appendChild($error); } } $dom->formatOutput = true; return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } private function createError(\DOMDocument $dom, $appliedFixer) { $error = $dom->createElement('error'); $error->setAttribute('severity', 'warning'); $error->setAttribute('source', 'PHP-CS-Fixer.'.$appliedFixer); $error->setAttribute('message', 'Found violation(s) of type: '.$appliedFixer); return $error; } } getChanged() as $file => $fixResult) { $jfile = ['name' => $file]; if ($reportSummary->shouldAddAppliedFixers()) { $jfile['appliedFixers'] = $fixResult['appliedFixers']; } if (!empty($fixResult['diff'])) { $jfile['diff'] = $fixResult['diff']; } $jFiles[] = $jfile; } $json = [ 'files' => $jFiles, ]; if (null !== $reportSummary->getTime()) { $json['time'] = [ 'total' => round($reportSummary->getTime() / 1000, 3), ]; } if (null !== $reportSummary->getMemory()) { $json['memory'] = round($reportSummary->getMemory() / 1024 / 1024, 3); } $json = json_encode($json); return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($json) : $json; } } files()->name('*Reporter.php')->in(__DIR__) as $file) { $relativeNamespace = $file->getRelativePath(); $builtInReporters[] = sprintf( '%s\\%s%s', __NAMESPACE__, $relativeNamespace ? $relativeNamespace.'\\' : '', $file->getBasename('.php') ); } } foreach ($builtInReporters as $reporterClass) { $this->registerReporter(new $reporterClass()); } return $this; } public function registerReporter(ReporterInterface $reporter) { $format = $reporter->getFormat(); if (isset($this->reporters[$format])) { throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); } $this->reporters[$format] = $reporter; return $this; } public function getFormats() { $formats = array_keys($this->reporters); sort($formats); return $formats; } public function getReporter($format) { if (!isset($this->reporters[$format])) { throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); } return $this->reporters[$format]; } } appendChild($dom->createElement('testsuites')); $testsuite = $testsuites->appendChild($dom->createElement('testsuite')); $testsuite->setAttribute('name', 'PHP CS Fixer'); if (\count($reportSummary->getChanged())) { $this->createFailedTestCases($dom, $testsuite, $reportSummary); } else { $this->createSuccessTestCase($dom, $testsuite); } if ($reportSummary->getTime()) { $testsuite->setAttribute( 'time', sprintf( '%.3f', $reportSummary->getTime() / 1000 ) ); } $dom->formatOutput = true; return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } private function createSuccessTestCase(\DOMDocument $dom, \DOMElement $testsuite) { $testcase = $dom->createElement('testcase'); $testcase->setAttribute('name', 'All OK'); $testcase->setAttribute('assertions', '1'); $testsuite->appendChild($testcase); $testsuite->setAttribute('tests', '1'); $testsuite->setAttribute('assertions', '1'); $testsuite->setAttribute('failures', '0'); $testsuite->setAttribute('errors', '0'); } private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite, ReportSummary $reportSummary) { $assertionsCount = 0; foreach ($reportSummary->getChanged() as $file => $fixResult) { $testcase = $this->createFailedTestCase( $dom, $file, $fixResult, $reportSummary->shouldAddAppliedFixers() ); $testsuite->appendChild($testcase); $assertionsCount += (int) $testcase->getAttribute('assertions'); } $testsuite->setAttribute('tests', (string) \count($reportSummary->getChanged())); $testsuite->setAttribute('assertions', (string) $assertionsCount); $testsuite->setAttribute('failures', (string) $assertionsCount); $testsuite->setAttribute('errors', '0'); } private function createFailedTestCase(\DOMDocument $dom, $file, array $fixResult, $shouldAddAppliedFixers) { $appliedFixersCount = \count($fixResult['appliedFixers']); $testName = str_replace('.', '_DOT_', Preg::replace('@\.'.pathinfo($file, PATHINFO_EXTENSION).'$@', '', $file)); $testcase = $dom->createElement('testcase'); $testcase->setAttribute('name', $testName); $testcase->setAttribute('file', $file); $testcase->setAttribute('assertions', (string) $appliedFixersCount); $failure = $dom->createElement('failure'); $failure->setAttribute('type', 'code_style'); $testcase->appendChild($failure); if ($shouldAddAppliedFixers) { $failureContent = "applied fixers:\n---------------\n"; foreach ($fixResult['appliedFixers'] as $appliedFixer) { $failureContent .= "* {$appliedFixer}\n"; } } else { $failureContent = "Wrong code style\n"; } if (!empty($fixResult['diff'])) { $failureContent .= "\nDiff:\n---------------\n\n".$fixResult['diff']; } $failure->appendChild($dom->createCDATASection(trim($failureContent))); return $testcase; } } changed = $changed; $this->time = $time; $this->memory = $memory; $this->addAppliedFixers = $addAppliedFixers; $this->isDryRun = $isDryRun; $this->isDecoratedOutput = $isDecoratedOutput; } public function isDecoratedOutput() { return $this->isDecoratedOutput; } public function isDryRun() { return $this->isDryRun; } public function getChanged() { return $this->changed; } public function getMemory() { return $this->memory; } public function getTime() { return $this->time; } public function shouldAddAppliedFixers() { return $this->addAppliedFixers; } } getChanged() as $file => $fixResult) { ++$i; $output .= sprintf('%4d) %s', $i, $file); if ($reportSummary->shouldAddAppliedFixers()) { $output .= $this->getAppliedFixers($reportSummary->isDecoratedOutput(), $fixResult); } $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult); $output .= PHP_EOL; } return $output.$this->getFooter($reportSummary->getTime(), $reportSummary->getMemory(), $reportSummary->isDryRun()); } private function getAppliedFixers($isDecoratedOutput, array $fixResult) { return sprintf( $isDecoratedOutput ? ' (%s)' : ' (%s)', implode(', ', $fixResult['appliedFixers']) ); } private function getDiff($isDecoratedOutput, array $fixResult) { if (empty($fixResult['diff'])) { return ''; } $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL )); return PHP_EOL.$diffFormatter->format($fixResult['diff']).PHP_EOL; } private function getFooter($time, $memory, $isDryRun) { if (0 === $time || 0 === $memory) { return ''; } return PHP_EOL.sprintf( '%s all files in %.3f seconds, %.3f MB memory used'.PHP_EOL, $isDryRun ? 'Checked' : 'Fixed', $time / 1000, $memory / 1024 / 1024 ); } } handler = $handler; $this->signature = $signature; $this->isDryRun = $isDryRun; $this->cacheDirectory = $cacheDirectory ?: new Directory(''); $this->readCache(); } public function __destruct() { $this->writeCache(); } public function needFixing($file, $fileContent) { $file = $this->cacheDirectory->getRelativePathTo($file); return !$this->cache->has($file) || $this->cache->get($file) !== $this->calcHash($fileContent); } public function setFile($file, $fileContent) { $file = $this->cacheDirectory->getRelativePathTo($file); $hash = $this->calcHash($fileContent); if ($this->isDryRun && $this->cache->has($file) && $this->cache->get($file) !== $hash) { $this->cache->clear($file); return; } $this->cache->set($file, $hash); } private function readCache() { $cache = $this->handler->read(); if (!$cache || !$this->signature->equals($cache->getSignature())) { $cache = new Cache($this->signature); } $this->cache = $cache; } private function writeCache() { $this->handler->write($this->cache); } private function calcHash($content) { return crc32($content); } } signature = $signature; } public function getSignature() { return $this->signature; } public function has($file) { return \array_key_exists($file, $this->hashes); } public function get($file) { if (!$this->has($file)) { return; } return $this->hashes[$file]; } public function set($file, $hash) { if (!\is_int($hash)) { throw new \InvalidArgumentException(sprintf( 'Value needs to be an integer, got "%s".', \is_object($hash) ? \get_class($hash) : \gettype($hash) )); } $this->hashes[$file] = $hash; } public function clear($file) { unset($this->hashes[$file]); } public function toJson() { $json = json_encode([ 'php' => $this->getSignature()->getPhpVersion(), 'version' => $this->getSignature()->getFixerVersion(), 'rules' => $this->getSignature()->getRules(), 'hashes' => $this->hashes, ]); if (JSON_ERROR_NONE !== json_last_error()) { throw new \UnexpectedValueException(sprintf( 'Can not encode cache signature to JSON, error: "%s". If you have non-UTF8 chars in your signature, like in license for `header_comment`, consider enabling `ext-mbstring` or install `symfony/polyfill-mbstring`.', json_last_error_msg() )); } return $json; } public static function fromJson($json) { $data = json_decode($json, true); if (null === $data && JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException(sprintf( 'Value needs to be a valid JSON string, got "%s", error: "%s".', \is_object($json) ? \get_class($json) : \gettype($json), json_last_error_msg() )); } $requiredKeys = [ 'php', 'version', 'rules', 'hashes', ]; $missingKeys = array_diff_key(array_flip($requiredKeys), $data); if (\count($missingKeys)) { throw new \InvalidArgumentException(sprintf( 'JSON data is missing keys "%s"', implode('", "', $missingKeys) )); } $signature = new Signature( $data['php'], $data['version'], $data['rules'] ); $cache = new self($signature); $cache->hashes = $data['hashes']; return $cache; } } directoryName = $directoryName; } public function getRelativePathTo($file) { $file = $this->normalizePath($file); if ( '' === $this->directoryName || 0 !== stripos($file, $this->directoryName.\DIRECTORY_SEPARATOR) ) { return $file; } return substr($file, \strlen($this->directoryName) + 1); } private function normalizePath($path) { return str_replace(['\\', '/'], \DIRECTORY_SEPARATOR, $path); } } file = $file; } public function getFile() { return $this->file; } public function read() { if (!file_exists($this->file)) { return; } $content = file_get_contents($this->file); try { $cache = Cache::fromJson($content); } catch (\InvalidArgumentException $exception) { return; } return $cache; } public function write(CacheInterface $cache) { $content = $cache->toJson(); if (file_exists($this->file)) { if (is_dir($this->file)) { throw new IOException( sprintf('Cannot write cache file "%s" as the location exists as directory.', realpath($this->file)), 0, null, $this->file ); } if (!is_writable($this->file)) { throw new IOException( sprintf('Cannot write to file "%s" as it is not writable.', realpath($this->file)), 0, null, $this->file ); } } else { @touch($this->file); @chmod($this->file, 0666); } $bytesWritten = @file_put_contents($this->file, $content); if (false === $bytesWritten) { $error = error_get_last(); throw new IOException( sprintf('Failed to write file "%s", "%s".', $this->file, isset($error['message']) ? $error['message'] : 'no reason available'), 0, null, $this->file ); } } } phpVersion = $phpVersion; $this->fixerVersion = $fixerVersion; $this->rules = self::utf8Encode($rules); } public function getPhpVersion() { return $this->phpVersion; } public function getFixerVersion() { return $this->fixerVersion; } public function getRules() { return $this->rules; } public function equals(SignatureInterface $signature) { return $this->phpVersion === $signature->getPhpVersion() && $this->fixerVersion === $signature->getFixerVersion() && $this->rules === $signature->getRules(); } private static function utf8Encode(array $data) { if (!\function_exists('mb_detect_encoding')) { return $data; } array_walk_recursive($data, static function (&$item) { if (\is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { $item = utf8_encode($item); } }); return $data; } } isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $argumentsAnalyzer = new ArgumentsAnalyzer(); $index = 0; $end = $tokens->count() - 1; while (true) { $candidate = $this->find('fopen', $tokens, $index, $end); if (null === $candidate) { break; } $index = $candidate[1]; $arguments = $argumentsAnalyzer->getArguments( $tokens, $index, $candidate[2] ); $argumentsCount = \count($arguments); if ($argumentsCount < 2 || $argumentsCount > 4) { continue; } $argumentStartIndex = array_keys($arguments)[1]; $this->fixFopenFlagToken( $tokens, $argumentStartIndex, $arguments[$argumentStartIndex] ); } } abstract protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex); protected function isValidModeString($mode) { $modeLength = \strlen($mode); if ($modeLength < 1 || $modeLength > 13) { return false; } $validFlags = [ 'a' => true, 'b' => true, 'c' => true, 'e' => true, 'r' => true, 't' => true, 'w' => true, 'x' => true, ]; if (!isset($validFlags[$mode[0]])) { return false; } unset($validFlags[$mode[0]]); for ($i = 1; $i < $modeLength; ++$i) { if (isset($validFlags[$mode[$i]])) { unset($validFlags[$mode[$i]]); continue; } if ('+' !== $mode[$i] || ( 'a' !== $mode[$i - 1] && 'c' !== $mode[$i - 1] && 'r' !== $mode[$i - 1] && 'w' !== $mode[$i - 1] && 'x' !== $mode[$i - 1] ) ) { return false; } } return true; } } $minimum)) { throw new \InvalidArgumentException('Minimum needs to be either null or an integer greater than 0.'); } if (null !== $maximum) { if (!\is_int($maximum) || 1 > $maximum) { throw new \InvalidArgumentException('Maximum needs to be either null or an integer greater than 0.'); } if (null !== $minimum && $maximum < $minimum) { throw new \InvalidArgumentException('Maximum should not be lower than the minimum.'); } } $this->minimum = $minimum; $this->maximum = $maximum; } public function isSatisfiedBy($version) { if (null !== $this->minimum && $version < $this->minimum) { return false; } if (null !== $this->maximum && $version > $this->maximum) { return false; } return true; } } codeSample = new CodeSample($code, $configuration); $this->splFileInfo = $splFileInfo; } public function getCode() { return $this->codeSample->getCode(); } public function getConfiguration() { return $this->codeSample->getConfiguration(); } public function getSplFileInfo() { return $this->splFileInfo; } } code = $code; $this->configuration = $configuration; } public function getCode() { return $this->code; } public function getConfiguration() { return $this->configuration; } } codeSample = new CodeSample($code, $configuration); $this->versionSpecification = $versionSpecification; } public function getCode() { return $this->codeSample->getCode(); } public function getConfiguration() { return $this->codeSample->getConfiguration(); } public function isSuitableFor($version) { return $this->versionSpecification->isSatisfiedBy($version); } } summary = $summary; $this->codeSamples = $codeSamples; $this->description = $description; $this->configurationDescription = $configurationDescription; $this->defaultConfiguration = $defaultConfiguration; $this->riskyDescription = $riskyDescription; } public function getSummary() { return $this->summary; } public function getDescription() { return $this->description; } public function getConfigurationDescription() { @trigger_error(sprintf('%s is deprecated and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); return $this->configurationDescription; } public function getDefaultConfiguration() { @trigger_error(sprintf('%s is deprecated and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); return $this->defaultConfiguration; } public function getRiskyDescription() { return $this->riskyDescription; } public function getCodeSamples() { return $this->codeSamples; } } type = $type; $this->filePath = $filePath; $this->source = $source; $this->appliedFixers = $appliedFixers; $this->diff = $diff; } public function getFilePath() { return $this->filePath; } public function getSource() { return $this->source; } public function getType() { return $this->type; } public function getAppliedFixers() { return $this->appliedFixers; } public function getDiff() { return $this->diff; } } errors, static function (Error $error) { return Error::TYPE_INVALID === $error->getType(); }); } public function getExceptionErrors() { return array_filter($this->errors, static function (Error $error) { return Error::TYPE_EXCEPTION === $error->getType(); }); } public function getLintErrors() { return array_filter($this->errors, static function (Error $error) { return Error::TYPE_LINT === $error->getType(); }); } public function isEmpty() { return empty($this->errors); } public function report(Error $error) { $this->errors[] = $error; } } [ 'encoding' => true, 'full_opening_tag' => true, ], '@PSR2' => [ '@PSR1' => true, 'blank_line_after_namespace' => true, 'braces' => true, 'class_definition' => true, 'elseif' => true, 'function_declaration' => true, 'indentation_type' => true, 'line_ending' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 'no_break_comment' => true, 'no_closing_tag' => true, 'no_spaces_after_function_name' => true, 'no_spaces_inside_parenthesis' => true, 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'single_blank_line_at_eof' => true, 'single_class_element_per_statement' => ['elements' => ['property']], 'single_import_per_statement' => true, 'single_line_after_imports' => true, 'switch_case_semicolon_to_colon' => true, 'switch_case_space' => true, 'visibility_required' => true, ], '@Symfony' => [ '@PSR2' => true, 'binary_operator_spaces' => true, 'blank_line_after_opening_tag' => true, 'blank_line_before_statement' => [ 'statements' => ['return'], ], 'braces' => [ 'allow_single_line_closure' => true, ], 'cast_spaces' => true, 'class_attributes_separation' => ['elements' => ['method']], 'class_definition' => ['single_line' => true], 'concat_space' => ['spacing' => 'none'], 'declare_equal_normalize' => true, 'function_typehint_space' => true, 'include' => true, 'increment_style' => true, 'lowercase_cast' => true, 'lowercase_static_reference' => true, 'magic_constant_casing' => true, 'magic_method_casing' => true, 'method_argument_space' => true, 'native_function_casing' => true, 'new_with_braces' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_blank_lines' => ['tokens' => [ 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'throw', 'use', ]], 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => ['use' => 'echo'], 'no_multiline_whitespace_around_double_arrow' => true, 'no_short_bool_cast' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_around_offset' => true, 'no_trailing_comma_in_list_call' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_unneeded_control_parentheses' => true, 'no_unneeded_curly_braces' => true, 'no_unneeded_final_method' => true, 'no_unused_imports' => true, 'no_whitespace_before_comma_in_array' => true, 'no_whitespace_in_blank_line' => true, 'normalize_index_brace' => true, 'object_operator_without_whitespace' => true, 'php_unit_fqcn_annotation' => true, 'phpdoc_align' => [ 'tags' => [ 'method', 'param', 'property', 'return', 'throws', 'type', 'var', ], ], 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, 'phpdoc_inline_tag' => true, 'phpdoc_no_access' => true, 'phpdoc_no_alias_tag' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_no_package' => true, 'phpdoc_no_useless_inheritdoc' => true, 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, 'phpdoc_separation' => true, 'phpdoc_single_line_var_spacing' => true, 'phpdoc_summary' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_types' => true, 'phpdoc_types_order' => [ 'null_adjustment' => 'always_last', 'sort_algorithm' => 'none', ], 'phpdoc_var_without_name' => true, 'protected_to_private' => true, 'return_type_declaration' => true, 'semicolon_after_instruction' => true, 'short_scalar_cast' => true, 'single_blank_line_before_namespace' => true, 'single_class_element_per_statement' => true, 'single_line_comment_style' => [ 'comment_types' => ['hash'], ], 'single_quote' => true, 'space_after_semicolon' => [ 'remove_in_empty_for_expressions' => true, ], 'standardize_increment' => true, 'standardize_not_equals' => true, 'ternary_operator_spaces' => true, 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => true, ], '@Symfony:risky' => [ 'dir_constant' => true, 'ereg_to_preg' => true, 'error_suppression' => true, 'fopen_flag_order' => true, 'fopen_flags' => ['b_mode' => false], 'function_to_constant' => true, 'implode_call' => true, 'is_null' => true, 'modernize_types_casting' => true, 'native_constant_invocation' => [ 'fix_built_in' => false, 'include' => [ 'DIRECTORY_SEPARATOR', 'PHP_SAPI', 'PHP_VERSION_ID', ], 'scope' => 'namespaced', ], 'native_function_invocation' => [ 'include' => [NativeFunctionInvocationFixer::SET_COMPILER_OPTIMIZED], 'scope' => 'namespaced', 'strict' => true, ], 'no_alias_functions' => true, 'no_homoglyph_names' => true, 'non_printable_character' => [ 'use_escape_sequences_in_strings' => false, ], 'php_unit_construct' => true, 'psr4' => true, 'self_accessor' => true, 'set_type_to_cast' => true, ], '@PhpCsFixer' => [ '@Symfony' => true, 'align_multiline_comment' => true, 'array_indentation' => true, 'array_syntax' => ['syntax' => 'short'], 'blank_line_before_statement' => true, 'combine_consecutive_issets' => true, 'combine_consecutive_unsets' => true, 'compact_nullable_typehint' => true, 'escape_implicit_backslashes' => true, 'explicit_indirect_variable' => true, 'explicit_string_variable' => true, 'fully_qualified_strict_types' => true, 'heredoc_to_nowdoc' => true, 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 'method_chaining_indentation' => true, 'multiline_comment_opening_closing' => true, 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], 'no_alternative_syntax' => true, 'no_binary_string' => true, 'no_extra_blank_lines' => ['tokens' => [ 'break', 'continue', 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'throw', 'use', ]], 'no_null_property_initialization' => true, 'no_short_echo_tag' => true, 'no_superfluous_elseif' => true, 'no_unneeded_curly_braces' => true, 'no_unneeded_final_method' => true, 'no_unset_cast' => true, 'no_useless_else' => true, 'no_useless_return' => true, 'ordered_class_elements' => true, 'ordered_imports' => true, 'php_unit_internal_class' => true, 'php_unit_method_casing' => true, 'php_unit_ordered_covers' => true, 'php_unit_test_class_requires_covers' => true, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_order' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_types_order' => true, 'phpdoc_var_annotation_correct_order' => true, 'return_assignment' => true, 'single_line_comment_style' => true, ], '@PhpCsFixer:risky' => [ '@Symfony:risky' => true, 'comment_to_phpdoc' => true, 'final_internal_class' => true, 'function_to_constant' => ['functions' => [ 'get_called_class', 'get_class', 'php_sapi_name', 'phpversion', 'pi', ]], 'logical_operators' => true, 'no_unreachable_default_argument_value' => true, 'no_unset_on_property' => true, 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_strict' => true, 'php_unit_test_annotation' => true, 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'strict_comparison' => true, 'strict_param' => true, 'string_line_ending' => true, ], '@DoctrineAnnotation' => [ 'doctrine_annotation_array_assignment' => [ 'operator' => ':', ], 'doctrine_annotation_braces' => true, 'doctrine_annotation_indentation' => true, 'doctrine_annotation_spaces' => [ 'before_array_assignments_colon' => false, ], ], '@PHP56Migration' => [], '@PHP56Migration:risky' => [ 'pow_to_exponentiation' => true, ], '@PHP70Migration' => [ '@PHP56Migration' => true, 'ternary_to_null_coalescing' => true, ], '@PHP70Migration:risky' => [ '@PHP56Migration:risky' => true, 'combine_nested_dirname' => true, 'declare_strict_types' => true, 'non_printable_character' => [ 'use_escape_sequences_in_strings' => true, ], 'random_api_migration' => ['replacements' => [ 'mt_rand' => 'random_int', 'rand' => 'random_int', ]], ], '@PHP71Migration' => [ '@PHP70Migration' => true, 'visibility_required' => ['elements' => [ 'const', 'method', 'property', ]], ], '@PHP71Migration:risky' => [ '@PHP70Migration:risky' => true, 'void_return' => true, ], '@PHPUnit30Migration:risky' => [ 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_3_0], ], '@PHPUnit32Migration:risky' => [ '@PHPUnit30Migration:risky' => true, 'php_unit_no_expectation_annotation' => ['target' => PhpUnitTargetVersion::VERSION_3_2], ], '@PHPUnit35Migration:risky' => [ '@PHPUnit32Migration:risky' => true, 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_3_5], ], '@PHPUnit43Migration:risky' => [ '@PHPUnit35Migration:risky' => true, 'php_unit_no_expectation_annotation' => ['target' => PhpUnitTargetVersion::VERSION_4_3], ], '@PHPUnit48Migration:risky' => [ '@PHPUnit43Migration:risky' => true, 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_4_8], ], '@PHPUnit50Migration:risky' => [ '@PHPUnit48Migration:risky' => true, 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_5_0], ], '@PHPUnit52Migration:risky' => [ '@PHPUnit50Migration:risky' => true, 'php_unit_expectation' => ['target' => PhpUnitTargetVersion::VERSION_5_2], ], '@PHPUnit54Migration:risky' => [ '@PHPUnit52Migration:risky' => true, 'php_unit_mock' => ['target' => PhpUnitTargetVersion::VERSION_5_4], ], '@PHPUnit55Migration:risky' => [ '@PHPUnit54Migration:risky' => true, 'php_unit_mock' => ['target' => PhpUnitTargetVersion::VERSION_5_5], ], '@PHPUnit56Migration:risky' => [ '@PHPUnit55Migration:risky' => true, 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_5_6], 'php_unit_expectation' => ['target' => PhpUnitTargetVersion::VERSION_5_6], ], '@PHPUnit57Migration:risky' => [ '@PHPUnit56Migration:risky' => true, 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_5_7], ], '@PHPUnit60Migration:risky' => [ '@PHPUnit57Migration:risky' => true, 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_6_0], ], ]; private $set; private $rules; public function __construct(array $set = []) { foreach ($set as $key => $value) { if (\is_int($key)) { throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value)); } } $this->set = $set; $this->resolveSet(); } public static function create(array $set = []) { return new self($set); } public function hasRule($rule) { return \array_key_exists($rule, $this->rules); } public function getRuleConfiguration($rule) { if (!$this->hasRule($rule)) { throw new \InvalidArgumentException(sprintf('Rule "%s" is not in the set.', $rule)); } if (true === $this->rules[$rule]) { return null; } return $this->rules[$rule]; } public function getRules() { return $this->rules; } public function getSetDefinitionNames() { return array_keys($this->setDefinitions); } private function getSetDefinition($name) { if (!isset($this->setDefinitions[$name])) { throw new \InvalidArgumentException(sprintf('Set "%s" does not exist.', $name)); } return $this->setDefinitions[$name]; } private function resolveSet() { $rules = $this->set; $resolvedRules = []; foreach ($rules as $name => $value) { if ('@' === $name[0]) { if (!\is_bool($value)) { throw new \UnexpectedValueException(sprintf('Nested rule set "%s" configuration must be a boolean.', $name)); } $set = $this->resolveSubset($name, $value); $resolvedRules = array_merge($resolvedRules, $set); } else { $resolvedRules[$name] = $value; } } $resolvedRules = array_filter($resolvedRules); $this->rules = $resolvedRules; return $this; } private function resolveSubset($setName, $setValue) { $rules = $this->getSetDefinition($setName); foreach ($rules as $name => $value) { if ('@' === $name[0]) { $set = $this->resolveSubset($name, $setValue); unset($rules[$name]); $rules = array_merge($rules, $set); } elseif (!$setValue) { $rules[$name] = false; } else { $rules[$name] = $value; } } return $rules; } } tags = Annotation::getTagsWithTypes(); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType($this->tags); if (empty($annotations)) { continue; } foreach ($annotations as $annotation) { $this->fixTypes($annotation); } $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } abstract protected function normalize($type); private function fixTypes(Annotation $annotation) { $types = $annotation->getTypes(); $new = $this->normalizeTypes($types); if ($types !== $new) { $annotation->setTypes($new); } } private function normalizeTypes(array $types) { foreach ($types as $index => $type) { $types[$index] = $this->normalizeType($type); } return $types; } private function normalizeType($type) { if ('[]' === substr($type, -2)) { return $this->normalize(substr($type, 0, -2)).'[]'; } return $this->normalize($type); } } isAnyTokenKindsFound(Token::getClassyTokenKinds()); } public function isRisky() { return true; } public function getPriority() { return -10; } public function supports(\SplFileInfo $file) { if ($file instanceof StdinFileInfo) { return false; } $filenameParts = explode('.', $file->getBasename(), 2); if ( (!isset($filenameParts[1]) || 'php' !== $filenameParts[1]) || 0 === Preg::match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $filenameParts[0]) ) { return false; } try { $tokens = Tokens::fromCode(sprintf('isKeyword() || $tokens[3]->isMagicConstant()) { return false; } } catch (\ParseError $e) { return false; } return !Preg::match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath()); } } nameValidator = new FixerNameValidator(); } public static function create() { return new self(); } public function setWhitespacesConfig(WhitespacesFixerConfig $config) { foreach ($this->fixers as $fixer) { if ($fixer instanceof WhitespacesAwareFixerInterface) { $fixer->setWhitespacesConfig($config); } } return $this; } public function getFixers() { $this->fixers = Utils::sortFixers($this->fixers); return $this->fixers; } public function registerBuiltInFixers() { static $builtInFixers = null; if (null === $builtInFixers) { $builtInFixers = []; foreach (SymfonyFinder::create()->files()->in(__DIR__.'/Fixer') as $file) { $relativeNamespace = $file->getRelativePath(); $fixerClass = 'PhpCsFixer\\Fixer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); if ('Fixer' === substr($fixerClass, -5)) { $builtInFixers[] = $fixerClass; } } } foreach ($builtInFixers as $class) { $this->registerFixer(new $class(), false); } return $this; } public function registerCustomFixers(array $fixers) { foreach ($fixers as $fixer) { $this->registerFixer($fixer, true); } return $this; } public function registerFixer(FixerInterface $fixer, $isCustom) { $name = $fixer->getName(); if (isset($this->fixersByName[$name])) { throw new \UnexpectedValueException(sprintf('Fixer named "%s" is already registered.', $name)); } if (!$this->nameValidator->isValid($name, $isCustom)) { throw new \UnexpectedValueException(sprintf('Fixer named "%s" has invalid name.', $name)); } $this->fixers[] = $fixer; $this->fixersByName[$name] = $fixer; return $this; } public function useRuleSet(RuleSetInterface $ruleSet) { $fixers = []; $fixersByName = []; $fixerConflicts = []; $fixerNames = array_keys($ruleSet->getRules()); foreach ($fixerNames as $name) { if (!\array_key_exists($name, $this->fixersByName)) { throw new \UnexpectedValueException(sprintf('Rule "%s" does not exist.', $name)); } $fixer = $this->fixersByName[$name]; $config = $ruleSet->getRuleConfiguration($name); if (null !== $config) { if ($fixer instanceof ConfigurableFixerInterface) { if (!\is_array($config) || !\count($config)) { throw new InvalidFixerConfigurationException($fixer->getName(), 'Configuration must be an array and may not be empty.'); } $fixer->configure($config); } else { throw new InvalidFixerConfigurationException($fixer->getName(), 'Is not configurable.'); } } $fixers[] = $fixer; $fixersByName[$name] = $fixer; $conflicts = array_intersect($this->getFixersConflicts($fixer), $fixerNames); if (\count($conflicts) > 0) { $fixerConflicts[$name] = $conflicts; } } if (\count($fixerConflicts) > 0) { throw new \UnexpectedValueException($this->generateConflictMessage($fixerConflicts)); } $this->fixers = $fixers; $this->fixersByName = $fixersByName; return $this; } public function hasRule($name) { return isset($this->fixersByName[$name]); } private function getFixersConflicts(FixerInterface $fixer) { static $conflictMap = [ 'no_blank_lines_before_namespace' => ['single_blank_line_before_namespace'], ]; $fixerName = $fixer->getName(); return \array_key_exists($fixerName, $conflictMap) ? $conflictMap[$fixerName] : []; } private function generateConflictMessage(array $fixerConflicts) { $message = 'Rule contains conflicting fixers:'; $report = []; foreach ($fixerConflicts as $fixer => $fixers) { $report[$fixer] = array_filter( $fixers, static function ($candidate) use ($report, $fixer) { return !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true); } ); if (\count($report[$fixer]) > 0) { $message .= sprintf("\n- \"%s\" with \"%s\"", $fixer, implode('", "', $report[$fixer])); } } return $message; } } fixerName = $fixerName; } public function getFixerName() { return $this->fixerName; } } type = $type; $this->content = $content; } public function getType() { return $this->type; } public function setType($type) { $this->type = $type; } public function getContent() { return $this->content; } public function setContent($content) { $this->content = $content; } public function isType($types) { if (!\is_array($types)) { $types = [$types]; } return \in_array($this->getType(), $types, true); } public function clear() { $this->setContent(''); } } isGivenKind(T_DOC_COMMENT)) { throw new \InvalidArgumentException('Input must be a T_DOC_COMMENT token.'); } $tokens = new self(); $content = $input->getContent(); $ignoredTextPosition = 0; $currentPosition = 0; while (false !== $nextAtPosition = strpos($content, '@', $currentPosition)) { if (0 !== $nextAtPosition && !Preg::match('/\s/', $content[$nextAtPosition - 1])) { $currentPosition = $nextAtPosition + 1; continue; } $lexer = new DocLexer(); $lexer->setInput(substr($content, $nextAtPosition)); $scannedTokens = []; $index = 0; $nbScannedTokensToUse = 0; $nbScopes = 0; while (null !== $token = $lexer->peek()) { if (0 === $index && DocLexer::T_AT !== $token['type']) { break; } if (1 === $index) { if (DocLexer::T_IDENTIFIER !== $token['type'] || \in_array($token['value'], $ignoredTags, true)) { break; } $nbScannedTokensToUse = 2; } if ($index >= 2 && 0 === $nbScopes && !\in_array($token['type'], [DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS], true)) { break; } $scannedTokens[] = $token; if (DocLexer::T_OPEN_PARENTHESIS === $token['type']) { ++$nbScopes; } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token['type']) { if (0 === --$nbScopes) { $nbScannedTokensToUse = \count($scannedTokens); break; } } ++$index; } if (0 !== $nbScopes) { break; } if (0 !== $nbScannedTokensToUse) { $ignoredTextLength = $nextAtPosition - $ignoredTextPosition; if (0 !== $ignoredTextLength) { $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition, $ignoredTextLength)); } $lastTokenEndIndex = 0; foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) { if (DocLexer::T_STRING === $token['type']) { $token['value'] = '"'.str_replace('"', '""', $token['value']).'"'; } $missingTextLength = $token['position'] - $lastTokenEndIndex; if ($missingTextLength > 0) { $tokens[] = new Token(DocLexer::T_NONE, substr( $content, $nextAtPosition + $lastTokenEndIndex, $missingTextLength )); } $tokens[] = new Token($token['type'], $token['value']); $lastTokenEndIndex = $token['position'] + \strlen($token['value']); } $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + \strlen($token['value']); } else { $currentPosition = $nextAtPosition + 1; } } if ($ignoredTextPosition < \strlen($content)) { $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition)); } return $tokens; } public function getNextMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, 1); } public function getPreviousMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, -1); } public function getNextTokenOfType($type, $index) { return $this->getTokenOfTypeSibling($index, $type, 1); } public function getPreviousTokenOfType($type, $index) { return $this->getTokenOfTypeSibling($index, $type, -1); } public function getAnnotationEnd($index) { $currentIndex = null; if (isset($this[$index + 2])) { if ($this[$index + 2]->isType(DocLexer::T_OPEN_PARENTHESIS)) { $currentIndex = $index + 2; } elseif ( isset($this[$index + 3]) && $this[$index + 2]->isType(DocLexer::T_NONE) && $this[$index + 3]->isType(DocLexer::T_OPEN_PARENTHESIS) && Preg::match('/^(\R\s*\*\s*)*\s*$/', $this[$index + 2]->getContent()) ) { $currentIndex = $index + 3; } } if (null !== $currentIndex) { $level = 0; for ($max = \count($this); $currentIndex < $max; ++$currentIndex) { if ($this[$currentIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { ++$level; } elseif ($this[$currentIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) { --$level; } if (0 === $level) { return $currentIndex; } } return null; } return $index + 1; } public function getArrayEnd($index) { $level = 1; for (++$index, $max = \count($this); $index < $max; ++$index) { if ($this[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) { ++$level; } elseif ($this[$index]->isType($index, DocLexer::T_CLOSE_CURLY_BRACES)) { --$level; } if (0 === $level) { return $index; } } return null; } public function getCode() { $code = ''; foreach ($this as $token) { $code .= $token->getContent(); } return $code; } public function insertAt($index, Token $token) { $this->setSize($this->getSize() + 1); for ($i = $this->getSize() - 1; $i > $index; --$i) { $this[$i] = isset($this[$i - 1]) ? $this[$i - 1] : new Token(); } $this[$index] = $token; } public function offsetSet($index, $token) { if (!$token instanceof Token) { $type = \gettype($token); if ('object' === $type) { $type = \get_class($token); } throw new \InvalidArgumentException(sprintf( 'Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, %s given.', $type )); } if (null === $index) { $index = \count($this); $this->setSize($this->getSize() + 1); } parent::offsetSet($index, $token); } public function offsetUnset($index) { if (!isset($this[$index])) { throw new \OutOfBoundsException(sprintf('Index %s is invalid or does not exist.', $index)); } $max = \count($this) - 1; while ($index < $max) { $this[$index] = $this[$index + 1]; ++$index; } parent::offsetUnset($index); $this->setSize($max); } private function getMeaningfulTokenSibling($index, $direction) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { break; } if (!$this[$index]->isType(DocLexer::T_NONE)) { return $index; } } return null; } private function getTokenOfTypeSibling($index, $type, $direction) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { break; } if ($this[$index]->isType($type)) { return $index; } } return null; } } line = $line; } public function getName() { if (null === $this->name) { Preg::matchAll('/@[a-zA-Z0-9_-]+(?=\s|$)/', $this->line->getContent(), $matches); if (isset($matches[0][0])) { $this->name = ltrim($matches[0][0], '@'); } else { $this->name = 'other'; } } return $this->name; } public function setName($name) { $current = $this->getName(); if ('other' === $current) { throw new \RuntimeException('Cannot set name on unknown tag.'); } $this->line->setContent(Preg::replace("/@${current}/", "@${name}", $this->line->getContent(), 1)); $this->name = $name; } public function valid() { return \in_array($this->getName(), self::$tags, true); } } is any non-array, non-generic, non-alternated type, eg `int` or `\Foo` # is array of , eg `int[]` or `\Foo[]` # is generic collection type, like `array`, `Collection` and more complex like `Collection>` # is , or type, like `int`, `bool[]` or `Collection` # is one or more types alternated via `|`, like `int|bool[]|Collection` (? (? (? (?&simple)\[\] ) | (? [@$?]?[\\\\\w]+ ) | (? (?&simple) < (?:(?&types),\s*)?(?:(?&types)|(?&generic)) > ) ) (?: \| (?:(?&simple)|(?&array)|(?&generic)) )* ) '; private static $tags = [ 'method', 'param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var', ]; private $lines; private $start; private $end; private $tag; private $typesContent; private $types; public function __construct(array $lines) { $this->lines = array_values($lines); $keys = array_keys($lines); $this->start = $keys[0]; $this->end = end($keys); } public function __toString() { return $this->getContent(); } public static function getTagsWithTypes() { return self::$tags; } public function getStart() { return $this->start; } public function getEnd() { return $this->end; } public function getTag() { if (null === $this->tag) { $this->tag = new Tag($this->lines[0]); } return $this->tag; } public function getTypes() { if (null === $this->types) { $this->types = []; $content = $this->getTypesContent(); while ('' !== $content && false !== $content) { Preg::match( '{^'.self::REGEX_TYPES.'$}x', $content, $matches ); $this->types[] = $matches['type']; $content = substr($content, \strlen($matches['type']) + 1); } } return $this->types; } public function setTypes(array $types) { $pattern = '/'.preg_quote($this->getTypesContent(), '/').'/'; $this->lines[0]->setContent(Preg::replace($pattern, implode('|', $types), $this->lines[0]->getContent(), 1)); $this->clearCache(); } public function getNormalizedTypes() { $normalized = array_map(static function ($type) { return strtolower($type); }, $this->getTypes()); sort($normalized); return $normalized; } public function remove() { foreach ($this->lines as $line) { $line->remove(); } $this->clearCache(); } public function getContent() { return implode('', $this->lines); } public function supportTypes() { return \in_array($this->getTag()->getName(), self::$tags, true); } private function getTypesContent() { if (null === $this->typesContent) { $name = $this->getTag()->getName(); if (!$this->supportTypes()) { throw new \RuntimeException('This tag does not support types.'); } $matchingResult = Preg::match( '{^(?:\s*\*|/\*\*)\s*@'.$name.'\s+'.self::REGEX_TYPES.'(?:[ \t].*)?$}sx', $this->lines[0]->getContent(), $matches ); $this->typesContent = 1 === $matchingResult ? $matches['types'] : ''; } return $this->typesContent; } private function clearCache() { $this->types = null; $this->typesContent = null; } } lines[] = new Line($line); } } public function __toString() { return $this->getContent(); } public function getLines() { return $this->lines; } public function getLine($pos) { if (isset($this->lines[$pos])) { return $this->lines[$pos]; } } public function getAnnotations() { if (null === $this->annotations) { $this->annotations = []; $total = \count($this->lines); for ($index = 0; $index < $total; ++$index) { if ($this->lines[$index]->containsATag()) { $lines = \array_slice($this->lines, $index, $this->findAnnotationLength($index), true); $annotation = new Annotation($lines); $index = $annotation->getEnd(); $this->annotations[] = $annotation; } } } return $this->annotations; } public function getAnnotation($pos) { $annotations = $this->getAnnotations(); if (isset($annotations[$pos])) { return $annotations[$pos]; } } public function getAnnotationsOfType($types) { $annotations = []; $types = (array) $types; foreach ($this->getAnnotations() as $annotation) { $tag = $annotation->getTag()->getName(); foreach ($types as $type) { if ($type === $tag) { $annotations[] = $annotation; } } } return $annotations; } public function getContent() { return implode('', $this->lines); } private function findAnnotationLength($start) { $index = $start; while ($line = $this->getLine(++$index)) { if ($line->containsATag()) { break; } if (!$line->containsUsefulContent()) { $next = $this->getLine($index + 1); if (null === $next || !$next->containsUsefulContent() || $next->containsATag()) { break; } } } return $index - $start; } } getName(); $secondName = $second->getName(); if ($firstName === $secondName) { return true; } foreach (self::$groups as $group) { if (\in_array($firstName, $group, true) && \in_array($secondName, $group, true)) { return true; } } return false; } } doc = $doc; } public function getEnd() { $reachedContent = false; foreach ($this->doc->getLines() as $index => $line) { if ($reachedContent && ($line->containsATag() || !$line->containsUsefulContent())) { return $index - 1; } if ($line->containsATag()) { return null; } if ($line->containsUsefulContent()) { $reachedContent = true; } } } } content = $content; } public function __toString() { return $this->content; } public function getContent() { return $this->content; } public function containsUsefulContent() { return 0 !== Preg::match('/\\*\s*\S+/', $this->content) && !$this->isTheStart() && !$this->isTheEnd(); } public function containsATag() { return 0 !== Preg::match('/\\*\s*@/', $this->content); } public function isTheStart() { return false !== strpos($this->content, '/**'); } public function isTheEnd() { return false !== strpos($this->content, '*/'); } public function setContent($content) { $this->content = $content; } public function remove() { $this->content = ''; } public function addBlank() { $matched = Preg::match('/^([ \t]*\*)[^\r\n]*(\r?\n)$/', $this->content, $matches); if (1 !== $matched) { return; } $this->content .= $matches[1].$matches[2]; } } true]; private $usingCache = true; public function __construct($name = 'default') { $this->name = $name; } public static function create() { return new static(); } public function getCacheFile() { return $this->cacheFile; } public function getCustomFixers() { return $this->customFixers; } public function getFinder() { if (null === $this->finder) { $this->finder = new Finder(); } return $this->finder; } public function getFormat() { return $this->format; } public function getHideProgress() { return $this->hideProgress; } public function getIndent() { return $this->indent; } public function getLineEnding() { return $this->lineEnding; } public function getName() { return $this->name; } public function getPhpExecutable() { return $this->phpExecutable; } public function getRiskyAllowed() { return $this->isRiskyAllowed; } public function getRules() { return $this->rules; } public function getUsingCache() { return $this->usingCache; } public function registerCustomFixers($fixers) { if (false === \is_array($fixers) && false === $fixers instanceof \Traversable) { throw new \InvalidArgumentException(sprintf( 'Argument must be an array or a Traversable, got "%s".', \is_object($fixers) ? \get_class($fixers) : \gettype($fixers) )); } foreach ($fixers as $fixer) { $this->addCustomFixer($fixer); } return $this; } public function setCacheFile($cacheFile) { $this->cacheFile = $cacheFile; return $this; } public function setFinder($finder) { if (false === \is_array($finder) && false === $finder instanceof \Traversable) { throw new \InvalidArgumentException(sprintf( 'Argument must be an array or a Traversable, got "%s".', \is_object($finder) ? \get_class($finder) : \gettype($finder) )); } $this->finder = $finder; return $this; } public function setFormat($format) { $this->format = $format; return $this; } public function setHideProgress($hideProgress) { $this->hideProgress = $hideProgress; return $this; } public function setIndent($indent) { $this->indent = $indent; return $this; } public function setLineEnding($lineEnding) { $this->lineEnding = $lineEnding; return $this; } public function setPhpExecutable($phpExecutable) { $this->phpExecutable = $phpExecutable; return $this; } public function setRiskyAllowed($isRiskyAllowed) { $this->isRiskyAllowed = $isRiskyAllowed; return $this; } public function setRules(array $rules) { $this->rules = $rules; return $this; } public function setUsingCache($usingCache) { $this->usingCache = $usingCache; return $this; } private function addCustomFixer(FixerInterface $fixer) { $this->customFixers[] = $fixer; } } isGivenKind(T_CLASS)) { throw new \LogicException(sprintf('No T_CLASS at given index %d, got %s.', $index, $tokens[$index]->getName())); } $index = $tokens->getNextMeaningfulToken($index); if (0 !== Preg::match('/(?:Test|TestCase)$/', $tokens[$index]->getContent())) { return true; } while (null !== $index = $tokens->getNextMeaningfulToken($index)) { if ($tokens[$index]->equals('{')) { break; } if (!$tokens[$index]->isGivenKind(T_STRING)) { continue; } if (0 !== Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { return true; } } return false; } public function findPhpUnitClasses(Tokens $tokens, $beginAtBottom = true) { $direction = $beginAtBottom ? -1 : 1; for ($index = 1 === $direction ? 0 : $tokens->count() - 1; $tokens->offsetExists($index); $index += $direction) { if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isPhpUnitClass($tokens, $index)) { continue; } $startIndex = $tokens->getNextTokenOfKind($index, ['{'], false); if (null === $startIndex) { return; } $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); yield [$startIndex, $endIndex]; } } } indent = $indent; $this->lineEnding = $lineEnding; } public function getIndent() { return $this->indent; } public function getLineEnding() { return $this->lineEnding; } } getRealPath(); } public function getRealPath() { return 'php://stdin'; } public function getATime() { return 0; } public function getBasename($suffix = null) { return $this->getFilename(); } public function getCTime() { return 0; } public function getExtension() { return '.php'; } public function getFileInfo($className = null) { throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); } public function getFilename() { return 'stdin.php'; } public function getGroup() { return 0; } public function getInode() { return 0; } public function getLinkTarget() { return ''; } public function getMTime() { return 0; } public function getOwner() { return 0; } public function getPath() { return ''; } public function getPathInfo($className = null) { throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); } public function getPathname() { return $this->getFilename(); } public function getPerms() { return 0; } public function getSize() { return 0; } public function getType() { return 'file'; } public function isDir() { return false; } public function isExecutable() { return false; } public function isFile() { return true; } public function isLink() { return false; } public function isReadable() { return true; } public function isWritable() { return false; } public function openFile($openMode = 'r', $useIncludePath = false, $context = null) { throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); } public function setFileClass($className = null) { } public function setInfoClass($className = null) { } } injectAlignmentPlaceholders($tokensClone, 0, \count($tokens)); $content = $this->replacePlaceholder($tokensClone); $tokens->setCode($content); } abstract protected function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt); protected function replacePlaceholder(Tokens $tokens) { $tmpCode = $tokens->generateCode(); for ($j = 0; $j <= $this->deepestLevel; ++$j) { $placeholder = sprintf(self::ALIGNABLE_PLACEHOLDER, $j); if (false === strpos($tmpCode, $placeholder)) { continue; } $lines = explode("\n", $tmpCode); $linesWithPlaceholder = []; $blockSize = 0; $linesWithPlaceholder[$blockSize] = []; foreach ($lines as $index => $line) { if (substr_count($line, $placeholder) > 0) { $linesWithPlaceholder[$blockSize][] = $index; } else { ++$blockSize; $linesWithPlaceholder[$blockSize] = []; } } foreach ($linesWithPlaceholder as $group) { if (\count($group) < 1) { continue; } $rightmostSymbol = 0; foreach ($group as $index) { $rightmostSymbol = max($rightmostSymbol, strpos(utf8_decode($lines[$index]), $placeholder)); } foreach ($group as $index) { $line = $lines[$index]; $currentSymbol = strpos(utf8_decode($line), $placeholder); $delta = abs($rightmostSymbol - $currentSymbol); if ($delta > 0) { $line = str_replace($placeholder, str_repeat(' ', $delta).$placeholder, $line); $lines[$index] = $line; } } } $tmpCode = str_replace($placeholder, '', implode("\n", $lines)); } return $tmpCode; } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } abstract public function log($level, $message, array $context = array()); } logger = $logger; } } $vendorDir . '/paragonie/random_compat/lib/random.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', ); __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( 'Symfony\\Polyfill\\Php72\\' => 23, 'Symfony\\Polyfill\\Php70\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Component\\Stopwatch\\' => 28, 'Symfony\\Component\\Process\\' => 26, 'Symfony\\Component\\OptionsResolver\\' => 34, 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\Filesystem\\' => 29, 'Symfony\\Component\\EventDispatcher\\' => 34, 'Symfony\\Component\\Debug\\' => 24, 'Symfony\\Component\\Console\\' => 26, ), 'P' => array ( 'Psr\\Log\\' => 8, 'PhpCsFixer\\' => 11, ), 'D' => array ( 'Doctrine\\Common\\Annotations\\' => 28, ), 'C' => array ( 'Composer\\XdebugHandler\\' => 23, 'Composer\\Semver\\' => 16, ), ); public static $prefixDirsPsr4 = array ( 'Symfony\\Polyfill\\Php72\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', ), 'Symfony\\Polyfill\\Php70\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), 'Symfony\\Component\\Stopwatch\\' => array ( 0 => __DIR__ . '/..' . '/symfony/stopwatch', ), 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), 'Symfony\\Component\\OptionsResolver\\' => array ( 0 => __DIR__ . '/..' . '/symfony/options-resolver', ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', ), 'Symfony\\Component\\Filesystem\\' => array ( 0 => __DIR__ . '/..' . '/symfony/filesystem', ), 'Symfony\\Component\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', ), 'Symfony\\Component\\Debug\\' => array ( 0 => __DIR__ . '/..' . '/symfony/debug', ), 'Symfony\\Component\\Console\\' => array ( 0 => __DIR__ . '/..' . '/symfony/console', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), 'PhpCsFixer\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), 'Doctrine\\Common\\Annotations\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', ), 'Composer\\XdebugHandler\\' => array ( 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', ), 'Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), ); public static $prefixesPsr0 = array ( 'D' => array ( 'Doctrine\\Common\\Lexer\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/lexer/lib', ), ), ); public static $classMap = array ( 'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', 'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php', 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v1_4\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Chunk.php', 'PhpCsFixer\\Diff\\v1_4\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Diff.php', 'PhpCsFixer\\Diff\\v1_4\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Differ.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', 'PhpCsFixer\\Diff\\v1_4\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Line.php', 'PhpCsFixer\\Diff\\v1_4\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Parser.php', 'PhpCsFixer\\Diff\\v2_0\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Chunk.php', 'PhpCsFixer\\Diff\\v2_0\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Diff.php', 'PhpCsFixer\\Diff\\v2_0\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Differ.php', 'PhpCsFixer\\Diff\\v2_0\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Exception/Exception.php', 'PhpCsFixer\\Diff\\v2_0\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php', 'PhpCsFixer\\Diff\\v2_0\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Line.php', 'PhpCsFixer\\Diff\\v2_0\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v2_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Parser.php', 'PhpCsFixer\\Diff\\v2_0\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Chunk.php', 'PhpCsFixer\\Diff\\v3_0\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php', 'PhpCsFixer\\Diff\\v3_0\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Diff.php', 'PhpCsFixer\\Diff\\v3_0\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Differ.php', 'PhpCsFixer\\Diff\\v3_0\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/Exception.php', 'PhpCsFixer\\Diff\\v3_0\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php', 'PhpCsFixer\\Diff\\v3_0\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Line.php', 'PhpCsFixer\\Diff\\v3_0\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Parser.php', 'PhpCsFixer\\Diff\\v3_0\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Tests\\TestCase' => __DIR__ . '/../..' . '/tests/TestCase.php', 'PhpCsFixer\\Tests\\Test\\AbstractFixerTestCase' => __DIR__ . '/../..' . '/tests/Test/AbstractFixerTestCase.php', 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationCaseFactory' => __DIR__ . '/../..' . '/tests/Test/AbstractIntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationTestCase' => __DIR__ . '/../..' . '/tests/Test/AbstractIntegrationTestCase.php', 'PhpCsFixer\\Tests\\Test\\Assert\\AssertTokensTrait' => __DIR__ . '/../..' . '/tests/Test/Assert/AssertTokensTrait.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCase' => __DIR__ . '/../..' . '/tests/Test/IntegrationCase.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactory' => __DIR__ . '/../..' . '/tests/Test/IntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactoryInterface' => __DIR__ . '/../..' . '/tests/Test/IntegrationCaseFactoryInterface.php', 'PhpCsFixer\\Tests\\Test\\InternalIntegrationCaseFactory' => __DIR__ . '/../..' . '/tests/Test/InternalIntegrationCaseFactory.php', 'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', 'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit25323ae44725c653331b1bdf47e322d2::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit25323ae44725c653331b1bdf47e322d2::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit25323ae44725c653331b1bdf47e322d2::$prefixesPsr0; $loader->classMap = ComposerStaticInit25323ae44725c653331b1bdf47e322d2::$classMap; }, null, ClassLoader::class); } } = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit25323ae44725c653331b1bdf47e322d2::getInitializer($loader)); } else { $map = re