diff --git a/.phan/config.php b/.phan/config.php index 9f187b0..ec758d4 100644 --- a/.phan/config.php +++ b/.phan/config.php @@ -197,10 +197,14 @@ // A list of plugin files to execute 'plugins' => [ // NOTE: src/Phan/Language/Internal/FunctionSignatureMap.php mixes value without key as return type with values having keys deliberately. - 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php', // (TODO: make BlockExitStatus more reliable) - 'vendor/phan/phan/.phan/plugins/DollarDollarPlugin.php', - 'vendor/phan/phan/.phan/plugins/DuplicateArrayKeyPlugin.php', - 'vendor/phan/phan/.phan/plugins/UnreachableCodePlugin.php', // (TODO: make BlockExitStatus more reliable) + 'AlwaysReturnPlugin', + 'DemoPlugin', + 'DollarDollarPlugin', + 'UnreachableCodePlugin', + // NOTE: src/Phan/Language/Internal/FunctionSignatureMap.php mixes value without keys (as return type) with values having keys deliberately. + 'DuplicateArrayKeyPlugin', + 'PregRegexCheckerPlugin', + 'PrintfCheckerPlugin', // NOTE: This plugin only produces correct results when // Phan is run on a single core (-j1). // 'vendor/phan/phan/.phan/plugins/UnusedSuppressionPlugin.php', diff --git a/composer.json b/composer.json index 52c12d4..ca8047d 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,8 @@ "keywords": ["php", "ast", "parser"], "type": "library", "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, "authors": [ { "name": "Tyson Andre" @@ -11,11 +13,10 @@ ], "require": { "php": ">=7.1", - "nikic/PHP-Parser": "~3.1.1" + "nikic/PHP-Parser": "~4.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.7", - "phan/phan": "~0.10.0" + "phpunit/phpunit": "^5.7" }, "suggest": { "ext-ast": "~0.1.5" diff --git a/composer.lock b/composer.lock index 0a5c391..18b16c6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,28 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "cb69a86cf26f7737b52a35355c064850", + "content-hash": "e217990d5ad7b81a9e9fa6b9b9f18038", "packages": [ { "name": "nikic/php-parser", - "version": "v3.1.1", + "version": "v4.0.0alpha2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a1e8e1a30e1352f118feff1a8481066ddc2f234a" + "reference": "a659240dc28d83b207273f7182480cea562d4966" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a1e8e1a30e1352f118feff1a8481066ddc2f234a", - "reference": "a1e8e1a30e1352f118feff1a8481066ddc2f234a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a659240dc28d83b207273f7182480cea562d4966", + "reference": "a659240dc28d83b207273f7182480cea562d4966", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.5" + "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" + "phpunit/phpunit": "^6" }, "bin": [ "bin/php-parse" @@ -33,7 +33,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -55,7 +55,7 @@ "parser", "php" ], - "time": "2017-09-02T17:10:46+00:00" + "time": "2017-11-10T22:36:59+00:00" } ], "packages-dev": [ @@ -115,37 +115,40 @@ }, { "name": "myclabs/deep-copy", - "version": "1.6.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" - } + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -153,64 +156,7 @@ "object", "object graph" ], - "time": "2017-04-12T18:52:22+00:00" - }, - { - "name": "phan/phan", - "version": "0.10.0", - "source": { - "type": "git", - "url": "https://github.com/phan/phan.git", - "reference": "f54b4cd4206f68ed53bbc95910351fba8b6c9ada" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phan/phan/zipball/f54b4cd4206f68ed53bbc95910351fba8b6c9ada", - "reference": "f54b4cd4206f68ed53bbc95910351fba8b6c9ada", - "shasum": "" - }, - "require": { - "ext-ast": "^0.1.5", - "nikic/php-parser": "~3.1.1", - "php": "~7.1.0 || ~7.2.0", - "symfony/console": "~2.3|~3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.3.0" - }, - "bin": [ - "phan", - "phan_client", - "tocheckstyle" - ], - "type": "project", - "autoload": { - "psr-4": { - "Phan\\": "src/Phan" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rasmus Lerdorf" - }, - { - "name": "Andrew S. Morrison" - }, - { - "name": "Tyson Andre" - } - ], - "description": "A static analyzer for PHP", - "keywords": [ - "analyzer", - "php", - "static" - ], - "time": "2017-09-24T19:13:44+00:00" + "time": "2017-10-19T19:58:43+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -672,16 +618,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.22", + "version": "5.7.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "10df877596c9906d4110b5b905313829043f2ada" + "reference": "78532d5269d984660080d8e0f4c99c5c2ea65ffe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/10df877596c9906d4110b5b905313829043f2ada", - "reference": "10df877596c9906d4110b5b905313829043f2ada", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/78532d5269d984660080d8e0f4c99c5c2ea65ffe", + "reference": "78532d5269d984660080d8e0f4c99c5c2ea65ffe", "shasum": "" }, "require": { @@ -750,7 +696,7 @@ "testing", "xunit" ], - "time": "2017-09-24T07:23:38+00:00" + "time": "2017-10-15T06:13:55+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -811,53 +757,6 @@ ], "time": "2017-06-30T09:13:00+00:00" }, - { - "name": "psr/log", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2016-10-10T12:19:37+00:00" - }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -1371,201 +1270,18 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, - { - "name": "symfony/console", - "version": "v3.3.10", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/116bc56e45a8e5572e51eb43ab58c769a352366c", - "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.3", - "symfony/dependency-injection": "~3.3", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/filesystem": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/filesystem": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "time": "2017-10-02T06:42:24+00:00" - }, - { - "name": "symfony/debug", - "version": "v3.3.10", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd", - "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "time": "2017-10-02T06:42:24+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", - "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2017-06-14T15:44:48+00:00" - }, { "name": "symfony/yaml", - "version": "v3.3.10", + "version": "v3.3.11", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46" + "reference": "0938408c4faa518d95230deabb5f595bf0de31b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", - "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0938408c4faa518d95230deabb5f595bf0de31b9", + "reference": "0938408c4faa518d95230deabb5f595bf0de31b9", "shasum": "" }, "require": { @@ -1607,7 +1323,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-10-05T14:43:42+00:00" + "time": "2017-11-10T18:26:04+00:00" }, { "name": "webmozart/assert", @@ -1661,9 +1377,9 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": ">=7.1" diff --git a/src/ASTConverter/ASTConverter.php b/src/ASTConverter/ASTConverter.php index e8a15ed..9b0ab2f 100644 --- a/src/ASTConverter/ASTConverter.php +++ b/src/ASTConverter/ASTConverter.php @@ -85,6 +85,9 @@ final class ASTConverter { /** @var int */ private $instance_phpparser_preferred_version = ParserFactory::ONLY_PHP7; + /** @var ?string */ + private static $file_contents = null; + // No-op. public function __construct() { } @@ -100,10 +103,15 @@ public function __construct() { } */ public function parseCodeAsPHPAST(string $source, int $ast_version, bool $suppress_errors = false, array &$errors = null) { if (!\in_array($ast_version, self::SUPPORTED_AST_VERSIONS)) { - throw new \InvalidArgumentException(sprintf("Unexpected version: want %d, got %d", implode(', ', self::SUPPORTED_AST_VERSIONS), $ast_version)); + throw new \InvalidArgumentException(\sprintf("Unexpected version: want %s, got %d", implode(', ', self::SUPPORTED_AST_VERSIONS), $ast_version)); + } + self::$file_contents = $source; + try { + $parser_node = $this->phpParserParse($source, $suppress_errors, $errors); + return self::phpParserToPhpast($parser_node, $ast_version); + } finally { + self::$file_contents = null; } - $parser_node = $this->phpParserParse($source, $suppress_errors, $errors); - return self::phpParserToPhpast($parser_node, $ast_version); } /** @@ -210,31 +218,6 @@ private static function phpParserStmtlistToAstNode(array $parser_nodes, $lineno) return $stmts; } - /** - // Convert this into the most common way to write a namespace node. - private static function normalizeNamespaces(ast\Node $stmts_node) : ast\Node { - $has_namespace = false; - foreach ($stmts_node->children as $stmt) { - if ($stmt->kind !== \ast\AST_NAMESPACE) { - continue; - } - $has_namespace = true; - if ($stmt->children['name'] === null) { - return $stmts_node; - } - } - if ($has_namespace === false) { - return $stmts_node; - } - $new_node = clone($stmts_node); - $children = []; - foreach ($stmts_node->children as $stmt) { - if ($stmt->kind == \ast\AST_NAMESPACE) { - } - } - } - */ - /** * @param PHPParser\Node[] $exprs */ @@ -577,10 +560,14 @@ private static function initHandleMap() : array { return self::phpParserListToAstList($n, $start_line); }, 'PhpParser\Node\Expr\MethodCall' => function(PhpParser\Node\Expr\MethodCall $n, int $start_line) : ast\Node { - return self::astNodeMethodCall( - self::phpParserNodeToAstNode($n->var), - is_string($n->name) ? $n->name : self::phpParserNodeToAstNode($n->name), - self::phpParserArgListToAstArgList($n->args, $start_line), + return new ast\Node( + ast\AST_METHOD_CALL, + 0, + [ + 'expr' => self::phpParserNodeToAstNode($n->var), + 'method' => self::phpParserNodeToAstNode($n->name), + 'args' => self::phpParserArgListToAstArgList($n->args, $start_line), + ], $start_line ); }, @@ -619,7 +606,9 @@ private static function initHandleMap() : array { if (\count($parts) === 1 && $parts[0] instanceof PhpParser\Node\Scalar\EncapsedStringPart) { $value = $parts[0]->value; } else { - $value_inner = array_map(function(PhpParser\Node $node) { return self::phpParserNodeToAstNode($node); }, $parts); + $value_inner = array_map(function(PhpParser\Node $node) { + return self::phpParserNodeToAstNode($node); + }, $parts); $value = new ast\Node(ast\AST_ENCAPS_LIST, 0, $value_inner, $start_line); } return new ast\Node(ast\AST_SHELL_EXEC, 0, ['expr' => $value], $start_line); @@ -684,6 +673,10 @@ private static function initHandleMap() : array { $start_line ); }, + /** @return string */ + 'PhpParser\Node\Identifier' => function(PhpParser\Node\Identifier $n, int $start_line) { + return $n->name; + }, 'PhpParser\Node\Name' => function(PhpParser\Node\Name $n, int $start_line) : ast\Node { $name = implode('\\', $n->parts); return new ast\Node(ast\AST_NAME, ast\flags\NAME_NOT_FQ, ['name' => $name], $start_line); @@ -709,16 +702,25 @@ private static function initHandleMap() : array { $n->byRef, $n->variadic, self::phpParserTypeToAstNode($n->type, $type_line), - $n->name, + $n->var->name, self::phpParserTypeToAstNode($n->default, $default_line), $start_line ); }, 'PhpParser\Node\Scalar\Encapsed' => function(PhpParser\Node\Scalar\Encapsed $n, int $start_line) : ast\Node { + $value_inner = \array_map(function(PhpParser\Node $n) { + return self::phpParserNodeToAstNode($n); + }, $n->parts); + + if ($n->getAttribute('kind') === PhpParser\Node\Scalar\String_::KIND_HEREDOC) { + // Reproduce quirk of php-ast + $value_inner[] = ''; + } + return new ast\Node( ast\AST_ENCAPS_LIST, 0, - array_map(function(PhpParser\Node $n) { return self::phpParserNodeToAstNode($n); }, $n->parts), + $value_inner, $start_line ); }, @@ -764,7 +766,7 @@ private static function initHandleMap() : array { 'PhpParser\Node\Stmt\Catch_' => function(PhpParser\Node\Stmt\Catch_ $n, int $start_line) : ast\Node { return self::astStmtCatch( self::phpParserNameListToAstNameList($n->types, $start_line), - $n->var, + $n->var->name, self::phpParserStmtlistToAstNode($n->stmts, $start_line), $start_line ); @@ -807,7 +809,7 @@ private static function initHandleMap() : array { ], $start_line, self::extractPhpdocComment($n->getAttribute('comments')), - $n->name, + $n->name->name, $n->getAttribute('endLine'), self::nextDeclId() ); @@ -845,6 +847,10 @@ private static function initHandleMap() : array { } return count($ast_echos) === 1 ? $ast_echos[0] : $ast_echos; }, + /** @return ast\Node|ast\Node[] */ + 'PhpParser\Node\Stmt\Expression' => function(PhpParser\Node\Stmt\Expression $n, int $start_line) { + return self::phpParserNodeToAstNode($n->expr); + }, 'PhpParser\Node\Stmt\Foreach_' => function(PhpParser\Node\Stmt\Foreach_ $n, int $start_line) : ast\Node { $value = self::phpParserNodeToAstNode($n->valueVar); if ($n->byRef) { @@ -875,11 +881,10 @@ private static function initHandleMap() : array { $end_line = $n->getAttribute('endLine') ?: $start_line; $return_type = $n->returnType; $return_type_line = sl($return_type) ?: $end_line; - $ast_return_type = self::phpParserTypeToAstNode($return_type, $return_type_line); return self::astDeclFunction( $n->byRef, - $n->name, + $n->name->name, self::phpParserParamsToAstParams($n->params, $start_line), null, // uses self::phpParserTypeToAstNode($return_type, $return_type_line), @@ -901,15 +906,43 @@ private static function initHandleMap() : array { return new ast\Node( \ast\AST_GOTO, 0, - ['label' => $n->name], + ['label' => $n->name->name], $start_line ); }, 'PhpParser\Node\Stmt\HaltCompiler' => function(PhpParser\Node\Stmt\HaltCompiler $n, int $start_line) : ast\Node { + $source = self::$file_contents; + $offset = 'TODO compute halt compiler offset'; + $computed_offset = 0; + if (\is_string($source)) { + $all_tokens = \token_get_all($source); + foreach ($all_tokens as $i => $token) { + if (\is_array($token)) { + if ($token[0] === T_HALT_COMPILER) { + $j = $i; + do { + $cur_token = $all_tokens[$j++] ?? null; + if ($cur_token === null) { + break; + } + if (\is_array($cur_token)) { + $cur_token = $cur_token[1]; + } + $computed_offset += \strlen($cur_token); + } while ($cur_token !== ';'); + $offset = $computed_offset; + break; + } + $computed_offset += \strlen($token[1]); + } else { + $computed_offset += \strlen($token); + } + } + } return new ast\Node( \ast\AST_HALT_COMPILER, 0, - ['offset' => 'TODO compute halt compiler offset'], // FIXME implement + ['offset' => $offset], // FIXME implement $start_line ); }, @@ -937,7 +970,7 @@ private static function initHandleMap() : array { return new ast\Node( \ast\AST_LABEL, 0, - ['name' => $n->name], + ['name' => $n->name->name], $start_line ); }, @@ -964,25 +997,12 @@ private static function initHandleMap() : array { }, 'PhpParser\Node\Stmt\Namespace_' => function(PhpParser\Node\Stmt\Namespace_ $n, int $start_line) : array { $php_parser_stmts = $n->stmts; + $namespace_kind = $n->getAttribute('kind'); // KIND_SEMICOLON or KIND_BRACED \assert(\is_array($php_parser_stmts)); $name = $n->name !== null ? self::phpParserNameToString($n->name) : null; $stmts_of_namespace = is_array($php_parser_stmts) ? self::phpParserStmtlistToAstNode($php_parser_stmts, $start_line) : null; - $end_line_of_wrapper = null; - $end_line_of_contents = null; - if ($stmts_of_namespace !== null) { - $end_line_of_wrapper = ($n->getAttribute('endLine') ?: $n->getAttribute('startLine') ?: null); - $within_namespace = $n->stmts ?? []; - if (count($within_namespace) > 0) { - foreach ($within_namespace as $s) { - $end_line_of_contents = $s->getAttribute('endLine') ?: $s->getAttribute('startLine'); - if ($end_line_of_contents && $end_line_of_contents != $end_line_of_wrapper) { - break; - } - } - } - } - if ($name === null || $end_line_of_contents <= $end_line_of_wrapper) { + if ($namespace_kind === PHPParser\Node\Stmt\Namespace_::KIND_BRACED) { // `namespace {}` (explicit global namespace) can only be written one way. // If a child nodes line number fall within the same range, assume it's a namespace {} return [ @@ -1024,7 +1044,7 @@ private static function initHandleMap() : array { $static_nodes = []; foreach ($n->vars as $var) { $static_nodes[] = new ast\Node(ast\AST_STATIC, 0, [ - 'var' => new ast\Node(ast\AST_VAR, 0, ['name' => $var->name], sl($var) ?: $start_line), + 'var' => new ast\Node(ast\AST_VAR, 0, ['name' => $var->var->name], sl($var) ?: $start_line), 'default' => $var->default !== null ? self::phpParserNodeToAstNode($var->default) : null, ], sl($var) ?: $start_line); } @@ -1075,14 +1095,13 @@ private static function initHandleMap() : array { }, 'PhpParser\Node\Stmt\TraitUseAdaptation\Alias' => function(PhpParser\Node\Stmt\TraitUseAdaptation\Alias $n, int $start_line) : ast\Node { $old_class = $n->trait !== null ? self::phpParserNodeToAstNode($n->trait) : null; - $flags = ($n->trait instanceof PhpParser\Node\Name\FullyQualified) ? ast\flags\NAME_FQ : ast\flags\NAME_NOT_FQ; // TODO: flags for visibility return new ast\Node(ast\AST_TRAIT_ALIAS, self::phpParserVisibilityToAstVisibility($n->newModifier ?? 0, false), [ 'method' => new ast\Node(ast\AST_METHOD_REFERENCE, 0, [ 'class' => $old_class, - 'method' => $n->method, + 'method' => $n->method->name, ], $start_line), - 'alias' => $n->newName, + 'alias' => $n->newName->name ?? null, ], $start_line); }, 'PhpParser\Node\Stmt\TraitUseAdaptation\Precedence' => function(PhpParser\Node\Stmt\TraitUseAdaptation\Precedence $n, int $start_line) : ast\Node { @@ -1092,7 +1111,7 @@ private static function initHandleMap() : array { return new ast\Node(ast\AST_TRAIT_PRECEDENCE, 0, [ 'method' => new ast\Node(ast\AST_METHOD_REFERENCE, 0, [ 'class' => new ast\Node(ast\AST_NAME, $flags, ['name' => $old_class], $start_line), - 'method' => $n->method, + 'method' => $n->method->name, ], $start_line), 'insteadof' => self::phpParserNameListToAstNameList($n->insteadof, $start_line), ], $start_line); @@ -1130,10 +1149,14 @@ private static function initHandleMap() : array { $start_line ); }, + /** @return string */ + 'PhpParser\Node\VarLikeIdentifier' => function(PhpParser\Node\VarLikeIdentifier $n, int $start_line) { + return $n->name; + }, ]; - foreach ($closures as $key => $value) { - assert(class_exists($key), "Class $key should exist"); + foreach ($closures as $key => $_) { + \assert(\class_exists($key), "Class $key should exist"); } return $closures; } @@ -1273,8 +1296,12 @@ private static function phpParserTypeToAstNode($type, int $line) { return $type; } $original_type = $type; + // TODO: Still need to check for Name? if ($type instanceof PhpParser\Node\Name) { $type = self::phpParserNameToString($type); + } else if ($type instanceof PhpParser\Node\Identifier) { + // Represents a non-namespaced name. + $type = $type->name; } if (\is_string($type)) { switch(strtolower($type)) { @@ -1437,7 +1464,7 @@ private static function phpParserClosureUsesToAstClosureUses( } $ast_uses = []; foreach ($uses as $use) { - $ast_uses[] = new ast\Node(ast\AST_CLOSURE_VAR, $use->byRef ? 1 : 0, ['name' => $use->var], $use->getAttribute('startLine')); + $ast_uses[] = new ast\Node(ast\AST_CLOSURE_VAR, $use->byRef ? 1 : 0, ['name' => $use->var->name], $use->getAttribute('startLine')); } return new ast\Node(ast\AST_CLOSURE_USES, 0, $ast_uses, $ast_uses[0]->lineno ?? $line); @@ -1615,23 +1642,27 @@ private static function astStmtClass( ], $line, $doc_comment, - $name, + $name->name, $end_line, self::nextDeclId() ); } + /** + * @param PhpParser\Node\Arg[] $args + * @param int $line + */ private static function phpParserArgListToAstArgList(array $args, int $line) : ast\Node { - $node = new ast\Node(); - $node->kind = ast\AST_ARG_LIST; - $node->flags = 0; $ast_args = []; foreach ($args as $arg) { $ast_args[] = self::phpParserNodeToAstNode($arg); } - $node->lineno = $ast_args[0]->lineno ?? $line; - $node->children = $ast_args; - return $node; + return new ast\Node( + ast\AST_ARG_LIST, + 0, + $ast_args, + $ast_args[0]->lineno ?? $line + ); } /** @param ?array $uses */ @@ -1645,7 +1676,7 @@ private static function phpParserUseListToAstUseList($uses) : array { // ast doesn't fill in an alias if it's identical to the real name, // but phpParser does? $name = implode('\\', $use->name->parts); - $alias = $use->alias; + $alias = $use->alias->name; $ast_use->children = [ 'name' => $name, 'alias' => $alias !== end($use->name->parts) ? $alias : null, @@ -1771,7 +1802,7 @@ private static function phpParserIfStmtToAstIfStmt(PhpParser\Node $node) : ast\N /** * @suppress PhanUndeclaredProperty */ - private static function astNodeAssignop(int $flags, PhpParser\Node $node, int $start_line) { + private static function astNodeAssignop(int $flags, PhpParser\Node\Expr\AssignOp $node, int $start_line) { return new ast\Node( ast\AST_ASSIGN_OP, $flags, @@ -1807,7 +1838,7 @@ private static function phpParserNodesToLeftRightChildren($left, $right) : array */ private static function phpParserPropelemToAstPropelem(PhpParser\Node\Stmt\PropertyProperty $n, $doc_comment) : ast\Node{ $children = [ - 'name' => $n->name, + 'name' => self::phpParserNodeToAstNode($n->name), 'default' => $n->default ? self::phpParserNodeToAstNode($n->default) : null, ]; @@ -1821,7 +1852,7 @@ private static function phpParserPropelemToAstPropelem(PhpParser\Node\Stmt\Prope */ private static function phpParserConstelemToAstConstelem(PhpParser\Node\Const_ $n, $doc_comment) : ast\Node{ $children = [ - 'name' => $n->name, + 'name' => $n->name->name, 'value' => self::phpParserNodeToAstNode($n->value), ]; @@ -1892,13 +1923,16 @@ private static function phpParserConstToAstNode(PhpParser\Node\Stmt\Const_ $n, i } /** + * @param PhpParser\Node\Stmt\DeclareDeclare[] $declares + * @param int $start_line + * @param ?string $first_doc_comment * @suppress PhanUndeclaredProperty */ private static function phpParserDeclareListToAstDeclares(array $declares, int $start_line, string $first_doc_comment = null) : ast\Node { $ast_declare_elements = []; foreach ($declares as $declare) { $children = [ - 'name' => $declare->key, + 'name' => $declare->key->name, 'value' => self::phpParserNodeToAstNode($declare->value), ]; $doc_comment = self::extractPhpdocComment($declare->getAttribute('comments')) ?? $first_doc_comment; @@ -1939,15 +1973,11 @@ private static function astNodeCall($expr, $args, int $start_line) : ast\Node{ return new ast\Node(ast\AST_CALL, 0, ['expr' => $expr, 'args' => $args], $start_line); } - private static function astNodeMethodCall($expr, $method, ast\Node $args, int $start_line) : ast\Node { - return new ast\Node(ast\AST_METHOD_CALL, 0, ['expr' => $expr, 'method' => $method, 'args' => $args], $start_line); - } - private static function astNodeStaticCall($class, $method, ast\Node $args, int $start_line) : ast\Node { // TODO: is this applicable? if (\is_string($class)) { if (substr($class, 0, 1) === '\\') { - $expr = substr($class, 1); + $class = substr($class, 1); } $class = new ast\Node(ast\AST_NAME, ast\flags\NAME_FQ, ['name' => $class], $start_line); } @@ -2142,11 +2172,3 @@ function sl($node) { } return null; } - -/** @return ?int */ -function el($node) { - if ($node instanceof PhpParser\Node) { - return $node->getAttribute('endLine'); - } - return null; -} diff --git a/test_files/src/namespace_short.php b/test_files/src/namespace_short.php new file mode 100644 index 0000000..7013268 --- /dev/null +++ b/test_files/src/namespace_short.php @@ -0,0 +1,3 @@ +_scanSourceDirForPHP($source_dir); - sort($paths); + $counts = []; + foreach ($paths as $path) { + $counts[$path] = \count(\token_get_all(\file_get_contents($path))); + } + usort($paths, function($a, $b) use ($counts) { return $counts[$a] <=> $counts[$b]; }); $supports40 = self::hasNativeASTSupport(40); $supports45 = self::hasNativeASTSupport(45); $supports50 = self::hasNativeASTSupport(50); @@ -104,6 +108,9 @@ public static function normalizeLineNumbers(ast\Node $node) : ast\Node { /** @dataProvider astValidFileExampleProvider */ public function testFallbackFromParser(string $file_name, int $ast_version) { + if ($ast_version === 40) { + file_put_contents('/tmp/files', "$file_name\n", FILE_APPEND); + } $test_folder_name = basename(dirname($file_name)); if (PHP_VERSION_ID < 70100 && $test_folder_name === 'php71_or_newer') { $this->markTestIncomplete('php-ast cannot parse php7.1 syntax when running in php7.0'); @@ -123,7 +130,7 @@ public function testFallbackFromParser(string $file_name, int $ast_version) { } $this->assertInstanceOf('\ast\Node', $fallback_ast, 'The fallback must also return a tree of php-ast nodes'); - if ($test_folder_name === 'phan_test_files' || $test_folder_name === 'php-src_tests') { + if ($test_folder_name === 'phan_test_files' || $test_folder_name === 'php-src_tests' || $test_folder_name === 'php-src_tests2') { $fallback_ast = self::normalizeLineNumbers($fallback_ast); $ast = self::normalizeLineNumbers($ast); }