Updated Rector to commit dc9573d9bd60ec966db626be91c13cc6a5c4bbef

dc9573d9bd [Php55] Make StringClassNameToClassConstantRector configurable to keep first pre-backslash string configurable (#5354)
This commit is contained in:
Tomas Votruba 2023-12-11 13:06:54 +00:00
parent d2dde21bd9
commit 39e43b6cca
21 changed files with 235 additions and 2769 deletions

View File

@ -4,6 +4,7 @@ declare (strict_types=1);
namespace Rector\Php55\Rector\String_;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name\FullyQualified;
@ -38,6 +39,14 @@ final class StringClassNameToClassConstantRector extends AbstractRector implemen
* @var string[]
*/
private $classesToSkip = [];
/**
* @var bool
*/
private $shouldKeepPreslash = \false;
/**
* @var string
*/
public const SHOULD_KEEP_PRE_SLASH = 'should_keep_pre_slash';
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
@ -70,7 +79,7 @@ class SomeClass
}
}
CODE_SAMPLE
, ['ClassName', 'AnotherClassName'])]);
, ['ClassName', 'AnotherClassName', \Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::SHOULD_KEEP_PRE_SLASH => \false])]);
}
/**
* @return array<class-string<Node>>
@ -81,7 +90,7 @@ CODE_SAMPLE
}
/**
* @param String_|FuncCall|ClassConst $node
* @return \PhpParser\Node\Expr\ClassConstFetch|null|int
* @return \PhpParser\Node\Expr\BinaryOp\Concat|\PhpParser\Node\Expr\ClassConstFetch|null|int
*/
public function refactor(Node $node)
{
@ -115,6 +124,12 @@ CODE_SAMPLE
return null;
}
$fullyQualified = new FullyQualified($classLikeName);
if ($this->shouldKeepPreslash && $classLikeName !== $node->value) {
$preSlashCount = \strlen($node->value) - \strlen($classLikeName);
$preSlash = \str_repeat('\\', $preSlashCount);
$string = new String_($preSlash);
return new Concat($string, new ClassConstFetch($fullyQualified, 'class'));
}
return new ClassConstFetch($fullyQualified, 'class');
}
/**
@ -122,6 +137,10 @@ CODE_SAMPLE
*/
public function configure(array $configuration) : void
{
if (isset($configuration[self::SHOULD_KEEP_PRE_SLASH]) && \is_bool($configuration[self::SHOULD_KEEP_PRE_SLASH])) {
$this->shouldKeepPreslash = $configuration[self::SHOULD_KEEP_PRE_SLASH];
unset($configuration[self::SHOULD_KEEP_PRE_SLASH]);
}
Assert::allString($configuration);
$this->classesToSkip = $configuration;
}

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '68c42e7e843bfb9fde2b6516bb1bb76648d67045';
public const PACKAGE_VERSION = 'dc9573d9bd60ec966db626be91c13cc6a5c4bbef';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2023-12-10 17:31:56';
public const RELEASE_DATE = '2023-12-11 14:04:32';
/**
* @var int
*/

View File

@ -1500,6 +1500,7 @@ return array(
'Rector\\DowngradePhp81\\Rector\\Property\\DowngradeReadonlyPropertyRector' => $vendorDir . '/rector/rector-downgrade-php/rules/DowngradePhp81/Rector/Property/DowngradeReadonlyPropertyRector.php',
'Rector\\DowngradePhp81\\Rector\\StmtsAwareInterface\\DowngradeSetAccessibleReflectionPropertyRector' => $vendorDir . '/rector/rector-downgrade-php/rules/DowngradePhp81/Rector/StmtsAwareInterface/DowngradeSetAccessibleReflectionPropertyRector.php',
'Rector\\DowngradePhp82\\Rector\\Class_\\DowngradeReadonlyClassRector' => $vendorDir . '/rector/rector-downgrade-php/rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php',
'Rector\\DowngradePhp82\\Rector\\FunctionLike\\DowngradeStandaloneNullTrueFalseReturnTypeRector' => $vendorDir . '/rector/rector-downgrade-php/rules/DowngradePhp82/Rector/FunctionLike/DowngradeStandaloneNullTrueFalseReturnTypeRector.php',
'Rector\\EarlyReturn\\NodeAnalyzer\\IfAndAnalyzer' => $baseDir . '/rules/EarlyReturn/NodeAnalyzer/IfAndAnalyzer.php',
'Rector\\EarlyReturn\\NodeAnalyzer\\SimpleScalarAnalyzer' => $baseDir . '/rules/EarlyReturn/NodeAnalyzer/SimpleScalarAnalyzer.php',
'Rector\\EarlyReturn\\NodeFactory\\InvertedIfFactory' => $baseDir . '/rules/EarlyReturn/NodeFactory/InvertedIfFactory.php',

View File

@ -1718,6 +1718,7 @@ class ComposerStaticInita55c41c7fa52abd86138c6f32df1d185
'Rector\\DowngradePhp81\\Rector\\Property\\DowngradeReadonlyPropertyRector' => __DIR__ . '/..' . '/rector/rector-downgrade-php/rules/DowngradePhp81/Rector/Property/DowngradeReadonlyPropertyRector.php',
'Rector\\DowngradePhp81\\Rector\\StmtsAwareInterface\\DowngradeSetAccessibleReflectionPropertyRector' => __DIR__ . '/..' . '/rector/rector-downgrade-php/rules/DowngradePhp81/Rector/StmtsAwareInterface/DowngradeSetAccessibleReflectionPropertyRector.php',
'Rector\\DowngradePhp82\\Rector\\Class_\\DowngradeReadonlyClassRector' => __DIR__ . '/..' . '/rector/rector-downgrade-php/rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php',
'Rector\\DowngradePhp82\\Rector\\FunctionLike\\DowngradeStandaloneNullTrueFalseReturnTypeRector' => __DIR__ . '/..' . '/rector/rector-downgrade-php/rules/DowngradePhp82/Rector/FunctionLike/DowngradeStandaloneNullTrueFalseReturnTypeRector.php',
'Rector\\EarlyReturn\\NodeAnalyzer\\IfAndAnalyzer' => __DIR__ . '/../..' . '/rules/EarlyReturn/NodeAnalyzer/IfAndAnalyzer.php',
'Rector\\EarlyReturn\\NodeAnalyzer\\SimpleScalarAnalyzer' => __DIR__ . '/../..' . '/rules/EarlyReturn/NodeAnalyzer/SimpleScalarAnalyzer.php',
'Rector\\EarlyReturn\\NodeFactory\\InvertedIfFactory' => __DIR__ . '/../..' . '/rules/EarlyReturn/NodeFactory/InvertedIfFactory.php',

View File

@ -701,17 +701,17 @@
},
{
"name": "nikic\/php-parser",
"version": "v4.17.1",
"version_normalized": "4.17.1.0",
"version": "v4.18.0",
"version_normalized": "4.18.0.0",
"source": {
"type": "git",
"url": "https:\/\/github.com\/nikic\/PHP-Parser.git",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/nikic\/PHP-Parser\/zipball\/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"url": "https:\/\/api.github.com\/repos\/nikic\/PHP-Parser\/zipball\/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"shasum": ""
},
"require": {
@ -722,7 +722,7 @@
"ircmaxell\/php-yacc": "^0.0.7",
"phpunit\/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"time": "2023-08-13T19:53:39+00:00",
"time": "2023-12-10T21:03:43+00:00",
"bin": [
"bin\/php-parse"
],
@ -771,7 +771,7 @@
],
"support": {
"issues": "https:\/\/github.com\/nikic\/PHP-Parser\/issues",
"source": "https:\/\/github.com\/nikic\/PHP-Parser\/tree\/v4.17.1"
"source": "https:\/\/github.com\/nikic\/PHP-Parser\/tree\/v4.18.0"
},
"install-path": "..\/nikic\/php-parser"
},
@ -1743,12 +1743,12 @@
"source": {
"type": "git",
"url": "https:\/\/github.com\/rectorphp\/rector-downgrade-php.git",
"reference": "a08ccadb84cad9df0cbf6a03007710975e8f7232"
"reference": "917085c6a2a99412440cd3143288d1a17abb5c44"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-downgrade-php\/zipball\/a08ccadb84cad9df0cbf6a03007710975e8f7232",
"reference": "a08ccadb84cad9df0cbf6a03007710975e8f7232",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-downgrade-php\/zipball\/917085c6a2a99412440cd3143288d1a17abb5c44",
"reference": "917085c6a2a99412440cd3143288d1a17abb5c44",
"shasum": ""
},
"require": {
@ -1775,7 +1775,7 @@
"tomasvotruba\/unused-public": "^0.2",
"tracy\/tracy": "^2.10"
},
"time": "2023-11-26T07:49:39+00:00",
"time": "2023-12-11T13:03:13+00:00",
"default-branch": true,
"type": "rector-extension",
"extra": {

File diff suppressed because one or more lines are too long

View File

@ -1,30 +0,0 @@
What do all those files mean?
=============================
* `php5.y`: PHP 5 grammar written in a pseudo language
* `php7.y`: PHP 7 grammar written in a pseudo language
* `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars
* `parser.template`: A `kmyacc` parser prototype file for PHP
* `tokens.template`: A `kmyacc` prototype file for the `Tokens` class
* `rebuildParsers.php`: Preprocesses the grammar and builds the parser using `kmyacc`
.phpy pseudo language
=====================
The `.y` file is a normal grammar in `kmyacc` (`yacc`) style, with some transformations
applied to it:
* Nodes are created using the syntax `Name[..., ...]`. This is transformed into
`new Name(..., ..., attributes())`
* Some function-like constructs are resolved (see `rebuildParsers.php` for a list)
Building the parser
===================
Run `php grammar/rebuildParsers.php` to rebuild the parsers. Additional options:
* The `KMYACC` environment variable can be used to specify an alternative `kmyacc` binary.
By default the `phpyacc` dev dependency will be used. To use the original `kmyacc`, you
need to compile [moriyoshi's fork](https://github.com/moriyoshi/kmyacc-forked).
* The `--debug` option enables emission of debug symbols and creates the `y.output` file.
* The `--keep-tmp-grammar` option preserves the preprocessed grammar file.

View File

@ -1,106 +0,0 @@
<?php
$meta #
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval(%n) $stackPos-(%l-%n)
#semval(%n,%t) $stackPos-(%l-%n)
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
#include;
/* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following:
* * the grammar files grammar/php5.y or grammar/php7.y
* * the skeleton file grammar/parser.template
* * the preprocessing script grammar/rebuildParsers.php
*/
class #(-p) extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = #(YYMAXLEX);
protected $actionTableSize = #(YYLAST);
protected $gotoTableSize = #(YYGLAST);
protected $invalidSymbol = #(YYBADCH);
protected $errorSymbol = #(YYINTERRTOK);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
protected $YY2TBLSTATE = #(YY2TBLSTATE);
protected $numNonLeafStates = #(YYNLSTATES);
protected $symbolToName = array(
#listvar terminals
);
protected $tokenToSymbol = array(
#listvar yytranslate
);
protected $action = array(
#listvar yyaction
);
protected $actionCheck = array(
#listvar yycheck
);
protected $actionBase = array(
#listvar yybase
);
protected $actionDefault = array(
#listvar yydefault
);
protected $goto = array(
#listvar yygoto
);
protected $gotoCheck = array(
#listvar yygcheck
);
protected $gotoBase = array(
#listvar yygbase
);
protected $gotoDefault = array(
#listvar yygdefault
);
protected $ruleToNonTerminal = array(
#listvar yylhs
);
protected $ruleToLength = array(
#listvar yylen
);
#if -t
protected $productions = array(
#production-strings;
);
#endif
protected function initReduceCallbacks() {
$this->reduceCallbacks = [
#reduce
%n => function ($stackPos) {
%b
},
#noact
%n => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
#endreduce
];
}
}
#tailcode;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +0,0 @@
<?php
namespace RectorPrefix202312;
///////////////////////////////
/// Utility regex constants ///
///////////////////////////////
const LIB = '(?(DEFINE)
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
(?<comment>/\\*[^*]*+(?:\\*(?!/)[^*]*+)*+\\*/)
(?<code>\\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';
const PARAMS = '\\[(?<params>[^[\\]]*+(?:\\[(?&params)\\][^[\\]]*+)*+)\\]';
const ARGS = '\\((?<args>[^()]*+(?:\\((?&args)\\)[^()]*+)*+)\\)';
///////////////////////////////
/// Preprocessing functions ///
///////////////////////////////
function preprocessGrammar($code)
{
$code = resolveNodes($code);
$code = resolveMacros($code);
$code = resolveStackAccess($code);
return $code;
}
function resolveNodes($code)
{
return \preg_replace_callback('~\\b(?<name>[A-Z][a-zA-Z_\\\\]++)\\s*' . \RectorPrefix202312\PARAMS . '~', function ($matches) {
// recurse
$matches['params'] = resolveNodes($matches['params']);
$params = magicSplit('(?:' . \RectorPrefix202312\PARAMS . '|' . \RectorPrefix202312\ARGS . ')(*SKIP)(*FAIL)|,', $matches['params']);
$paramCode = '';
foreach ($params as $param) {
$paramCode .= $param . ', ';
}
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
}, $code);
}
function resolveMacros($code)
{
return \preg_replace_callback('~\\b(?<!::|->)(?!array\\()(?<name>[a-z][A-Za-z]++)' . \RectorPrefix202312\ARGS . '~', function ($matches) {
// recurse
$matches['args'] = resolveMacros($matches['args']);
$name = $matches['name'];
$args = magicSplit('(?:' . \RectorPrefix202312\PARAMS . '|' . \RectorPrefix202312\ARGS . ')(*SKIP)(*FAIL)|,', $matches['args']);
if ('attributes' === $name) {
assertArgs(0, $args, $name);
return '$this->startAttributeStack[#1] + $this->endAttributes';
}
if ('stackAttributes' === $name) {
assertArgs(1, $args, $name);
return '$this->startAttributeStack[' . $args[0] . ']' . ' + $this->endAttributeStack[' . $args[0] . ']';
}
if ('init' === $name) {
return '$$ = array(' . \implode(', ', $args) . ')';
}
if ('push' === $name) {
assertArgs(2, $args, $name);
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
}
if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }' . ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
}
if ('toArray' == $name) {
assertArgs(1, $args, $name);
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
}
if ('parseVar' === $name) {
assertArgs(1, $args, $name);
return 'substr(' . $args[0] . ', 1)';
}
if ('parseEncapsed' === $name) {
assertArgs(3, $args, $name);
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\\Scalar\\EncapsedStringPart) {' . ' $s->value = Node\\Scalar\\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
}
if ('makeNop' === $name) {
assertArgs(3, $args, $name);
return '$startAttributes = ' . $args[1] . ';' . ' if (isset($startAttributes[\'comments\']))' . ' { ' . $args[0] . ' = new Stmt\\Nop($startAttributes + ' . $args[2] . '); }' . ' else { ' . $args[0] . ' = null; }';
}
if ('makeZeroLengthNop' == $name) {
assertArgs(2, $args, $name);
return '$startAttributes = ' . $args[1] . ';' . ' if (isset($startAttributes[\'comments\']))' . ' { ' . $args[0] . ' = new Stmt\\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }' . ' else { ' . $args[0] . ' = null; }';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; ' . 'if (!empty($attrs[\'comments\'])) {' . '$stmts[0]->setAttribute(\'comments\', ' . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
}
return $matches[0];
}, $code);
}
function assertArgs($num, $args, $name)
{
if ($num != \count($args)) {
die('Wrong argument count for ' . $name . '().');
}
}
function resolveStackAccess($code)
{
$code = \preg_replace('/\\$\\d+/', '$this->semStack[$0]', $code);
$code = \preg_replace('/#(\\d+)/', '$$1', $code);
return $code;
}
function removeTrailingWhitespace($code)
{
$lines = \explode("\n", $code);
$lines = \array_map('rtrim', $lines);
return \implode("\n", $lines);
}
//////////////////////////////
/// Regex helper functions ///
//////////////////////////////
function regex($regex)
{
return '~' . \RectorPrefix202312\LIB . '(?:' . \str_replace('~', '\\~', $regex) . ')~';
}
function magicSplit($regex, $string)
{
$pieces = \preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
foreach ($pieces as &$piece) {
$piece = \trim($piece);
}
if ($pieces === ['']) {
return [];
}
return $pieces;
}

View File

@ -1,64 +0,0 @@
<?php
namespace RectorPrefix202312;
require __DIR__ . '/phpyLang.php';
$grammarFileToName = [__DIR__ . '/php5.y' => 'Php5', __DIR__ . '/php7.y' => 'Php7'];
$tokensFile = __DIR__ . '/tokens.y';
$tokensTemplate = __DIR__ . '/tokens.template';
$skeletonFile = __DIR__ . '/parser.template';
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
$tmpResultFile = __DIR__ . '/tmp_parser.php';
$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
$tokensResultsFile = $resultDir . '/Tokens.php';
$kmyacc = \getenv('KMYACC');
if (!$kmyacc) {
// Use phpyacc from dev dependencies by default.
$kmyacc = __DIR__ . '/../vendor/bin/phpyacc';
}
$options = \array_flip($argv);
$optionDebug = isset($options['--debug']);
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
///////////////////
/// Main script ///
///////////////////
$tokens = \file_get_contents($tokensFile);
foreach ($grammarFileToName as $grammarFile => $name) {
echo "Building temporary {$name} grammar file.\n";
$grammarCode = \file_get_contents($grammarFile);
$grammarCode = \str_replace('%tokens', $tokens, $grammarCode);
$grammarCode = preprocessGrammar($grammarCode);
\file_put_contents($tmpGrammarFile, $grammarCode);
$additionalArgs = $optionDebug ? '-t -v' : '';
echo "Building {$name} parser.\n";
$output = execCmd("{$kmyacc} {$additionalArgs} -m {$skeletonFile} -p {$name} {$tmpGrammarFile}");
$resultCode = \file_get_contents($tmpResultFile);
$resultCode = removeTrailingWhitespace($resultCode);
ensureDirExists($resultDir);
\file_put_contents("{$resultDir}/{$name}.php", $resultCode);
\unlink($tmpResultFile);
echo "Building token definition.\n";
$output = execCmd("{$kmyacc} -m {$tokensTemplate} {$tmpGrammarFile}");
\rename($tmpResultFile, $tokensResultsFile);
if (!$optionKeepTmpGrammar) {
\unlink($tmpGrammarFile);
}
}
////////////////////////////////
/// Utility helper functions ///
////////////////////////////////
function ensureDirExists($dir)
{
if (!\is_dir($dir)) {
\mkdir($dir, 0777, \true);
}
}
function execCmd($cmd)
{
$output = \trim(\shell_exec("{$cmd} 2>&1"));
if ($output !== "") {
echo "> " . $cmd . "\n";
echo $output;
}
return $output;
}

View File

@ -1,17 +0,0 @@
<?php
$meta #
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval(%n) $this->stackPos-(%l-%n)
#semval(%n,%t) $this->stackPos-(%l-%n)
namespace PhpParser\Parser;
#include;
/* GENERATED file based on grammar/tokens.y */
final class Tokens
{
#tokenval
const %s = %n;
#endtokenval
}

View File

@ -1,115 +0,0 @@
/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
* both. This is enforced by sharing this token file. */
%right T_THROW
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
%left ','
%left T_LOGICAL_OR
%left T_LOGICAL_XOR
%left T_LOGICAL_AND
%right T_PRINT
%right T_YIELD
%right T_DOUBLE_ARROW
%right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL
%left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%left '|'
%left '^'
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left T_SL T_SR
%left '+' '-' '.'
%left '*' '/' '%'
%right '!'
%nonassoc T_INSTANCEOF
%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
%right T_POW
%right '['
%nonassoc T_NEW T_CLONE
%token T_EXIT
%token T_IF
%left T_ELSEIF
%left T_ELSE
%left T_ENDIF
%token T_LNUMBER
%token T_DNUMBER
%token T_STRING
%token T_STRING_VARNAME
%token T_VARIABLE
%token T_NUM_STRING
%token T_INLINE_HTML
%token T_ENCAPSED_AND_WHITESPACE
%token T_CONSTANT_ENCAPSED_STRING
%token T_ECHO
%token T_DO
%token T_WHILE
%token T_ENDWHILE
%token T_FOR
%token T_ENDFOR
%token T_FOREACH
%token T_ENDFOREACH
%token T_DECLARE
%token T_ENDDECLARE
%token T_AS
%token T_SWITCH
%token T_MATCH
%token T_ENDSWITCH
%token T_CASE
%token T_DEFAULT
%token T_BREAK
%token T_CONTINUE
%token T_GOTO
%token T_FUNCTION
%token T_FN
%token T_CONST
%token T_RETURN
%token T_TRY
%token T_CATCH
%token T_FINALLY
%token T_THROW
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_VAR
%token T_UNSET
%token T_ISSET
%token T_EMPTY
%token T_HALT_COMPILER
%token T_CLASS
%token T_TRAIT
%token T_INTERFACE
%token T_ENUM
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR
%token T_NULLSAFE_OBJECT_OPERATOR
%token T_DOUBLE_ARROW
%token T_LIST
%token T_ARRAY
%token T_CALLABLE
%token T_CLASS_C
%token T_TRAIT_C
%token T_METHOD_C
%token T_FUNC_C
%token T_LINE
%token T_FILE
%token T_START_HEREDOC
%token T_END_HEREDOC
%token T_DOLLAR_OPEN_CURLY_BRACES
%token T_CURLY_OPEN
%token T_PAAMAYIM_NEKUDOTAYIM
%token T_NAMESPACE
%token T_NS_C
%token T_DIR
%token T_NS_SEPARATOR
%token T_ELLIPSIS
%token T_NAME_FULLY_QUALIFIED
%token T_NAME_QUALIFIED
%token T_NAME_RELATIVE
%token T_ATTRIBUTE
%token T_ENUM

View File

@ -110,6 +110,9 @@ class NameResolver extends NodeVisitorAbstract
}
} else {
if ($node instanceof Stmt\ClassConst) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} else {
if ($node instanceof Stmt\EnumCase) {

View File

@ -649,6 +649,8 @@ class Php5 extends \PhpParser\ParserAbstract
if ($this->semStack[$stackPos - (2 - 2)] !== null) {
$this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)];
$this->semValue = $this->semStack[$stackPos - (2 - 1)];
} else {
$this->semValue = $this->semStack[$stackPos - (2 - 1)];
}
}, 260 => function ($stackPos) {
$this->semValue = array();

View File

@ -832,6 +832,8 @@ class Php7 extends \PhpParser\ParserAbstract
if ($this->semStack[$stackPos - (2 - 2)] !== null) {
$this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)];
$this->semValue = $this->semStack[$stackPos - (2 - 1)];
} else {
$this->semValue = $this->semStack[$stackPos - (2 - 1)];
}
}, 341 => function ($stackPos) {
$this->semValue = array();

View File

@ -3,6 +3,8 @@
declare (strict_types=1);
namespace PhpParser;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Php7;
class ParserFactory
{
const PREFER_PHP7 = 1;
@ -36,4 +38,31 @@ class ParserFactory
throw new \LogicException('Kind must be one of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5');
}
}
/**
* Create a parser targeting the newest version supported by this library. Code for older
* versions will be accepted if there have been no relevant backwards-compatibility breaks in
* PHP.
*
* All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos,
* startFilePos, endFilePos) will be enabled.
*/
public function createForNewestSupportedVersion() : \PhpParser\Parser
{
return new Php7(new Emulative($this->getLexerOptions()));
}
/**
* Create a parser targeting the host PHP version, that is the PHP version we're currently
* running on. This parser will not use any token emulation.
*
* All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos,
* startFilePos, endFilePos) will be enabled.
*/
public function createForHostVersion() : \PhpParser\Parser
{
return new Php7(new \PhpParser\Lexer($this->getLexerOptions()));
}
private function getLexerOptions() : array
{
return ['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos']];
}
}

View File

@ -9,7 +9,7 @@ namespace Rector\RectorInstaller;
*/
final class GeneratedConfig
{
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 9de7d58'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main a08ccad'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 90e87f1'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 38014d4'));
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 9de7d58'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main 917085c'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main 90e87f1'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 38014d4'));
private function __construct()
{
}

View File

@ -6,7 +6,8 @@ namespace RectorPrefix202312;
use Rector\Config\RectorConfig;
use Rector\Core\ValueObject\PhpVersion;
use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector;
use Rector\DowngradePhp82\Rector\FunctionLike\DowngradeStandaloneNullTrueFalseReturnTypeRector;
return static function (RectorConfig $rectorConfig) : void {
$rectorConfig->phpVersion(PhpVersion::PHP_81);
$rectorConfig->rule(DowngradeReadonlyClassRector::class);
$rectorConfig->rules([DowngradeReadonlyClassRector::class, DowngradeStandaloneNullTrueFalseReturnTypeRector::class]);
};

View File

@ -0,0 +1,159 @@
<?php
declare (strict_types=1);
namespace Rector\DowngradePhp82\Rector\FunctionLike;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\UnionType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\PhpParser\AstResolver;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @changelog https://wiki.php.net/rfc/null-false-standalone-types
* @changelog https://wiki.php.net/rfc/true-type
* @see \Rector\Tests\DowngradePhp82\Rector\FunctionLike\DowngradeStandaloneNullTrueFalseReturnTypeRector\DowngradeStandaloneNullTrueFalseReturnTypeRectorTest
*/
final class DowngradeStandaloneNullTrueFalseReturnTypeRector extends AbstractRector
{
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory
*/
private $phpDocInfoFactory;
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger
*/
private $phpDocTypeChanger;
/**
* @readonly
* @var \Rector\Core\PhpParser\AstResolver
*/
private $astResolver;
public function __construct(PhpDocInfoFactory $phpDocInfoFactory, PhpDocTypeChanger $phpDocTypeChanger, AstResolver $astResolver)
{
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->phpDocTypeChanger = $phpDocTypeChanger;
$this->astResolver = $astResolver;
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes() : array
{
return [FunctionLike::class];
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Downgrade standalone return null, true, or false', [new CodeSample(<<<'CODE_SAMPLE'
final class SomeClass
{
public function run(): null
{
return null;
}
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
final class SomeClass
{
public function run(): mixed
{
return null;
}
}
CODE_SAMPLE
)]);
}
/**
* @param ClassMethod|Function_|Closure|ArrowFunction $node
*/
public function refactor(Node $node) : ?Node
{
$returnType = $node->returnType;
if (!$returnType instanceof Identifier) {
return null;
}
$returnTypeName = $this->getName($returnType);
if (!\in_array($returnTypeName, ['null', 'false', 'true'], \true)) {
return null;
}
// in closure and arrow function can't add `@return null` docblock as they are Expr
// that rely on Stmt
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
$this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $this->resolveType($returnType));
$node->returnType = $this->resolveNativeType($node, $returnType);
return $node;
}
private function resolveType(Identifier $identifier) : Type
{
$nodeName = $this->getName($identifier);
if ($nodeName === 'null') {
return new NullType();
}
if ($nodeName === 'false') {
return new ConstantBooleanType(\false);
}
return new ConstantBooleanType(\true);
}
/**
* @return \PhpParser\Node\ComplexType|\PhpParser\Node\Identifier
*/
private function resolveNativeType(Node $node, Identifier $identifier)
{
if ($node instanceof ClassMethod) {
$returnTypeFromParent = $this->resolveParentNativeReturnType($node);
if ($returnTypeFromParent instanceof UnionType || $returnTypeFromParent instanceof NullableType) {
$this->traverseNodesWithCallable($returnTypeFromParent, static function (Node $subNode) : Node {
$subNode->setAttribute(AttributeKey::ORIGINAL_NODE, null);
return $subNode;
});
return $returnTypeFromParent;
}
}
$nodeName = $this->getName($identifier);
if ($nodeName === 'null') {
return new Identifier('mixed');
}
return new Identifier('bool');
}
private function resolveParentNativeReturnType(ClassMethod $classMethod) : ?Node
{
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if ($scope === null) {
return null;
}
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
return null;
}
$methodName = $classMethod->name->toString();
$parents = \array_merge(\is_array($classReflection->getParents()) ? $classReflection->getParents() : \iterator_to_array($classReflection->getParents()), \is_array($classReflection->getInterfaces()) ? $classReflection->getInterfaces() : \iterator_to_array($classReflection->getInterfaces()));
foreach ($parents as $parent) {
if (!$parent->hasMethod($methodName)) {
continue;
}
$parentClassMethod = $this->astResolver->resolveClassMethod($parent->getName(), $methodName);
if (!$parentClassMethod instanceof ClassMethod) {
continue;
}
return $parentClassMethod->returnType;
}
return null;
}
}