File manager - Edit - /home/linknsbh/sabel-eltaqwa.com/assets/lfm/files/shares/events/thumbs/phpstan.tar
Back
phpdoc-parser/CLAUDE.MD 0000644 00000020435 15213400535 0010474 0 ustar 00 # CLAUDE.MD - Instructions for AI Assistant This file contains important instructions for working on the phpdoc-parser project. ## Project Overview **phpstan/phpdoc-parser** is a library that represents PHPDocs with an Abstract Syntax Tree (AST). It supports parsing and modifying PHPDocs, and is primarily used by PHPStan for static analysis. ### Key Features - Parses PHPDoc comments into an AST representation - Supports all PHPDoc tags and types (see [PHPStan documentation](https://phpstan.org/writing-php-code/phpdocs-basics)) - Format-preserving printer for modifying and printing AST nodes - Support for Doctrine Annotations parsing - Nullable, intersection, generic, and conditional types support ### Requirements - PHP ^7.4 || ^8.0 - Platform target: PHP 7.4.6 ## Project Structure ### Source Code (`src/`) The source code is organized into the following main components: 1. **Lexer** (`src/Lexer/`) - `Lexer.php` - Tokenizes PHPDoc strings 2. **Parser** (`src/Parser/`) - `PhpDocParser.php` - Main PHPDoc parser (parses tags and structure) - `TypeParser.php` - Parses PHPDoc type expressions - `ConstExprParser.php` - Parses constant expressions - `TokenIterator.php` - Iterator for tokens - `StringUnescaper.php` - Handles string unescaping - `ParserException.php` - Exception handling 3. **AST** (`src/Ast/`) - `Node.php` - Base AST node interface - `NodeTraverser.php` - Traverses and transforms AST - `NodeVisitor.php` - Visitor pattern for AST traversal - `Type/` - Type nodes (GenericTypeNode, ArrayTypeNode, UnionTypeNode, etc.) - `PhpDoc/` - PHPDoc tag nodes (ParamTagValueNode, ReturnTagValueNode, etc.) - `ConstExpr/` - Constant expression nodes - `NodeVisitor/` - Built-in visitors (CloningVisitor, etc.) 4. **Printer** (`src/Printer/`) - `Printer.php` - Prints AST back to PHPDoc format - `Differ.php` - Computes differences between AST nodes - `DiffElem.php` - Represents diff elements 5. **Configuration** - `ParserConfig.php` - Parser configuration (attributes to use) ### Tests (`tests/PHPStan/`) Tests mirror the source structure and include: 1. **Parser Tests** (`tests/PHPStan/Parser/`) - `TypeParserTest.php` - Type parsing tests - `PhpDocParserTest.php` - PHPDoc parsing tests - `ConstExprParserTest.php` - Constant expression parsing tests - `FuzzyTest.php` - Fuzzy testing - `Doctrine/` - Doctrine annotation test fixtures 2. **AST Tests** (`tests/PHPStan/Ast/`) - `NodeTraverserTest.php` - Node traversal tests - `Attributes/AttributesTest.php` - AST attribute tests - `ToString/` - Tests for converting AST to string - `NodeVisitor/` - Visitor pattern tests 3. **Printer Tests** (`tests/PHPStan/Printer/`) - Tests for format-preserving printing functionality ### Configuration Files - `phpunit.xml` - PHPUnit test configuration - `phpstan.neon` - PHPStan static analysis configuration - `phpstan-baseline.neon` - PHPStan baseline (known issues) - `phpcs.xml` - PHP CodeSniffer configuration - `composer.json` - Dependencies and autoloading ## How the Parser Works The parsing flow follows these steps: 1. **Lexing**: `Lexer` tokenizes the PHPDoc string into tokens 2. **Parsing**: `PhpDocParser` uses `TypeParser` and `ConstExprParser` to build an AST 3. **Traversal/Modification**: `NodeTraverser` with `NodeVisitor` can traverse and modify the AST 4. **Printing**: `Printer` converts the AST back to PHPDoc format (optionally preserving formatting) ### Basic Usage Example ```php $config = new ParserConfig(usedAttributes: []); $lexer = new Lexer($config); $constExprParser = new ConstExprParser($config); $typeParser = new TypeParser($config, $constExprParser); $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); $tokens = new TokenIterator($lexer->tokenize('/** @param Lorem $a */')); $phpDocNode = $phpDocParser->parse($tokens); ``` ### Format-Preserving Printing For format-preserving printing (used when modifying existing PHPDocs), enable these attributes: - `lines` - Preserve line information - `indexes` - Preserve token indexes - `comments` - Preserve comments ## Common Development Tasks ### Adding a New PHPDoc Tag 1. Create a new `*TagValueNode` class in `src/Ast/PhpDoc/` 2. Add parsing logic in `PhpDocParser.php` 3. Add tests in `tests/PHPStan/Parser/PhpDocParserTest.php` 4. Run tests and PHPStan ### Adding a New Type Node 1. Create a new `*TypeNode` class in `src/Ast/Type/` 2. Add parsing logic in `TypeParser.php` 3. Add printing logic in `Printer.php` 4. Add tests in `tests/PHPStan/Parser/TypeParserTest.php` 5. Run tests and PHPStan ### Modifying the Lexer 1. Update token generation in `Lexer.php` 2. Update parsers that consume those tokens 3. Add/update tests 4. Run comprehensive checks with `make check` ## Testing and Quality Checks ### Running Tests Tests are run using PHPUnit: ```bash make tests ``` Or directly: ```bash php vendor/bin/phpunit ``` ### Running PHPStan PHPStan static analysis is run with: ```bash make phpstan ``` Or directly: ```bash php vendor/bin/phpstan ``` ### Running All Checks To run all quality checks (lint, code style, tests, and PHPStan): ```bash make check ``` This runs: - `lint` - PHP syntax checking with parallel-lint - `cs` - Code style checking with phpcs - `tests` - PHPUnit test suite - `phpstan` - Static analysis ## CRITICAL RULES ### ⚠️ MANDATORY: Run After Every Change **You MUST run both tests and PHPStan after every code change:** ```bash make tests && make phpstan ``` Or use the comprehensive check: ```bash make check ``` **DO NOT** commit or consider work complete until both tests and PHPStan pass successfully. ### ⚠️ NEVER Delete Tests **NEVER delete any tests.** Tests are critical to the project's quality and regression prevention. If tests are failing: - Fix the implementation to make tests pass - Only modify tests if they contain actual bugs or if requirements have legitimately changed - When in doubt, ask before modifying any test ## Other Available Commands - `make cs-fix` - Automatically fix code style issues - `make lint` - Check PHP syntax only - `make cs` - Check code style only - `make phpstan-generate-baseline` - Generate PHPStan baseline (use sparingly) ## Workflow Summary 1. Make code changes 2. Run `make tests` - ensure all tests pass 3. Run `make phpstan` - ensure static analysis passes 4. Fix any issues found 5. Commit only when both pass 6. Repeat as needed **Remember: Tests and PHPStan MUST pass before any commit.** ## Coding Standards and Best Practices ### Code Style - Follow PSR-12 coding standards (enforced by phpcs) - Use tabs for indentation (project convention) - Run `make cs-fix` to automatically fix code style issues - Always run `make cs` to verify code style before committing ### PHPStan Rules - Project uses strict PHPStan rules (level max) - All code must pass static analysis - Avoid adding to phpstan-baseline.neon unless absolutely necessary - Type hints are required for all public APIs ### Testing Best Practices - All new features must include tests - Tests should be in the corresponding test directory matching src/ structure - Use data providers for testing multiple similar cases - Test both valid and invalid inputs - Include edge cases and error conditions ### AST Node Conventions - All AST nodes implement the `Node` interface - Nodes should be immutable where possible - Use `__toString()` for debugging output - Implement proper equality checks if needed - Follow the visitor pattern for AST traversal ### Parser Patterns - Parsers should be recursive descent style - Use `TokenIterator` for token consumption - Throw `ParserException` for syntax errors - Support optional attributes through `ParserConfig` - Maintain backwards compatibility when adding features ## Important Notes ### Backwards Compatibility - This library is used by PHPStan and many other tools - Breaking changes should be avoided - New features should be opt-in when possible - Deprecate before removing functionality ### Performance Considerations - The parser is performance-critical (runs on large codebases) - Avoid unnecessary object allocations - Be careful with regex patterns - Consider memory usage in loops ### Documentation - Public APIs should have PHPDoc comments - Complex logic should include inline comments - Update README.md when adding major features - Reference PHPStan documentation for PHPDoc tag specifications phpdoc-parser/README.md 0000644 00000011641 15213400535 0010573 0 ustar 00 <h1 align="center">PHPDoc Parser for PHPStan</h1> <p align="center"> <a href="https://github.com/phpstan/phpdoc-parser/actions"><img src="https://github.com/phpstan/phpdoc-parser/workflows/Build/badge.svg" alt="Build Status"></a> <a href="https://packagist.org/packages/phpstan/phpdoc-parser"><img src="https://poser.pugx.org/phpstan/phpdoc-parser/v/stable" alt="Latest Stable Version"></a> <a href="https://choosealicense.com/licenses/mit/"><img src="https://poser.pugx.org/phpstan/phpstan/license" alt="License"></a> <a href="https://phpstan.org/"><img src="https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat" alt="PHPStan Enabled"></a> </p> This library `phpstan/phpdoc-parser` represents PHPDocs with an AST (Abstract Syntax Tree). It supports parsing and modifying PHPDocs. For the complete list of supported PHPDoc features check out PHPStan documentation. PHPStan is the main (but not the only) user of this library. * [PHPDoc Basics](https://phpstan.org/writing-php-code/phpdocs-basics) (list of PHPDoc tags) * [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) (list of PHPDoc types) * [phpdoc-parser API Reference](https://phpstan.github.io/phpdoc-parser/2.3.x/namespace-PHPStan.PhpDocParser.html) with all the AST node types etc. This parser also supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/2.1.x/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html). ## Installation ``` composer require phpstan/phpdoc-parser ``` ## Basic usage ```php <?php require_once __DIR__ . '/vendor/autoload.php'; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; // basic setup $config = new ParserConfig(usedAttributes: []); $lexer = new Lexer($config); $constExprParser = new ConstExprParser($config); $typeParser = new TypeParser($config, $constExprParser); $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); // parsing and reading a PHPDoc string $tokens = new TokenIterator($lexer->tokenize('/** @param Lorem $a */')); $phpDocNode = $phpDocParser->parse($tokens); // PhpDocNode $paramTags = $phpDocNode->getParamTagValues(); // ParamTagValueNode[] echo $paramTags[0]->parameterName; // '$a' echo $paramTags[0]->type; // IdentifierTypeNode - 'Lorem' ``` ### Format-preserving printer This component can be used to modify the AST and print it again as close as possible to the original. It's heavily inspired by format-preserving printer component in [nikic/PHP-Parser](https://github.com/nikic/PHP-Parser). ```php <?php require_once __DIR__ . '/vendor/autoload.php'; use PHPStan\PhpDocParser\Ast\NodeTraverser; use PHPStan\PhpDocParser\Ast\NodeVisitor\CloningVisitor; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\Printer\Printer; // basic setup with enabled required lexer attributes $config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true, 'comments' => true]); $lexer = new Lexer($config); $constExprParser = new ConstExprParser($config); $typeParser = new TypeParser($config, $constExprParser); $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); $tokens = new TokenIterator($lexer->tokenize('/** @param Lorem $a */')); $phpDocNode = $phpDocParser->parse($tokens); // PhpDocNode $cloningTraverser = new NodeTraverser([new CloningVisitor()]); /** @var PhpDocNode $newPhpDocNode */ [$newPhpDocNode] = $cloningTraverser->traverse([$phpDocNode]); // change something in $newPhpDocNode $newPhpDocNode->getParamTagValues()[0]->type = new IdentifierTypeNode('Ipsum'); // print changed PHPDoc $printer = new Printer(); $newPhpDoc = $printer->printFormatPreserving($newPhpDocNode, $phpDocNode, $tokens); echo $newPhpDoc; // '/** @param Ipsum $a */' ``` ## Code of Conduct This project adheres to a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code. ## Building Initially you need to run `composer install`, or `composer update` in case you aren't working in a folder which was built before. Afterwards you can either run the whole build including linting and coding standards using make or run only tests using make tests phpdoc-parser/LICENSE 0000644 00000002121 15213400535 0010312 0 ustar 00 MIT License Copyright (c) 2016 Ondřej Mirtes Copyright (c) 2025 PHPStan s.r.o. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. phpdoc-parser/composer.json 0000644 00000001617 15213400535 0012040 0 ustar 00 { "name": "phpstan/phpdoc-parser", "description": "PHPDoc parser with support for nullable, intersection and generic types", "license": "MIT", "require": { "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "config": { "platform": { "php": "7.4.6" }, "sort-packages": true, "allow-plugins": { "phpstan/extension-installer": true } }, "autoload": { "psr-4": { "PHPStan\\PhpDocParser\\": [ "src/" ] } }, "autoload-dev": { "psr-4": { "PHPStan\\PhpDocParser\\": [ "tests/PHPStan" ] } }, "minimum-stability": "dev", "prefer-stable": true } phpdoc-parser/src/Parser/TypeParser.php 0000644 00000101064 15213400535 0014145 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Parser; use LogicException; use PHPStan\PhpDocParser\Ast; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use function in_array; use function str_replace; use function strlen; use function strpos; use function substr_compare; class TypeParser { private ParserConfig $config; private ConstExprParser $constExprParser; public function __construct( ParserConfig $config, ConstExprParser $constExprParser ) { $this->config = $config; $this->constExprParser = $constExprParser; } /** @phpstan-impure */ public function parse(TokenIterator $tokens): Ast\Type\TypeNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { $type = $this->parseNullable($tokens); } else { $type = $this->parseAtomic($tokens); $tokens->pushSavePoint(); $tokens->skipNewLineTokensAndConsumeComments(); try { $enrichedType = $this->enrichTypeOnUnionOrIntersection($tokens, $type); } catch (ParserException $parserException) { $enrichedType = null; } if ($enrichedType !== null) { $type = $enrichedType; $tokens->dropSavePoint(); } else { $tokens->rollback(); $type = $this->enrichTypeOnUnionOrIntersection($tokens, $type) ?? $type; } } return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex); } /** @phpstan-impure */ private function enrichTypeOnUnionOrIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): ?Ast\Type\TypeNode { if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { return $this->parseUnion($tokens, $type); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { return $this->parseIntersection($tokens, $type); } return null; } /** * @internal * @template T of Ast\Node * @param T $type * @return T */ public function enrichWithAttributes(TokenIterator $tokens, Ast\Node $type, int $startLine, int $startIndex): Ast\Node { if ($this->config->useLinesAttributes) { $type->setAttribute(Ast\Attribute::START_LINE, $startLine); $type->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine()); } $comments = $tokens->flushComments(); if ($this->config->useCommentsAttributes) { $type->setAttribute(Ast\Attribute::COMMENTS, $comments); } if ($this->config->useIndexAttributes) { $type->setAttribute(Ast\Attribute::START_INDEX, $startIndex); $type->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken()); } return $type; } /** @phpstan-impure */ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { $type = $this->parseNullable($tokens); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { $type = $this->parseConditionalForParameter($tokens, $tokens->currentTokenValue()); } else { $type = $this->parseAtomic($tokens); if ($tokens->isCurrentTokenValue('is')) { $type = $this->parseConditional($tokens, $type); } else { $tokens->skipNewLineTokensAndConsumeComments(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { $type = $this->subParseUnion($tokens, $type); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { $type = $this->subParseIntersection($tokens, $type); } } } return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex); } /** @phpstan-impure */ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $tokens->skipNewLineTokensAndConsumeComments(); $type = $this->subParse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) { $type = $this->enrichWithAttributes($tokens, new Ast\Type\ThisTypeNode(), $startLine, $startIndex); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex); } $currentTokenValue = $tokens->currentTokenValue(); $tokens->pushSavePoint(); // because of ConstFetchNode if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { $type = $this->enrichWithAttributes($tokens, new Ast\Type\IdentifierTypeNode($currentTokenValue), $startLine, $startIndex); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { $tokens->dropSavePoint(); // because of ConstFetchNode if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { $tokens->pushSavePoint(); $isHtml = $this->isHtml($tokens); $tokens->rollback(); if ($isHtml) { return $type; } $origType = $type; $type = $this->tryParseCallable($tokens, $type, true); if ($type === $origType) { $type = $this->parseGeneric($tokens, $type); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } } } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $type = $this->tryParseCallable($tokens, $type, false); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } elseif (in_array($type->name, [ Ast\Type\ArrayShapeNode::KIND_ARRAY, Ast\Type\ArrayShapeNode::KIND_LIST, Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_ARRAY, Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_LIST, 'object', ], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { if ($type->name === 'object') { $type = $this->parseObjectShape($tokens); } else { $type = $this->parseArrayShape($tokens, $type, $type->name); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess( $tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex), ); } } return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex); } else { $tokens->rollback(); // because of ConstFetchNode } } else { $tokens->dropSavePoint(); // because of ConstFetchNode } $currentTokenValue = $tokens->currentTokenValue(); $currentTokenType = $tokens->currentTokenType(); $currentTokenOffset = $tokens->currentTokenOffset(); $currentTokenLine = $tokens->currentTokenLine(); try { $constExpr = $this->constExprParser->parse($tokens); if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { throw new ParserException( $currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine, ); } $type = $this->enrichWithAttributes( $tokens, new Ast\Type\ConstTypeNode($constExpr), $startLine, $startIndex, ); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $type; } catch (LogicException $e) { throw new ParserException( $currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine, ); } } /** @phpstan-impure */ private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { $types[] = $this->parseAtomic($tokens); $tokens->pushSavePoint(); $tokens->skipNewLineTokensAndConsumeComments(); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { $tokens->rollback(); break; } $tokens->dropSavePoint(); } return new Ast\Type\UnionTypeNode($types); } /** @phpstan-impure */ private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { $tokens->skipNewLineTokensAndConsumeComments(); $types[] = $this->parseAtomic($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } return new Ast\Type\UnionTypeNode($types); } /** @phpstan-impure */ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { $types[] = $this->parseAtomic($tokens); $tokens->pushSavePoint(); $tokens->skipNewLineTokensAndConsumeComments(); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { $tokens->rollback(); break; } $tokens->dropSavePoint(); } return new Ast\Type\IntersectionTypeNode($types); } /** @phpstan-impure */ private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { $tokens->skipNewLineTokensAndConsumeComments(); $types[] = $this->parseAtomic($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } return new Ast\Type\IntersectionTypeNode($types); } /** @phpstan-impure */ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $negated = false; if ($tokens->isCurrentTokenValue('not')) { $negated = true; $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } $targetType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->skipNewLineTokensAndConsumeComments(); $ifType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $tokens->skipNewLineTokensAndConsumeComments(); $elseType = $this->subParse($tokens); return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated); } /** @phpstan-impure */ private function parseConditionalForParameter(TokenIterator $tokens, string $parameterName): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); $tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'is'); $negated = false; if ($tokens->isCurrentTokenValue('not')) { $negated = true; $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } $targetType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->skipNewLineTokensAndConsumeComments(); $ifType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $tokens->skipNewLineTokensAndConsumeComments(); $elseType = $this->subParse($tokens); return new Ast\Type\ConditionalTypeForParameterNode($parameterName, $targetType, $ifType, $elseType, $negated); } /** @phpstan-impure */ private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); $type = $this->parseAtomic($tokens); return new Ast\Type\NullableTypeNode($type); } /** @phpstan-impure */ public function isHtml(TokenIterator $tokens): bool { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { return false; } $htmlTagName = $tokens->currentTokenValue(); $tokens->next(); if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { return false; } $endTag = '</' . $htmlTagName . '>'; $endTagSearchOffset = - strlen($endTag); while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) { if ( ( $tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET) && strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== false ) || substr_compare($tokens->currentTokenValue(), $endTag, $endTagSearchOffset) === 0 ) { return true; } $tokens->next(); } return false; } /** @phpstan-impure */ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $tokens->skipNewLineTokensAndConsumeComments(); $startLine = $baseType->getAttribute(Ast\Attribute::START_LINE); $startIndex = $baseType->getAttribute(Ast\Attribute::START_INDEX); $genericTypes = []; $variances = []; $isFirst = true; while ( $isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) ) { $tokens->skipNewLineTokensAndConsumeComments(); // trailing comma case if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { break; } $isFirst = false; [$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } $type = new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances); if ($startLine !== null && $startIndex !== null) { $type = $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex); } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); return $type; } /** * @phpstan-impure * @return array{Ast\Type\TypeNode, Ast\Type\GenericTypeNode::VARIANCE_*} */ public function parseGenericTypeArgument(TokenIterator $tokens): array { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) { return [ $this->enrichWithAttributes($tokens, new Ast\Type\IdentifierTypeNode('mixed'), $startLine, $startIndex), Ast\Type\GenericTypeNode::VARIANCE_BIVARIANT, ]; } if ($tokens->tryConsumeTokenValue('contravariant')) { $variance = Ast\Type\GenericTypeNode::VARIANCE_CONTRAVARIANT; } elseif ($tokens->tryConsumeTokenValue('covariant')) { $variance = Ast\Type\GenericTypeNode::VARIANCE_COVARIANT; } else { $variance = Ast\Type\GenericTypeNode::VARIANCE_INVARIANT; } $type = $this->parse($tokens); return [$type, $variance]; } /** * @throws ParserException * @param ?callable(TokenIterator): string $parseDescription */ public function parseTemplateTagValue( TokenIterator $tokens, ?callable $parseDescription = null ): TemplateTagValueNode { $name = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $upperBound = $lowerBound = null; if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) { $upperBound = $this->parse($tokens); } if ($tokens->tryConsumeTokenValue('super')) { $lowerBound = $this->parse($tokens); } if ($tokens->tryConsumeTokenValue('=')) { $default = $this->parse($tokens); } else { $default = null; } if ($parseDescription !== null) { $description = $parseDescription($tokens); } else { $description = ''; } if ($name === '') { throw new LogicException('Template tag name cannot be empty.'); } return new Ast\PhpDoc\TemplateTagValueNode($name, $upperBound, $description, $default, $lowerBound); } /** @phpstan-impure */ private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier, bool $hasTemplate): Ast\Type\TypeNode { $templates = $hasTemplate ? $this->parseCallableTemplates($tokens) : []; $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); $tokens->skipNewLineTokensAndConsumeComments(); $parameters = []; if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { $parameters[] = $this->parseCallableParameter($tokens); $tokens->skipNewLineTokensAndConsumeComments(); while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { $tokens->skipNewLineTokensAndConsumeComments(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { break; } $parameters[] = $this->parseCallableParameter($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $returnType = $this->enrichWithAttributes($tokens, $this->parseCallableReturnType($tokens), $startLine, $startIndex); return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType, $templates); } /** * @return Ast\PhpDoc\TemplateTagValueNode[] * * @phpstan-impure */ private function parseCallableTemplates(TokenIterator $tokens): array { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $templates = []; $isFirst = true; while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { $tokens->skipNewLineTokensAndConsumeComments(); // trailing comma case if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { break; } $isFirst = false; $templates[] = $this->parseCallableTemplateArgument($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); return $templates; } private function parseCallableTemplateArgument(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); return $this->enrichWithAttributes( $tokens, $this->parseTemplateTagValue($tokens), $startLine, $startIndex, ); } /** @phpstan-impure */ private function parseCallableParameter(TokenIterator $tokens): Ast\Type\CallableTypeParameterNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $type = $this->parse($tokens); $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { $parameterName = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); } else { $parameterName = ''; } $isOptional = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); return $this->enrichWithAttributes( $tokens, new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional), $startLine, $startIndex, ); } /** @phpstan-impure */ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { return $this->parseNullable($tokens); } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $type = $this->subParse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $type; } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) { $type = new Ast\Type\ThisTypeNode(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, )); } return $type; } else { $currentTokenValue = $tokens->currentTokenValue(); $tokens->pushSavePoint(); // because of ConstFetchNode if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { $type = new Ast\Type\IdentifierTypeNode($currentTokenValue); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { $type = $this->parseGeneric( $tokens, $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, ), ); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, )); } } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, )); } elseif (in_array($type->name, [ Ast\Type\ArrayShapeNode::KIND_ARRAY, Ast\Type\ArrayShapeNode::KIND_LIST, Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_ARRAY, Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_LIST, 'object', ], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { if ($type->name === 'object') { $type = $this->parseObjectShape($tokens); } else { $type = $this->parseArrayShape($tokens, $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, ), $type->name); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, )); } } return $type; } else { $tokens->rollback(); // because of ConstFetchNode } } else { $tokens->dropSavePoint(); // because of ConstFetchNode } } $currentTokenValue = $tokens->currentTokenValue(); $currentTokenType = $tokens->currentTokenType(); $currentTokenOffset = $tokens->currentTokenOffset(); $currentTokenLine = $tokens->currentTokenLine(); try { $constExpr = $this->constExprParser->parse($tokens); if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { throw new ParserException( $currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine, ); } $type = $this->enrichWithAttributes( $tokens, new Ast\Type\ConstTypeNode($constExpr), $startLine, $startIndex, ); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $type; } catch (LogicException $e) { throw new ParserException( $currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine, ); } } /** @phpstan-impure */ private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier, bool $hasTemplate): Ast\Type\TypeNode { try { $tokens->pushSavePoint(); $type = $this->parseCallable($tokens, $identifier, $hasTemplate); $tokens->dropSavePoint(); } catch (ParserException $e) { $tokens->rollback(); $type = $identifier; } return $type; } /** @phpstan-impure */ private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $startLine = $type->getAttribute(Ast\Attribute::START_LINE); $startIndex = $type->getAttribute(Ast\Attribute::START_INDEX); try { while ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $tokens->pushSavePoint(); $canBeOffsetAccessType = !$tokens->isPrecededByHorizontalWhitespace(); $tokens->consumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET); if ($canBeOffsetAccessType && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET)) { $offset = $this->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET); $tokens->dropSavePoint(); $type = new Ast\Type\OffsetAccessTypeNode($type, $offset); if ($startLine !== null && $startIndex !== null) { $type = $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, ); } } else { $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET); $tokens->dropSavePoint(); $type = new Ast\Type\ArrayTypeNode($type); if ($startLine !== null && $startIndex !== null) { $type = $this->enrichWithAttributes( $tokens, $type, $startLine, $startIndex, ); } } } } catch (ParserException $e) { $tokens->rollback(); } return $type; } /** * @phpstan-impure * @param Ast\Type\ArrayShapeNode::KIND_* $kind */ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type, string $kind): Ast\Type\ArrayShapeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET); $items = []; $sealed = true; $unsealedType = null; $done = false; do { $tokens->skipNewLineTokensAndConsumeComments(); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { return Ast\Type\ArrayShapeNode::createSealed($items, $kind); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) { $sealed = false; $tokens->skipNewLineTokensAndConsumeComments(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { if ($kind === Ast\Type\ArrayShapeNode::KIND_ARRAY) { $unsealedType = $this->parseArrayShapeUnsealedType($tokens); } else { $unsealedType = $this->parseListShapeUnsealedType($tokens); } $tokens->skipNewLineTokensAndConsumeComments(); } $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA); break; } $items[] = $this->parseArrayShapeItem($tokens); $tokens->skipNewLineTokensAndConsumeComments(); if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { $done = true; } if ($tokens->currentTokenType() !== Lexer::TOKEN_COMMENT) { continue; } $tokens->next(); } while (!$done); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET); if ($sealed) { return Ast\Type\ArrayShapeNode::createSealed($items, $kind); } return Ast\Type\ArrayShapeNode::createUnsealed($items, $unsealedType, $kind); } /** @phpstan-impure */ private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShapeItemNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); // parse any comments above the item $tokens->skipNewLineTokensAndConsumeComments(); try { $tokens->pushSavePoint(); $key = $this->parseArrayShapeKey($tokens); $optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $value = $this->parse($tokens); $tokens->dropSavePoint(); return $this->enrichWithAttributes( $tokens, new Ast\Type\ArrayShapeItemNode($key, $optional, $value), $startLine, $startIndex, ); } catch (ParserException $e) { $tokens->rollback(); $value = $this->parse($tokens); return $this->enrichWithAttributes( $tokens, new Ast\Type\ArrayShapeItemNode(null, false, $value), $startLine, $startIndex, ); } } /** * @phpstan-impure * @return Ast\ConstExpr\ConstExprIntegerNode|Ast\ConstExpr\ConstExprStringNode|Ast\ConstExpr\ConstFetchNode|Ast\Type\IdentifierTypeNode */ private function parseArrayShapeKey(TokenIterator $tokens) { $startIndex = $tokens->currentTokenIndex(); $startLine = $tokens->currentTokenLine(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { $key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue())); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED); $tokens->next(); } else { $identifier = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) { $classConstantName = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $key = new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName); } else { $key = new Ast\Type\IdentifierTypeNode($identifier); } } return $this->enrichWithAttributes( $tokens, $key, $startLine, $startIndex, ); } /** * @phpstan-impure */ private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\ArrayShapeUnsealedTypeNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $tokens->skipNewLineTokensAndConsumeComments(); $valueType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $keyType = null; if ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { $tokens->skipNewLineTokensAndConsumeComments(); $keyType = $valueType; $valueType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); return $this->enrichWithAttributes( $tokens, new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, $keyType), $startLine, $startIndex, ); } /** * @phpstan-impure */ private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\ArrayShapeUnsealedTypeNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $tokens->skipNewLineTokensAndConsumeComments(); $valueType = $this->parse($tokens); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); return $this->enrichWithAttributes( $tokens, new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, null), $startLine, $startIndex, ); } /** * @phpstan-impure */ private function parseObjectShape(TokenIterator $tokens): Ast\Type\ObjectShapeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET); $items = []; do { $tokens->skipNewLineTokensAndConsumeComments(); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { return new Ast\Type\ObjectShapeNode($items); } $items[] = $this->parseObjectShapeItem($tokens); $tokens->skipNewLineTokensAndConsumeComments(); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); $tokens->skipNewLineTokensAndConsumeComments(); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET); return new Ast\Type\ObjectShapeNode($items); } /** @phpstan-impure */ private function parseObjectShapeItem(TokenIterator $tokens): Ast\Type\ObjectShapeItemNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $tokens->skipNewLineTokensAndConsumeComments(); $key = $this->parseObjectShapeKey($tokens); $optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $value = $this->parse($tokens); return $this->enrichWithAttributes( $tokens, new Ast\Type\ObjectShapeItemNode($key, $optional, $value), $startLine, $startIndex, ); } /** * @phpstan-impure * @return Ast\ConstExpr\ConstExprStringNode|Ast\Type\IdentifierTypeNode */ private function parseObjectShapeKey(TokenIterator $tokens) { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED); $tokens->next(); } else { $key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue()); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex); } } phpdoc-parser/src/Parser/PhpDocParser.php 0000644 00000113071 15213400535 0014402 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Parser; use LogicException; use PHPStan\PhpDocParser\Ast; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use PHPStan\ShouldNotHappenException; use function array_key_exists; use function count; use function rtrim; use function str_replace; use function trim; /** * @phpstan-import-type ValueType from Doctrine\DoctrineArgument as DoctrineValueType */ class PhpDocParser { private const DISALLOWED_DESCRIPTION_START_TOKENS = [ Lexer::TOKEN_UNION, Lexer::TOKEN_INTERSECTION, ]; private ParserConfig $config; private TypeParser $typeParser; private ConstExprParser $constantExprParser; private ConstExprParser $doctrineConstantExprParser; public function __construct( ParserConfig $config, TypeParser $typeParser, ConstExprParser $constantExprParser ) { $this->config = $config; $this->typeParser = $typeParser; $this->constantExprParser = $constantExprParser; $this->doctrineConstantExprParser = $constantExprParser->toDoctrine(); } public function parse(TokenIterator $tokens): Ast\PhpDoc\PhpDocNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PHPDOC); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $children = []; if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { $lastChild = $this->parseChild($tokens); $children[] = $lastChild; while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { if ( $lastChild instanceof Ast\PhpDoc\PhpDocTagNode && ( $lastChild->value instanceof Doctrine\DoctrineTagValueNode || $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode ) ) { $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { break; } $lastChild = $this->parseChild($tokens); $children[] = $lastChild; continue; } if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) { break; } if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { break; } $lastChild = $this->parseChild($tokens); $children[] = $lastChild; } } try { $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC); } catch (ParserException $e) { $name = ''; $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if (count($children) > 0) { $lastChild = $children[count($children) - 1]; if ($lastChild instanceof Ast\PhpDoc\PhpDocTagNode) { $name = $lastChild->name; $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); } } $tag = new Ast\PhpDoc\PhpDocTagNode( $name, $this->enrichWithAttributes( $tokens, new Ast\PhpDoc\InvalidTagValueNode($e->getMessage(), $e), $startLine, $startIndex, ), ); $tokens->forwardToTheEnd(); $comments = $tokens->flushComments(); if ($comments !== []) { throw new LogicException('Comments should already be flushed'); } return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode([$this->enrichWithAttributes($tokens, $tag, $startLine, $startIndex)]), 1, 0); } $comments = $tokens->flushComments(); if ($comments !== []) { throw new LogicException('Comments should already be flushed'); } return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode($children), 1, 0); } /** @phpstan-impure */ private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode { if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); return $this->enrichWithAttributes($tokens, $this->parseTag($tokens), $startLine, $startIndex); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_TAG)) { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $tag = $tokens->currentTokenValue(); $tokens->next(); $tagStartLine = $tokens->currentTokenLine(); $tagStartIndex = $tokens->currentTokenIndex(); return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocTagNode( $tag, $this->enrichWithAttributes( $tokens, $this->parseDoctrineTagValue($tokens, $tag), $tagStartLine, $tagStartIndex, ), ), $startLine, $startIndex); } $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $text = $this->parseText($tokens); return $this->enrichWithAttributes($tokens, $text, $startLine, $startIndex); } /** * @template T of Ast\Node * @param T $tag * @return T */ private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex): Ast\Node { if ($this->config->useLinesAttributes) { $tag->setAttribute(Ast\Attribute::START_LINE, $startLine); $tag->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine()); } if ($this->config->useIndexAttributes) { $tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex); $tag->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken()); } return $tag; } private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode { $text = ''; $endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END]; $savepoint = false; // if the next token is EOL, everything below is skipped and empty string is returned while (true) { $tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens); $text .= $tmpText; // stop if we're not at EOL - meaning it's the end of PHPDoc if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC)) { break; } if (!$savepoint) { $tokens->pushSavePoint(); $savepoint = true; } elseif ($tmpText !== '') { $tokens->dropSavePoint(); $tokens->pushSavePoint(); } $tokens->pushSavePoint(); $tokens->next(); // if we're at EOL, check what's next // if next is a PHPDoc tag, EOL, or end of PHPDoc, stop if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) { $tokens->rollback(); break; } // otherwise if the next is text, continue building the description string $tokens->dropSavePoint(); $text .= $tokens->getDetectedNewline() ?? "\n"; } if ($savepoint) { $tokens->rollback(); $text = rtrim($text, $tokens->getDetectedNewline() ?? "\n"); } return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t")); } private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens): string { $text = ''; $endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END]; $savepoint = false; // if the next token is EOL, everything below is skipped and empty string is returned while (true) { $tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens); $text .= $tmpText; // stop if we're not at EOL - meaning it's the end of PHPDoc if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC)) { if (!$tokens->isPrecededByHorizontalWhitespace()) { return trim($text . $this->parseText($tokens)->text, " \t"); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) { $tokens->pushSavePoint(); $child = $this->parseChild($tokens); if ($child instanceof Ast\PhpDoc\PhpDocTagNode) { if ( $child->value instanceof Ast\PhpDoc\GenericTagValueNode || $child->value instanceof Doctrine\DoctrineTagValueNode ) { $tokens->rollback(); break; } if ($child->value instanceof Ast\PhpDoc\InvalidTagValueNode) { $tokens->rollback(); $tokens->pushSavePoint(); $tokens->next(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $tokens->rollback(); break; } $tokens->rollback(); return trim($text . $this->parseText($tokens)->text, " \t"); } } $tokens->rollback(); return trim($text . $this->parseText($tokens)->text, " \t"); } break; } if (!$savepoint) { $tokens->pushSavePoint(); $savepoint = true; } elseif ($tmpText !== '') { $tokens->dropSavePoint(); $tokens->pushSavePoint(); } $tokens->pushSavePoint(); $tokens->next(); // if we're at EOL, check what's next // if next is a PHPDoc tag, EOL, or end of PHPDoc, stop if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) { $tokens->rollback(); break; } // otherwise if the next is text, continue building the description string $tokens->dropSavePoint(); $text .= $tokens->getDetectedNewline() ?? "\n"; } if ($savepoint) { $tokens->rollback(); $text = rtrim($text, $tokens->getDetectedNewline() ?? "\n"); } return trim($text, " \t"); } public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode { $tag = $tokens->currentTokenValue(); $tokens->next(); $value = $this->parseTagValue($tokens, $tag); return new Ast\PhpDoc\PhpDocTagNode($tag, $value); } public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); try { $tokens->pushSavePoint(); switch ($tag) { case '@param': case '@phpstan-param': case '@psalm-param': case '@phan-param': $tagValue = $this->parseParamTagValue($tokens); break; case '@param-immediately-invoked-callable': case '@phpstan-param-immediately-invoked-callable': $tagValue = $this->parseParamImmediatelyInvokedCallableTagValue($tokens); break; case '@param-later-invoked-callable': case '@phpstan-param-later-invoked-callable': $tagValue = $this->parseParamLaterInvokedCallableTagValue($tokens); break; case '@param-closure-this': case '@phpstan-param-closure-this': $tagValue = $this->parseParamClosureThisTagValue($tokens); break; case '@pure-unless-callable-is-impure': case '@phpstan-pure-unless-callable-is-impure': $tagValue = $this->parsePureUnlessCallableIsImpureTagValue($tokens); break; case '@var': case '@phpstan-var': case '@psalm-var': case '@phan-var': $tagValue = $this->parseVarTagValue($tokens); break; case '@return': case '@phpstan-return': case '@psalm-return': case '@phan-return': case '@phan-real-return': $tagValue = $this->parseReturnTagValue($tokens); break; case '@throws': case '@phpstan-throws': $tagValue = $this->parseThrowsTagValue($tokens); break; case '@mixin': case '@phan-mixin': $tagValue = $this->parseMixinTagValue($tokens); break; case '@psalm-require-extends': case '@phpstan-require-extends': $tagValue = $this->parseRequireExtendsTagValue($tokens); break; case '@psalm-require-implements': case '@phpstan-require-implements': $tagValue = $this->parseRequireImplementsTagValue($tokens); break; case '@psalm-inheritors': case '@phpstan-sealed': $tagValue = $this->parseSealedTagValue($tokens); break; case '@deprecated': $tagValue = $this->parseDeprecatedTagValue($tokens); break; case '@property': case '@property-read': case '@property-write': case '@phpstan-property': case '@phpstan-property-read': case '@phpstan-property-write': case '@psalm-property': case '@psalm-property-read': case '@psalm-property-write': case '@phan-property': case '@phan-property-read': case '@phan-property-write': $tagValue = $this->parsePropertyTagValue($tokens); break; case '@method': case '@phpstan-method': case '@psalm-method': case '@phan-method': $tagValue = $this->parseMethodTagValue($tokens); break; case '@template': case '@phpstan-template': case '@psalm-template': case '@phan-template': case '@template-covariant': case '@phpstan-template-covariant': case '@psalm-template-covariant': case '@template-contravariant': case '@phpstan-template-contravariant': case '@psalm-template-contravariant': $tagValue = $this->typeParser->parseTemplateTagValue( $tokens, fn ($tokens) => $this->parseOptionalDescription($tokens, true), ); break; case '@extends': case '@phpstan-extends': case '@phan-extends': case '@phan-inherits': case '@template-extends': $tagValue = $this->parseExtendsTagValue('@extends', $tokens); break; case '@implements': case '@phpstan-implements': case '@template-implements': $tagValue = $this->parseExtendsTagValue('@implements', $tokens); break; case '@use': case '@phpstan-use': case '@template-use': $tagValue = $this->parseExtendsTagValue('@use', $tokens); break; case '@phpstan-type': case '@psalm-type': case '@phan-type': $tagValue = $this->parseTypeAliasTagValue($tokens); break; case '@phpstan-import-type': case '@psalm-import-type': $tagValue = $this->parseTypeAliasImportTagValue($tokens); break; case '@phpstan-assert': case '@phpstan-assert-if-true': case '@phpstan-assert-if-false': case '@psalm-assert': case '@psalm-assert-if-true': case '@psalm-assert-if-false': case '@phan-assert': case '@phan-assert-if-true': case '@phan-assert-if-false': $tagValue = $this->parseAssertTagValue($tokens); break; case '@phpstan-this-out': case '@phpstan-self-out': case '@psalm-this-out': case '@psalm-self-out': $tagValue = $this->parseSelfOutTagValue($tokens); break; case '@param-out': case '@phpstan-param-out': case '@psalm-param-out': $tagValue = $this->parseParamOutTagValue($tokens); break; default: if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $tagValue = $this->parseDoctrineTagValue($tokens, $tag); } else { $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens)); } break; } $tokens->dropSavePoint(); } catch (ParserException $e) { $tokens->rollback(); $tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens, false), $e); } return $this->enrichWithAttributes($tokens, $tagValue, $startLine, $startIndex); } private function parseDoctrineTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); return new Doctrine\DoctrineTagValueNode( $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineAnnotation($tag, $this->parseDoctrineArguments($tokens, false)), $startLine, $startIndex, ), $this->parseOptionalDescriptionAfterDoctrineTag($tokens), ); } /** * @return list<Doctrine\DoctrineArgument> */ private function parseDoctrineArguments(TokenIterator $tokens, bool $deep): array { if (!$tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { return []; } if (!$deep) { $tokens->addEndOfLineToSkippedTokens(); } $arguments = []; try { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); do { if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { break; } $arguments[] = $this->parseDoctrineArgument($tokens); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); } finally { if (!$deep) { $tokens->removeEndOfLineFromSkippedTokens(); } } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); return $arguments; } private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\DoctrineArgument { if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)), $startLine, $startIndex, ); } $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); try { $tokens->pushSavePoint(); $currentValue = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $key = $this->enrichWithAttributes( $tokens, new IdentifierTypeNode($currentValue), $startLine, $startIndex, ); $tokens->consumeTokenType(Lexer::TOKEN_EQUAL); $value = $this->parseDoctrineArgumentValue($tokens); $tokens->dropSavePoint(); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineArgument($key, $value), $startLine, $startIndex, ); } catch (ParserException $e) { $tokens->rollback(); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)), $startLine, $startIndex, ); } } /** * @return DoctrineValueType */ private function parseDoctrineArgumentValue(TokenIterator $tokens) { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG)) { $name = $tokens->currentTokenValue(); $tokens->next(); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineAnnotation($name, $this->parseDoctrineArguments($tokens, true)), $startLine, $startIndex, ); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) { $items = []; do { if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { break; } $items[] = $this->parseDoctrineArrayItem($tokens); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineArray($items), $startLine, $startIndex, ); } $currentTokenValue = $tokens->currentTokenValue(); $tokens->pushSavePoint(); // because of ConstFetchNode if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { $identifier = $this->enrichWithAttributes( $tokens, new Ast\Type\IdentifierTypeNode($currentTokenValue), $startLine, $startIndex, ); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { $tokens->dropSavePoint(); return $identifier; } $tokens->rollback(); // because of ConstFetchNode } else { $tokens->dropSavePoint(); // because of ConstFetchNode } $currentTokenValue = $tokens->currentTokenValue(); $currentTokenType = $tokens->currentTokenType(); $currentTokenOffset = $tokens->currentTokenOffset(); $currentTokenLine = $tokens->currentTokenLine(); try { $constExpr = $this->doctrineConstantExprParser->parse($tokens); if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { throw new ParserException( $currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine, ); } return $constExpr; } catch (LogicException $e) { throw new ParserException( $currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine, ); } } private function parseDoctrineArrayItem(TokenIterator $tokens): Doctrine\DoctrineArrayItem { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); try { $tokens->pushSavePoint(); $key = $this->parseDoctrineArrayKey($tokens); if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) { if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COLON)) { $tokens->consumeTokenType(Lexer::TOKEN_EQUAL); // will throw exception } } $value = $this->parseDoctrineArgumentValue($tokens); $tokens->dropSavePoint(); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineArrayItem($key, $value), $startLine, $startIndex, ); } catch (ParserException $e) { $tokens->rollback(); return $this->enrichWithAttributes( $tokens, new Doctrine\DoctrineArrayItem(null, $this->parseDoctrineArgumentValue($tokens)), $startLine, $startIndex, ); } } /** * @return ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode */ private function parseDoctrineArrayKey(TokenIterator $tokens) { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { $key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue())); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) { $key = $this->doctrineConstantExprParser->parseDoctrineString($tokens->currentTokenValue(), $tokens); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { $value = $tokens->currentTokenValue(); $tokens->next(); $key = $this->doctrineConstantExprParser->parseDoctrineString($value, $tokens); } else { $currentTokenValue = $tokens->currentTokenValue(); $tokens->pushSavePoint(); // because of ConstFetchNode if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { $tokens->dropSavePoint(); throw new ParserException( $tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER, null, $tokens->currentTokenLine(), ); } if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { $tokens->dropSavePoint(); return $this->enrichWithAttributes( $tokens, new IdentifierTypeNode($currentTokenValue), $startLine, $startIndex, ); } $tokens->rollback(); $constExpr = $this->doctrineConstantExprParser->parse($tokens); if (!$constExpr instanceof Ast\ConstExpr\ConstFetchNode) { throw new ParserException( $tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER, null, $tokens->currentTokenLine(), ); } return $constExpr; } return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex); } /** * @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode */ private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode { if ( $tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE, Lexer::TOKEN_VARIADIC, Lexer::TOKEN_VARIABLE) ) { $type = null; } else { $type = $this->typeParser->parse($tokens); } $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); if ($type !== null) { return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference); } return new Ast\PhpDoc\TypelessParamTagValueNode($isVariadic, $parameterName, $description, $isReference); } private function parseParamImmediatelyInvokedCallableTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode { $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode($parameterName, $description); } private function parseParamLaterInvokedCallableTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode { $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode($parameterName, $description); } private function parseParamClosureThisTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamClosureThisTagValueNode { $type = $this->typeParser->parse($tokens); $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\ParamClosureThisTagValueNode($type, $parameterName, $description); } private function parsePureUnlessCallableIsImpureTagValue(TokenIterator $tokens): Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode { $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode($parameterName, $description); } private function parseVarTagValue(TokenIterator $tokens): Ast\PhpDoc\VarTagValueNode { $type = $this->typeParser->parse($tokens); $variableName = $this->parseOptionalVariableName($tokens); $description = $this->parseOptionalDescription($tokens, $variableName === ''); return new Ast\PhpDoc\VarTagValueNode($type, $variableName, $description); } private function parseReturnTagValue(TokenIterator $tokens): Ast\PhpDoc\ReturnTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\ReturnTagValueNode($type, $description); } private function parseThrowsTagValue(TokenIterator $tokens): Ast\PhpDoc\ThrowsTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\ThrowsTagValueNode($type, $description); } private function parseMixinTagValue(TokenIterator $tokens): Ast\PhpDoc\MixinTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\MixinTagValueNode($type, $description); } private function parseRequireExtendsTagValue(TokenIterator $tokens): Ast\PhpDoc\RequireExtendsTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\RequireExtendsTagValueNode($type, $description); } private function parseRequireImplementsTagValue(TokenIterator $tokens): Ast\PhpDoc\RequireImplementsTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\RequireImplementsTagValueNode($type, $description); } private function parseSealedTagValue(TokenIterator $tokens): Ast\PhpDoc\SealedTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\SealedTagValueNode($type, $description); } private function parseDeprecatedTagValue(TokenIterator $tokens): Ast\PhpDoc\DeprecatedTagValueNode { $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\DeprecatedTagValueNode($description); } private function parsePropertyTagValue(TokenIterator $tokens): Ast\PhpDoc\PropertyTagValueNode { $type = $this->typeParser->parse($tokens); $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\PropertyTagValueNode($type, $parameterName, $description); } private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueNode { $staticKeywordOrReturnTypeOrMethodName = $this->typeParser->parse($tokens); if ($staticKeywordOrReturnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode && $staticKeywordOrReturnTypeOrMethodName->name === 'static') { $isStatic = true; $returnTypeOrMethodName = $this->typeParser->parse($tokens); } else { $isStatic = false; $returnTypeOrMethodName = $staticKeywordOrReturnTypeOrMethodName; } if ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { $returnType = $returnTypeOrMethodName; $methodName = $tokens->currentTokenValue(); $tokens->next(); } elseif ($returnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode) { $returnType = $isStatic ? $staticKeywordOrReturnTypeOrMethodName : null; $methodName = $returnTypeOrMethodName->name; $isStatic = false; } else { $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); // will throw exception exit; } $templateTypes = []; if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { do { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $templateTypes[] = $this->enrichWithAttributes( $tokens, $this->typeParser->parseTemplateTagValue($tokens), $startLine, $startIndex, ); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); } $parameters = []; $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { $parameters[] = $this->parseMethodTagValueParameter($tokens); while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { $parameters[] = $this->parseMethodTagValueParameter($tokens); } } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes); } private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); switch ($tokens->currentTokenType()) { case Lexer::TOKEN_IDENTIFIER: case Lexer::TOKEN_OPEN_PARENTHESES: case Lexer::TOKEN_NULLABLE: $parameterType = $this->typeParser->parse($tokens); break; default: $parameterType = null; } $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); $parameterName = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) { $defaultValue = $this->constantExprParser->parse($tokens); } else { $defaultValue = null; } return $this->enrichWithAttributes( $tokens, new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue), $startLine, $startIndex, ); } private function parseExtendsTagValue(string $tagName, TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $baseType = new IdentifierTypeNode($tokens->currentTokenValue()); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $type = $this->typeParser->parseGeneric( $tokens, $this->typeParser->enrichWithAttributes($tokens, $baseType, $startLine, $startIndex), ); $description = $this->parseOptionalDescription($tokens, true); switch ($tagName) { case '@extends': return new Ast\PhpDoc\ExtendsTagValueNode($type, $description); case '@implements': return new Ast\PhpDoc\ImplementsTagValueNode($type, $description); case '@use': return new Ast\PhpDoc\UsesTagValueNode($type, $description); } throw new ShouldNotHappenException(); } private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasTagValueNode { $alias = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); // support phan-type/psalm-type syntax $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); try { $type = $this->typeParser->parse($tokens); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { throw new ParserException( $tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_PHPDOC_EOL, null, $tokens->currentTokenLine(), ); } } return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); } catch (ParserException $e) { $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\TypeAliasTagValueNode( $alias, $this->enrichWithAttributes($tokens, new Ast\Type\InvalidTypeNode($e), $startLine, $startIndex), ); } } private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasImportTagValueNode { $importedAlias = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'from'); $identifierStartLine = $tokens->currentTokenLine(); $identifierStartIndex = $tokens->currentTokenIndex(); $importedFrom = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $importedFromType = $this->enrichWithAttributes( $tokens, new IdentifierTypeNode($importedFrom), $identifierStartLine, $identifierStartIndex, ); $importedAs = null; if ($tokens->tryConsumeTokenValue('as')) { $importedAs = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, $importedFromType, $importedAs); } /** * @return Ast\PhpDoc\AssertTagValueNode|Ast\PhpDoc\AssertTagPropertyValueNode|Ast\PhpDoc\AssertTagMethodValueNode */ private function parseAssertTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode { $isNegated = $tokens->tryConsumeTokenType(Lexer::TOKEN_NEGATED); $isEquality = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); $type = $this->typeParser->parse($tokens); $parameter = $this->parseAssertParameter($tokens); $description = $this->parseOptionalDescription($tokens, false); if (array_key_exists('method', $parameter)) { return new Ast\PhpDoc\AssertTagMethodValueNode($type, $parameter['parameter'], $parameter['method'], $isNegated, $description, $isEquality); } elseif (array_key_exists('property', $parameter)) { return new Ast\PhpDoc\AssertTagPropertyValueNode($type, $parameter['parameter'], $parameter['property'], $isNegated, $description, $isEquality); } return new Ast\PhpDoc\AssertTagValueNode($type, $parameter['parameter'], $isNegated, $description, $isEquality); } /** * @return array{parameter: string}|array{parameter: string, property: string}|array{parameter: string, method: string} */ private function parseAssertParameter(TokenIterator $tokens): array { if ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) { $parameter = '$this'; $tokens->next(); } else { $parameter = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_ARROW)) { $tokens->consumeTokenType(Lexer::TOKEN_ARROW); $propertyOrMethod = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); return ['parameter' => $parameter, 'method' => $propertyOrMethod]; } return ['parameter' => $parameter, 'property' => $propertyOrMethod]; } return ['parameter' => $parameter]; } private function parseSelfOutTagValue(TokenIterator $tokens): Ast\PhpDoc\SelfOutTagValueNode { $type = $this->typeParser->parse($tokens); $description = $this->parseOptionalDescription($tokens, true); return new Ast\PhpDoc\SelfOutTagValueNode($type, $description); } private function parseParamOutTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamOutTagValueNode { $type = $this->typeParser->parse($tokens); $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens, false); return new Ast\PhpDoc\ParamOutTagValueNode($type, $parameterName, $description); } private function parseOptionalVariableName(TokenIterator $tokens): string { if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { $parameterName = $tokens->currentTokenValue(); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) { $parameterName = '$this'; $tokens->next(); } else { $parameterName = ''; } return $parameterName; } private function parseRequiredVariableName(TokenIterator $tokens): string { $parameterName = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); return $parameterName; } /** * @param bool $limitStartToken true should be used when the description immediately follows a parsed type */ private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken): string { if ($limitStartToken) { foreach (self::DISALLOWED_DESCRIPTION_START_TOKENS as $disallowedStartToken) { if (!$tokens->isCurrentTokenType($disallowedStartToken)) { continue; } $tokens->consumeTokenType(Lexer::TOKEN_OTHER); // will throw exception } if ( !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END) && !$tokens->isPrecededByHorizontalWhitespace() ) { $tokens->consumeTokenType(Lexer::TOKEN_HORIZONTAL_WS); // will throw exception } } return $this->parseText($tokens)->text; } } phpdoc-parser/src/Parser/ConstExprParser.php 0000644 00000017204 15213400535 0015153 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Parser; use PHPStan\PhpDocParser\Ast; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use function str_replace; use function strtolower; class ConstExprParser { private ParserConfig $config; private bool $parseDoctrineStrings; public function __construct( ParserConfig $config ) { $this->config = $config; $this->parseDoctrineStrings = false; } /** * @internal */ public function toDoctrine(): self { $self = new self($this->config); $self->parseDoctrineStrings = true; return $self; } public function parse(TokenIterator $tokens): Ast\ConstExpr\ConstExprNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) { $value = $tokens->currentTokenValue(); $tokens->next(); return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprFloatNode(str_replace('_', '', $value)), $startLine, $startIndex, ); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { $value = $tokens->currentTokenValue(); $tokens->next(); return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $value)), $startLine, $startIndex, ); } if ($this->parseDoctrineStrings && $tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) { $value = $tokens->currentTokenValue(); $tokens->next(); return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($value)), $startLine, $startIndex, ); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING, Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { if ($this->parseDoctrineStrings) { if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { throw new ParserException( $tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_DOUBLE_QUOTED_STRING, null, $tokens->currentTokenLine(), ); } $value = $tokens->currentTokenValue(); $tokens->next(); return $this->enrichWithAttributes( $tokens, $this->parseDoctrineString($value, $tokens), $startLine, $startIndex, ); } $value = StringUnescaper::unescapeString($tokens->currentTokenValue()); $type = $tokens->currentTokenType(); $tokens->next(); return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprStringNode( $value, $type === Lexer::TOKEN_SINGLE_QUOTED_STRING ? Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED : Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED, ), $startLine, $startIndex, ); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { $identifier = $tokens->currentTokenValue(); $tokens->next(); switch (strtolower($identifier)) { case 'true': return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprTrueNode(), $startLine, $startIndex, ); case 'false': return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprFalseNode(), $startLine, $startIndex, ); case 'null': return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprNullNode(), $startLine, $startIndex, ); case 'array': $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES, $startIndex); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) { $classConstantName = ''; $lastType = null; while (true) { if ($lastType !== Lexer::TOKEN_IDENTIFIER && $tokens->currentTokenType() === Lexer::TOKEN_IDENTIFIER) { $classConstantName .= $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $lastType = Lexer::TOKEN_IDENTIFIER; continue; } if ($lastType !== Lexer::TOKEN_WILDCARD && $tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) { $classConstantName .= '*'; $lastType = Lexer::TOKEN_WILDCARD; if ($tokens->getSkippedHorizontalWhiteSpaceIfAny() !== '') { break; } continue; } if ($lastType === null) { // trigger parse error if nothing valid was consumed $tokens->consumeTokenType(Lexer::TOKEN_WILDCARD); } break; } return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName), $startLine, $startIndex, ); } return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstFetchNode('', $identifier), $startLine, $startIndex, ); } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_SQUARE_BRACKET, $startIndex); } throw new ParserException( $tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER, null, $tokens->currentTokenLine(), ); } private function parseArray(TokenIterator $tokens, int $endToken, int $startIndex): Ast\ConstExpr\ConstExprArrayNode { $items = []; $startLine = $tokens->currentTokenLine(); if (!$tokens->tryConsumeTokenType($endToken)) { do { $items[] = $this->parseArrayItem($tokens); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken)); $tokens->consumeTokenType($endToken); } return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprArrayNode($items), $startLine, $startIndex, ); } /** * This method is supposed to be called with TokenIterator after reading TOKEN_DOUBLE_QUOTED_STRING and shifting * to the next token. */ public function parseDoctrineString(string $text, TokenIterator $tokens): Ast\ConstExpr\DoctrineConstExprStringNode { // Because of how Lexer works, a valid Doctrine string // can consist of a sequence of TOKEN_DOUBLE_QUOTED_STRING and TOKEN_DOCTRINE_ANNOTATION_STRING while ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING, Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) { $text .= $tokens->currentTokenValue(); $tokens->next(); } return new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($text)); } private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprArrayItemNode { $startLine = $tokens->currentTokenLine(); $startIndex = $tokens->currentTokenIndex(); $expr = $this->parse($tokens); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_ARROW)) { $key = $expr; $value = $this->parse($tokens); } else { $key = null; $value = $expr; } return $this->enrichWithAttributes( $tokens, new Ast\ConstExpr\ConstExprArrayItemNode($key, $value), $startLine, $startIndex, ); } /** * @template T of Ast\ConstExpr\ConstExprNode * @param T $node * @return T */ private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode { if ($this->config->useLinesAttributes) { $node->setAttribute(Ast\Attribute::START_LINE, $startLine); $node->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine()); } if ($this->config->useIndexAttributes) { $node->setAttribute(Ast\Attribute::START_INDEX, $startIndex); $node->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken()); } return $node; } } phpdoc-parser/src/Parser/StringUnescaper.php 0000644 00000004530 15213400535 0015163 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Parser; use PHPStan\ShouldNotHappenException; use function chr; use function hexdec; use function octdec; use function preg_replace_callback; use function str_replace; use function substr; class StringUnescaper { private const REPLACEMENTS = [ '\\' => '\\', 'n' => "\n", 'r' => "\r", 't' => "\t", 'f' => "\f", 'v' => "\v", 'e' => "\x1B", ]; public static function unescapeString(string $string): string { $quote = $string[0]; if ($quote === '\'') { return str_replace( ['\\\\', '\\\''], ['\\', '\''], substr($string, 1, -1), ); } return self::parseEscapeSequences(substr($string, 1, -1), '"'); } /** * Implementation based on https://github.com/nikic/PHP-Parser/blob/b0edd4c41111042d43bb45c6c657b2e0db367d9e/lib/PhpParser/Node/Scalar/String_.php#L90-L130 */ private static function parseEscapeSequences(string $str, string $quote): string { $str = str_replace('\\' . $quote, $quote, $str); return preg_replace_callback( '~\\\\([\\\\nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u\{([0-9a-fA-F]+)\})~', static function ($matches) { $str = $matches[1]; if (isset(self::REPLACEMENTS[$str])) { return self::REPLACEMENTS[$str]; } if ($str[0] === 'x' || $str[0] === 'X') { return chr((int) hexdec(substr($str, 1))); } if ($str[0] === 'u') { if (!isset($matches[2])) { throw new ShouldNotHappenException(); } return self::codePointToUtf8((int) hexdec($matches[2])); } return chr((int) octdec($str)); }, $str, ); } /** * Implementation based on https://github.com/nikic/PHP-Parser/blob/b0edd4c41111042d43bb45c6c657b2e0db367d9e/lib/PhpParser/Node/Scalar/String_.php#L132-L154 */ private static function codePointToUtf8(int $num): string { if ($num <= 0x7F) { return chr($num); } if ($num <= 0x7FF) { return chr(($num >> 6) + 0xC0) . chr(($num & 0x3F) + 0x80); } if ($num <= 0xFFFF) { return chr(($num >> 12) + 0xE0) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); } if ($num <= 0x1FFFFF) { return chr(($num >> 18) + 0xF0) . chr((($num >> 12) & 0x3F) + 0x80) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); } // Invalid UTF-8 codepoint escape sequence: Codepoint too large return "\xef\xbf\xbd"; } } phpdoc-parser/src/Parser/ParserException.php 0000644 00000004150 15213400535 0015160 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Parser; use Exception; use PHPStan\PhpDocParser\Lexer\Lexer; use function assert; use function json_encode; use function sprintf; use const JSON_INVALID_UTF8_SUBSTITUTE; use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; class ParserException extends Exception { private string $currentTokenValue; private int $currentTokenType; private int $currentOffset; private int $expectedTokenType; private ?string $expectedTokenValue; private ?int $currentTokenLine; public function __construct( string $currentTokenValue, int $currentTokenType, int $currentOffset, int $expectedTokenType, ?string $expectedTokenValue, ?int $currentTokenLine ) { $this->currentTokenValue = $currentTokenValue; $this->currentTokenType = $currentTokenType; $this->currentOffset = $currentOffset; $this->expectedTokenType = $expectedTokenType; $this->expectedTokenValue = $expectedTokenValue; $this->currentTokenLine = $currentTokenLine; parent::__construct(sprintf( 'Unexpected token %s, expected %s%s at offset %d%s', $this->formatValue($currentTokenValue), Lexer::TOKEN_LABELS[$expectedTokenType], $expectedTokenValue !== null ? sprintf(' (%s)', $this->formatValue($expectedTokenValue)) : '', $currentOffset, $currentTokenLine === null ? '' : sprintf(' on line %d', $currentTokenLine), )); } public function getCurrentTokenValue(): string { return $this->currentTokenValue; } public function getCurrentTokenType(): int { return $this->currentTokenType; } public function getCurrentOffset(): int { return $this->currentOffset; } public function getExpectedTokenType(): int { return $this->expectedTokenType; } public function getExpectedTokenValue(): ?string { return $this->expectedTokenValue; } public function getCurrentTokenLine(): ?int { return $this->currentTokenLine; } private function formatValue(string $value): string { $json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE); assert($json !== false); return $json; } } phpdoc-parser/src/Parser/TokenIterator.php 0000644 00000021475 15213400535 0014650 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Parser; use LogicException; use PHPStan\PhpDocParser\Ast\Comment; use PHPStan\PhpDocParser\Lexer\Lexer; use function array_pop; use function assert; use function count; use function in_array; use function strlen; use function substr; class TokenIterator { /** @var list<array{string, int, int}> */ private array $tokens; private int $index; /** @var list<Comment> */ private array $comments = []; /** @var list<array{int, list<Comment>}> */ private array $savePoints = []; /** @var list<int> */ private array $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS]; private ?string $newline = null; /** * @param list<array{string, int, int}> $tokens */ public function __construct(array $tokens, int $index = 0) { $this->tokens = $tokens; $this->index = $index; $this->skipIrrelevantTokens(); } /** * @return list<array{string, int, int}> */ public function getTokens(): array { return $this->tokens; } public function getContentBetween(int $startPos, int $endPos): string { if ($startPos < 0 || $endPos > count($this->tokens)) { throw new LogicException(); } $content = ''; for ($i = $startPos; $i < $endPos; $i++) { $content .= $this->tokens[$i][Lexer::VALUE_OFFSET]; } return $content; } public function getTokenCount(): int { return count($this->tokens); } public function currentTokenValue(): string { return $this->tokens[$this->index][Lexer::VALUE_OFFSET]; } public function currentTokenType(): int { return $this->tokens[$this->index][Lexer::TYPE_OFFSET]; } public function currentTokenOffset(): int { $offset = 0; for ($i = 0; $i < $this->index; $i++) { $offset += strlen($this->tokens[$i][Lexer::VALUE_OFFSET]); } return $offset; } public function currentTokenLine(): int { return $this->tokens[$this->index][Lexer::LINE_OFFSET]; } public function currentTokenIndex(): int { return $this->index; } public function endIndexOfLastRelevantToken(): int { $endIndex = $this->currentTokenIndex(); $endIndex--; while (in_array($this->tokens[$endIndex][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, true)) { if (!isset($this->tokens[$endIndex - 1])) { break; } $endIndex--; } return $endIndex; } public function isCurrentTokenValue(string $tokenValue): bool { return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue; } public function isCurrentTokenType(int ...$tokenType): bool { return in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, true); } public function isPrecededByHorizontalWhitespace(): bool { return ($this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] ?? -1) === Lexer::TOKEN_HORIZONTAL_WS; } /** * @throws ParserException */ public function consumeTokenType(int $tokenType): void { if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) { $this->throwError($tokenType); } if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) { if ($this->newline === null) { $this->detectNewline(); } } $this->next(); } /** * @throws ParserException */ public function consumeTokenValue(int $tokenType, string $tokenValue): void { if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType || $this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) { $this->throwError($tokenType, $tokenValue); } $this->next(); } /** @phpstan-impure */ public function tryConsumeTokenValue(string $tokenValue): bool { if ($this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) { return false; } $this->next(); return true; } /** * @return list<Comment> */ public function flushComments(): array { $res = $this->comments; $this->comments = []; return $res; } /** @phpstan-impure */ public function tryConsumeTokenType(int $tokenType): bool { if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) { return false; } if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) { if ($this->newline === null) { $this->detectNewline(); } } $this->next(); return true; } /** * @deprecated Use skipNewLineTokensAndConsumeComments instead (when parsing a type) */ public function skipNewLineTokens(): void { if (!$this->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { return; } do { $foundNewLine = $this->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } while ($foundNewLine === true); } public function skipNewLineTokensAndConsumeComments(): void { if ($this->currentTokenType() === Lexer::TOKEN_COMMENT) { $this->comments[] = new Comment($this->currentTokenValue(), $this->currentTokenLine(), $this->currentTokenIndex()); $this->next(); } if (!$this->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { return; } do { $foundNewLine = $this->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); if ($this->currentTokenType() !== Lexer::TOKEN_COMMENT) { continue; } $this->comments[] = new Comment($this->currentTokenValue(), $this->currentTokenLine(), $this->currentTokenIndex()); $this->next(); } while ($foundNewLine === true); } private function detectNewline(): void { $value = $this->currentTokenValue(); if (substr($value, 0, 2) === "\r\n") { $this->newline = "\r\n"; } elseif (substr($value, 0, 1) === "\n") { $this->newline = "\n"; } } public function getSkippedHorizontalWhiteSpaceIfAny(): string { if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { return $this->tokens[$this->index - 1][Lexer::VALUE_OFFSET]; } return ''; } /** @phpstan-impure */ public function joinUntil(int ...$tokenType): string { $s = ''; while (!in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, true)) { $s .= $this->tokens[$this->index++][Lexer::VALUE_OFFSET]; } return $s; } public function next(): void { $this->index++; $this->skipIrrelevantTokens(); } private function skipIrrelevantTokens(): void { if (!isset($this->tokens[$this->index])) { return; } while (in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, true)) { if (!isset($this->tokens[$this->index + 1])) { break; } $this->index++; } } public function addEndOfLineToSkippedTokens(): void { $this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL]; } public function removeEndOfLineFromSkippedTokens(): void { $this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS]; } /** @phpstan-impure */ public function forwardToTheEnd(): void { $lastToken = count($this->tokens) - 1; $this->index = $lastToken; } public function pushSavePoint(): void { $this->savePoints[] = [$this->index, $this->comments]; } public function dropSavePoint(): void { array_pop($this->savePoints); } public function rollback(): void { $savepoint = array_pop($this->savePoints); assert($savepoint !== null); [$this->index, $this->comments] = $savepoint; } /** * @throws ParserException */ private function throwError(int $expectedTokenType, ?string $expectedTokenValue = null): void { throw new ParserException( $this->currentTokenValue(), $this->currentTokenType(), $this->currentTokenOffset(), $expectedTokenType, $expectedTokenValue, $this->currentTokenLine(), ); } /** * Check whether the position is directly preceded by a certain token type. * * During this check TOKEN_HORIZONTAL_WS and TOKEN_PHPDOC_EOL are skipped */ public function hasTokenImmediatelyBefore(int $pos, int $expectedTokenType): bool { $tokens = $this->tokens; $pos--; for (; $pos >= 0; $pos--) { $token = $tokens[$pos]; $type = $token[Lexer::TYPE_OFFSET]; if ($type === $expectedTokenType) { return true; } if (!in_array($type, [ Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL, ], true)) { break; } } return false; } /** * Check whether the position is directly followed by a certain token type. * * During this check TOKEN_HORIZONTAL_WS and TOKEN_PHPDOC_EOL are skipped */ public function hasTokenImmediatelyAfter(int $pos, int $expectedTokenType): bool { $tokens = $this->tokens; $pos++; for ($c = count($tokens); $pos < $c; $pos++) { $token = $tokens[$pos]; $type = $token[Lexer::TYPE_OFFSET]; if ($type === $expectedTokenType) { return true; } if (!in_array($type, [ Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL, ], true)) { break; } } return false; } public function getDetectedNewline(): ?string { return $this->newline; } /** * Whether the given position is immediately surrounded by parenthesis. */ public function hasParentheses(int $startPos, int $endPos): bool { return $this->hasTokenImmediatelyBefore($startPos, Lexer::TOKEN_OPEN_PARENTHESES) && $this->hasTokenImmediatelyAfter($endPos, Lexer::TOKEN_CLOSE_PARENTHESES); } } phpdoc-parser/src/Lexer/Lexer.php 0000644 00000015224 15213400535 0012753 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Lexer; use PHPStan\PhpDocParser\ParserConfig; use function implode; use function preg_match_all; use const PREG_SET_ORDER; /** * Implementation based on Nette Tokenizer (New BSD License; https://github.com/nette/tokenizer) */ class Lexer { public const TOKEN_REFERENCE = 0; public const TOKEN_UNION = 1; public const TOKEN_INTERSECTION = 2; public const TOKEN_NULLABLE = 3; public const TOKEN_OPEN_PARENTHESES = 4; public const TOKEN_CLOSE_PARENTHESES = 5; public const TOKEN_OPEN_ANGLE_BRACKET = 6; public const TOKEN_CLOSE_ANGLE_BRACKET = 7; public const TOKEN_OPEN_SQUARE_BRACKET = 8; public const TOKEN_CLOSE_SQUARE_BRACKET = 9; public const TOKEN_COMMA = 10; public const TOKEN_VARIADIC = 11; public const TOKEN_DOUBLE_COLON = 12; public const TOKEN_DOUBLE_ARROW = 13; public const TOKEN_EQUAL = 14; public const TOKEN_OPEN_PHPDOC = 15; public const TOKEN_CLOSE_PHPDOC = 16; public const TOKEN_PHPDOC_TAG = 17; public const TOKEN_DOCTRINE_TAG = 18; public const TOKEN_FLOAT = 19; public const TOKEN_INTEGER = 20; public const TOKEN_SINGLE_QUOTED_STRING = 21; public const TOKEN_DOUBLE_QUOTED_STRING = 22; public const TOKEN_DOCTRINE_ANNOTATION_STRING = 23; public const TOKEN_IDENTIFIER = 24; public const TOKEN_THIS_VARIABLE = 25; public const TOKEN_VARIABLE = 26; public const TOKEN_HORIZONTAL_WS = 27; public const TOKEN_PHPDOC_EOL = 28; public const TOKEN_OTHER = 29; public const TOKEN_END = 30; public const TOKEN_COLON = 31; public const TOKEN_WILDCARD = 32; public const TOKEN_OPEN_CURLY_BRACKET = 33; public const TOKEN_CLOSE_CURLY_BRACKET = 34; public const TOKEN_NEGATED = 35; public const TOKEN_ARROW = 36; public const TOKEN_COMMENT = 37; public const TOKEN_LABELS = [ self::TOKEN_REFERENCE => '\'&\'', self::TOKEN_UNION => '\'|\'', self::TOKEN_INTERSECTION => '\'&\'', self::TOKEN_NULLABLE => '\'?\'', self::TOKEN_NEGATED => '\'!\'', self::TOKEN_OPEN_PARENTHESES => '\'(\'', self::TOKEN_CLOSE_PARENTHESES => '\')\'', self::TOKEN_OPEN_ANGLE_BRACKET => '\'<\'', self::TOKEN_CLOSE_ANGLE_BRACKET => '\'>\'', self::TOKEN_OPEN_SQUARE_BRACKET => '\'[\'', self::TOKEN_CLOSE_SQUARE_BRACKET => '\']\'', self::TOKEN_OPEN_CURLY_BRACKET => '\'{\'', self::TOKEN_CLOSE_CURLY_BRACKET => '\'}\'', self::TOKEN_COMMA => '\',\'', self::TOKEN_COMMENT => '\'//\'', self::TOKEN_COLON => '\':\'', self::TOKEN_VARIADIC => '\'...\'', self::TOKEN_DOUBLE_COLON => '\'::\'', self::TOKEN_DOUBLE_ARROW => '\'=>\'', self::TOKEN_ARROW => '\'->\'', self::TOKEN_EQUAL => '\'=\'', self::TOKEN_OPEN_PHPDOC => '\'/**\'', self::TOKEN_CLOSE_PHPDOC => '\'*/\'', self::TOKEN_PHPDOC_TAG => 'TOKEN_PHPDOC_TAG', self::TOKEN_DOCTRINE_TAG => 'TOKEN_DOCTRINE_TAG', self::TOKEN_PHPDOC_EOL => 'TOKEN_PHPDOC_EOL', self::TOKEN_FLOAT => 'TOKEN_FLOAT', self::TOKEN_INTEGER => 'TOKEN_INTEGER', self::TOKEN_SINGLE_QUOTED_STRING => 'TOKEN_SINGLE_QUOTED_STRING', self::TOKEN_DOUBLE_QUOTED_STRING => 'TOKEN_DOUBLE_QUOTED_STRING', self::TOKEN_DOCTRINE_ANNOTATION_STRING => 'TOKEN_DOCTRINE_ANNOTATION_STRING', self::TOKEN_IDENTIFIER => 'type', self::TOKEN_THIS_VARIABLE => '\'$this\'', self::TOKEN_VARIABLE => 'variable', self::TOKEN_HORIZONTAL_WS => 'TOKEN_HORIZONTAL_WS', self::TOKEN_OTHER => 'TOKEN_OTHER', self::TOKEN_END => 'TOKEN_END', self::TOKEN_WILDCARD => '*', ]; public const VALUE_OFFSET = 0; public const TYPE_OFFSET = 1; public const LINE_OFFSET = 2; private ParserConfig $config; // @phpstan-ignore property.onlyWritten private ?string $regexp = null; public function __construct(ParserConfig $config) { $this->config = $config; } /** * @return list<array{string, int, int}> */ public function tokenize(string $s): array { if ($this->regexp === null) { $this->regexp = $this->generateRegexp(); } preg_match_all($this->regexp, $s, $matches, PREG_SET_ORDER); $tokens = []; $line = 1; foreach ($matches as $match) { $type = (int) $match['MARK']; $tokens[] = [$match[0], $type, $line]; if ($type !== self::TOKEN_PHPDOC_EOL) { continue; } $line++; } $tokens[] = ['', self::TOKEN_END, $line]; return $tokens; } private function generateRegexp(): string { $patterns = [ self::TOKEN_HORIZONTAL_WS => '[\\x09\\x20]++', self::TOKEN_IDENTIFIER => '(?:[\\\\]?+[a-z_\\x80-\\xFF][0-9a-z_\\x80-\\xFF-]*+)++', self::TOKEN_THIS_VARIABLE => '\\$this(?![0-9a-z_\\x80-\\xFF])', self::TOKEN_VARIABLE => '\\$[a-z_\\x80-\\xFF][0-9a-z_\\x80-\\xFF]*+', // '&' followed by TOKEN_VARIADIC, TOKEN_VARIABLE, TOKEN_EQUAL, TOKEN_EQUAL or TOKEN_CLOSE_PARENTHESES self::TOKEN_REFERENCE => '&(?=\\s*+(?:[.,=)]|(?:\\$(?!this(?![0-9a-z_\\x80-\\xFF])))))', self::TOKEN_UNION => '\\|', self::TOKEN_INTERSECTION => '&', self::TOKEN_NULLABLE => '\\?', self::TOKEN_NEGATED => '!', self::TOKEN_OPEN_PARENTHESES => '\\(', self::TOKEN_CLOSE_PARENTHESES => '\\)', self::TOKEN_OPEN_ANGLE_BRACKET => '<', self::TOKEN_CLOSE_ANGLE_BRACKET => '>', self::TOKEN_OPEN_SQUARE_BRACKET => '\\[', self::TOKEN_CLOSE_SQUARE_BRACKET => '\\]', self::TOKEN_OPEN_CURLY_BRACKET => '\\{', self::TOKEN_CLOSE_CURLY_BRACKET => '\\}', self::TOKEN_COMMA => ',', self::TOKEN_COMMENT => '\/\/[^\\r\\n]*(?=\n|\r|\*/)', self::TOKEN_VARIADIC => '\\.\\.\\.', self::TOKEN_DOUBLE_COLON => '::', self::TOKEN_DOUBLE_ARROW => '=>', self::TOKEN_ARROW => '->', self::TOKEN_EQUAL => '=', self::TOKEN_COLON => ':', self::TOKEN_OPEN_PHPDOC => '/\\*\\*(?=\\s)\\x20?+', self::TOKEN_CLOSE_PHPDOC => '\\*/', self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+', self::TOKEN_DOCTRINE_TAG => '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*', self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/)\\x20?+)?', self::TOKEN_FLOAT => '[+\-]?(?:(?:[0-9]++(_[0-9]++)*\\.[0-9]*+(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]*+(_[0-9]++)*\\.[0-9]++(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]++(_[0-9]++)*e[+\-]?[0-9]++(_[0-9]++)*))', self::TOKEN_INTEGER => '[+\-]?(?:(?:0b[0-1]++(_[0-1]++)*)|(?:0o[0-7]++(_[0-7]++)*)|(?:0x[0-9a-f]++(_[0-9a-f]++)*)|(?:[0-9]++(_[0-9]++)*))', self::TOKEN_SINGLE_QUOTED_STRING => '\'(?:\\\\[^\\r\\n]|[^\'\\r\\n\\\\])*+\'', self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"', self::TOKEN_DOCTRINE_ANNOTATION_STRING => '"(?:""|[^"])*+"', self::TOKEN_WILDCARD => '\\*', // anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++', ]; foreach ($patterns as $type => &$pattern) { $pattern = '(?:' . $pattern . ')(*MARK:' . $type . ')'; } return '~' . implode('|', $patterns) . '~Asi'; } } phpdoc-parser/src/Ast/NodeVisitor.php 0000644 00000004720 15213400535 0013610 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; /** * Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1 * * Copyright (c) 2011, Nikita Popov * All rights reserved. */ interface NodeVisitor { /** * Called once before traversal. * * Return value semantics: * * null: $nodes stays as-is * * otherwise: $nodes is set to the return value * * @param Node[] $nodes Array of nodes * * @return Node[]|null Array of nodes */ public function beforeTraverse(array $nodes): ?array; /** * Called when entering a node. * * Return value semantics: * * null * => $node stays as-is * * array (of Nodes) * => The return value is merged into the parent array (at the position of the $node) * * NodeTraverser::REMOVE_NODE * => $node is removed from the parent array * * NodeTraverser::DONT_TRAVERSE_CHILDREN * => Children of $node are not traversed. $node stays as-is * * NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN * => Further visitors for the current node are skipped, and its children are not * traversed. $node stays as-is. * * NodeTraverser::STOP_TRAVERSAL * => Traversal is aborted. $node stays as-is * * otherwise * => $node is set to the return value * * @param Node $node Node * * @return Node|Node[]|NodeTraverser::*|null Replacement node (or special return value) */ public function enterNode(Node $node); /** * Called when leaving a node. * * Return value semantics: * * null * => $node stays as-is * * NodeTraverser::REMOVE_NODE * => $node is removed from the parent array * * NodeTraverser::STOP_TRAVERSAL * => Traversal is aborted. $node stays as-is * * array (of Nodes) * => The return value is merged into the parent array (at the position of the $node) * * otherwise * => $node is set to the return value * * @param Node $node Node * * @return Node|Node[]|NodeTraverser::REMOVE_NODE|NodeTraverser::STOP_TRAVERSAL|null Replacement node (or special return value) */ public function leaveNode(Node $node); /** * Called once after traversal. * * Return value semantics: * * null: $nodes stays as-is * * otherwise: $nodes is set to the return value * * @param Node[] $nodes Array of nodes * * @return Node[]|null Array of nodes */ public function afterTraverse(array $nodes): ?array; } phpdoc-parser/src/Ast/Node.php 0000644 00000000534 15213400535 0012227 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; interface Node { public function __toString(): string; /** * @param mixed $value */ public function setAttribute(string $key, $value): void; public function hasAttribute(string $key): bool; /** * @return mixed */ public function getAttribute(string $key); } phpdoc-parser/src/Ast/NodeVisitor/CloningVisitor.php 0000644 00000000671 15213400535 0016562 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\NodeVisitor; use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor; use PHPStan\PhpDocParser\Ast\Attribute; use PHPStan\PhpDocParser\Ast\Node; final class CloningVisitor extends AbstractNodeVisitor { public function enterNode(Node $originalNode): Node { $node = clone $originalNode; $node->setAttribute(Attribute::ORIGINAL_NODE, $originalNode); return $node; } } phpdoc-parser/src/Ast/AbstractNodeVisitor.php 0000644 00000001237 15213400535 0015274 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; /** * Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1 * * Copyright (c) 2011, Nikita Popov * All rights reserved. */ abstract class AbstractNodeVisitor implements NodeVisitor // phpcs:ignore SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix { public function beforeTraverse(array $nodes): ?array { return null; } public function enterNode(Node $node) { return null; } public function leaveNode(Node $node) { return null; } public function afterTraverse(array $nodes): ?array { return null; } } phpdoc-parser/src/Ast/Attribute.php 0000644 00000000525 15213400535 0013305 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; final class Attribute { public const START_LINE = 'startLine'; public const END_LINE = 'endLine'; public const START_INDEX = 'startIndex'; public const END_INDEX = 'endIndex'; public const ORIGINAL_NODE = 'originalNode'; public const COMMENTS = 'comments'; } phpdoc-parser/src/Ast/NodeTraverser.php 0000644 00000020322 15213400535 0014122 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; use LogicException; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function array_keys; use function array_pop; use function array_splice; use function count; use function get_class; use function get_object_vars; use function gettype; use function is_array; use function sprintf; /** * Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1 * * Copyright (c) 2011, Nikita Popov * All rights reserved. */ final class NodeTraverser { /** * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes * of the current node will not be traversed for any visitors. * * For subsequent visitors enterNode() will still be called on the current * node and leaveNode() will also be invoked for the current node. */ public const DONT_TRAVERSE_CHILDREN = 1; /** * If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns * STOP_TRAVERSAL, traversal is aborted. * * The afterTraverse() method will still be invoked. */ public const STOP_TRAVERSAL = 2; /** * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs * in an array, it will be removed from the array. * * For subsequent visitors leaveNode() will still be invoked for the * removed node. */ public const REMOVE_NODE = 3; /** * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes * of the current node will not be traversed for any visitors. * * For subsequent visitors enterNode() will not be called as well. * leaveNode() will be invoked for visitors that has enterNode() method invoked. */ public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4; /** @var list<NodeVisitor> Visitors */ private array $visitors = []; /** @var bool Whether traversal should be stopped */ private bool $stopTraversal; /** * @param list<NodeVisitor> $visitors */ public function __construct(array $visitors) { $this->visitors = $visitors; } /** * Traverses an array of nodes using the registered visitors. * * @param Node[] $nodes Array of nodes * * @return Node[] Traversed array of nodes */ public function traverse(array $nodes): array { $this->stopTraversal = false; foreach ($this->visitors as $visitor) { $return = $visitor->beforeTraverse($nodes); if ($return === null) { continue; } $nodes = $return; } $nodes = $this->traverseArray($nodes); foreach ($this->visitors as $visitor) { $return = $visitor->afterTraverse($nodes); if ($return === null) { continue; } $nodes = $return; } return $nodes; } /** * Recursively traverse a node. * * @param Node $node Node to traverse. * * @return Node Result of traversal (may be original node or new one) */ private function traverseNode(Node $node): Node { $subNodeNames = array_keys(get_object_vars($node)); foreach ($subNodeNames as $name) { $subNode =& $node->$name; if (is_array($subNode)) { $subNode = $this->traverseArray($subNode); if ($this->stopTraversal) { break; } } elseif ($subNode instanceof Node) { $traverseChildren = true; $breakVisitorIndex = null; foreach ($this->visitors as $visitorIndex => $visitor) { $return = $visitor->enterNode($subNode); if ($return === null) { continue; } if ($return instanceof Node) { $this->ensureReplacementReasonable($subNode, $return); $subNode = $return; } elseif ($return === self::DONT_TRAVERSE_CHILDREN) { $traverseChildren = false; } elseif ($return === self::DONT_TRAVERSE_CURRENT_AND_CHILDREN) { $traverseChildren = false; $breakVisitorIndex = $visitorIndex; break; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = true; break 2; } else { throw new LogicException( 'enterNode() returned invalid value of type ' . gettype($return), ); } } if ($traverseChildren) { $subNode = $this->traverseNode($subNode); if ($this->stopTraversal) { break; } } foreach ($this->visitors as $visitorIndex => $visitor) { $return = $visitor->leaveNode($subNode); if ($return !== null) { if ($return instanceof Node) { $this->ensureReplacementReasonable($subNode, $return); $subNode = $return; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = true; break 2; } elseif (is_array($return)) { throw new LogicException( 'leaveNode() may only return an array ' . 'if the parent structure is an array', ); } else { throw new LogicException( 'leaveNode() returned invalid value of type ' . gettype($return), ); } } if ($breakVisitorIndex === $visitorIndex) { break; } } } } return $node; } /** * Recursively traverse array (usually of nodes). * * @param mixed[] $nodes Array to traverse * * @return mixed[] Result of traversal (may be original array or changed one) */ private function traverseArray(array $nodes): array { $doNodes = []; foreach ($nodes as $i => &$node) { if ($node instanceof Node) { $traverseChildren = true; $breakVisitorIndex = null; foreach ($this->visitors as $visitorIndex => $visitor) { $return = $visitor->enterNode($node); if ($return === null) { continue; } if ($return instanceof Node) { $this->ensureReplacementReasonable($node, $return); $node = $return; } elseif (is_array($return)) { $doNodes[] = [$i, $return]; continue 2; } elseif ($return === self::REMOVE_NODE) { $doNodes[] = [$i, []]; continue 2; } elseif ($return === self::DONT_TRAVERSE_CHILDREN) { $traverseChildren = false; } elseif ($return === self::DONT_TRAVERSE_CURRENT_AND_CHILDREN) { $traverseChildren = false; $breakVisitorIndex = $visitorIndex; break; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = true; break 2; } else { throw new LogicException( 'enterNode() returned invalid value of type ' . gettype($return), ); } } if ($traverseChildren) { $node = $this->traverseNode($node); if ($this->stopTraversal) { break; } } foreach ($this->visitors as $visitorIndex => $visitor) { $return = $visitor->leaveNode($node); if ($return !== null) { if ($return instanceof Node) { $this->ensureReplacementReasonable($node, $return); $node = $return; } elseif (is_array($return)) { $doNodes[] = [$i, $return]; break; } elseif ($return === self::REMOVE_NODE) { $doNodes[] = [$i, []]; break; } elseif ($return === self::STOP_TRAVERSAL) { $this->stopTraversal = true; break 2; } else { throw new LogicException( 'leaveNode() returned invalid value of type ' . gettype($return), ); } } if ($breakVisitorIndex === $visitorIndex) { break; } } } elseif (is_array($node)) { throw new LogicException('Invalid node structure: Contains nested arrays'); } } if (count($doNodes) > 0) { while ([$i, $replace] = array_pop($doNodes)) { array_splice($nodes, $i, 1, $replace); } } return $nodes; } private function ensureReplacementReasonable(Node $old, Node $new): void { if ($old instanceof TypeNode && !$new instanceof TypeNode) { throw new LogicException(sprintf('Trying to replace TypeNode with %s', get_class($new))); } if ($old instanceof ConstExprNode && !$new instanceof ConstExprNode) { throw new LogicException(sprintf('Trying to replace ConstExprNode with %s', get_class($new))); } if ($old instanceof PhpDocChildNode && !$new instanceof PhpDocChildNode) { throw new LogicException(sprintf('Trying to replace PhpDocChildNode with %s', get_class($new))); } if ($old instanceof PhpDocTagValueNode && !$new instanceof PhpDocTagValueNode) { throw new LogicException(sprintf('Trying to replace PhpDocTagValueNode with %s', get_class($new))); } } } phpdoc-parser/src/Ast/PhpDoc/VarTagValueNode.php 0000644 00000002166 15213400535 0015511 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class VarTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $variableName; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $variableName, string $description) { $this->type = $type; $this->variableName = $variableName; $this->description = $description; } public function __toString(): string { return trim("$this->type " . trim("{$this->variableName} {$this->description}")); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['variableName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/RequireExtendsTagValueNode.php 0000644 00000001706 15213400535 0017727 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class RequireExtendsTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ParamTagValueNode.php 0000644 00000002640 15213400535 0016016 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class ParamTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public bool $isReference; public bool $isVariadic; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, bool $isVariadic, string $parameterName, string $description, bool $isReference) { $this->type = $type; $this->isReference = $isReference; $this->isVariadic = $isVariadic; $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { $reference = $this->isReference ? '&' : ''; $variadic = $this->isVariadic ? '...' : ''; return trim("{$this->type} {$reference}{$variadic}{$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['isVariadic'], $properties['parameterName'], $properties['description'], $properties['isReference']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PhpDocChildNode.php 0000644 00000000236 15213400535 0015445 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\Node; interface PhpDocChildNode extends Node { } phpdoc-parser/src/Ast/PhpDoc/ImplementsTagValueNode.php 0000644 00000001727 15213400535 0017100 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use function trim; class ImplementsTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public GenericTypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(GenericTypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PropertyTagValueNode.php 0000644 00000002117 15213400535 0016601 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class PropertyTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public string $propertyName; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $propertyName, string $description) { $this->type = $type; $this->propertyName = $propertyName; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->propertyName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['propertyName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/TypelessParamTagValueNode.php 0000644 00000002431 15213400535 0017545 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function trim; class TypelessParamTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public bool $isReference; public bool $isVariadic; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(bool $isVariadic, string $parameterName, string $description, bool $isReference) { $this->isReference = $isReference; $this->isVariadic = $isVariadic; $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { $reference = $this->isReference ? '&' : ''; $variadic = $this->isVariadic ? '...' : ''; return trim("{$reference}{$variadic}{$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['isVariadic'], $properties['parameterName'], $properties['description'], $properties['isReference']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php 0000644 00000002135 15213400535 0020202 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class ParamClosureThisTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $parameterName, string $description) { $this->type = $type; $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['parameterName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/AssertTagPropertyValueNode.php 0000644 00000002777 15213400535 0017777 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class AssertTagPropertyValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public string $parameter; public string $property; public bool $isNegated; public bool $isEquality; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $parameter, string $property, bool $isNegated, string $description, bool $isEquality) { $this->type = $type; $this->parameter = $parameter; $this->property = $property; $this->isNegated = $isNegated; $this->isEquality = $isEquality; $this->description = $description; } public function __toString(): string { $isNegated = $this->isNegated ? '!' : ''; $isEquality = $this->isEquality ? '=' : ''; return trim("{$isNegated}{$isEquality}{$this->type} {$this->parameter}->{$this->property} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['parameter'], $properties['property'], $properties['isNegated'], $properties['description'], $properties['isEquality']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/RequireImplementsTagValueNode.php 0000644 00000001711 15213400535 0020426 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class RequireImplementsTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/MethodTagValueParameterNode.php 0000644 00000003131 15213400535 0020033 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; class MethodTagValueParameterNode implements Node { use NodeAttributes; public ?TypeNode $type = null; public bool $isReference; public bool $isVariadic; public string $parameterName; public ?ConstExprNode $defaultValue = null; public function __construct(?TypeNode $type, bool $isReference, bool $isVariadic, string $parameterName, ?ConstExprNode $defaultValue) { $this->type = $type; $this->isReference = $isReference; $this->isVariadic = $isVariadic; $this->parameterName = $parameterName; $this->defaultValue = $defaultValue; } public function __toString(): string { $type = $this->type !== null ? "{$this->type} " : ''; $isReference = $this->isReference ? '&' : ''; $isVariadic = $this->isVariadic ? '...' : ''; $default = $this->defaultValue !== null ? " = {$this->defaultValue}" : ''; return "{$type}{$isReference}{$isVariadic}{$this->parameterName}{$default}"; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['isReference'], $properties['isVariadic'], $properties['parameterName'], $properties['defaultValue']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ExtendsTagValueNode.php 0000644 00000001724 15213400535 0016372 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use function trim; class ExtendsTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public GenericTypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(GenericTypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ParamImmediatelyInvokedCallableTagValueNode.php 0000644 00000001735 15213400535 0023146 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function trim; class ParamImmediatelyInvokedCallableTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(string $parameterName, string $description) { $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { return trim("{$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['parameterName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/SelfOutTagValueNode.php 0000644 00000001701 15213400535 0016334 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class SelfOutTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim($this->type . ' ' . $this->description); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/GenericTagValueNode.php 0000644 00000001357 15213400535 0016336 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; class GenericTagValueNode implements PhpDocTagValueNode { use NodeAttributes; /** @var string (may be empty) */ public string $value; public function __construct(string $value) { $this->value = $value; } public function __toString(): string { return $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PhpDocTagNode.php 0000644 00000001766 15213400535 0015146 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode; use function trim; class PhpDocTagNode implements PhpDocChildNode { use NodeAttributes; public string $name; public PhpDocTagValueNode $value; public function __construct(string $name, PhpDocTagValueNode $value) { $this->name = $name; $this->value = $value; } public function __toString(): string { if ($this->value instanceof DoctrineTagValueNode) { return (string) $this->value; } return trim("{$this->name} {$this->value}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['name'], $properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/TypeAliasTagValueNode.php 0000644 00000001572 15213400535 0016654 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class TypeAliasTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public string $alias; public TypeNode $type; public function __construct(string $alias, TypeNode $type) { $this->alias = $alias; $this->type = $type; } public function __toString(): string { return trim("{$this->alias} {$this->type}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['alias'], $properties['type']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ReturnTagValueNode.php 0000644 00000001676 15213400535 0016245 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class ReturnTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/SealedTagValueNode.php 0000644 00000001676 15213400535 0016163 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class SealedTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ParamOutTagValueNode.php 0000644 00000002125 15213400535 0016504 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class ParamOutTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $parameterName, string $description) { $this->type = $type; $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['parameterName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineAnnotation.php 0000644 00000002005 15213400535 0020063 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function implode; class DoctrineAnnotation implements Node { use NodeAttributes; public string $name; /** @var list<DoctrineArgument> */ public array $arguments; /** * @param list<DoctrineArgument> $arguments */ public function __construct(string $name, array $arguments) { $this->name = $name; $this->arguments = $arguments; } public function __toString(): string { $arguments = implode(', ', $this->arguments); return $this->name . '(' . $arguments . ')'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['name'], $properties['arguments']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineArgument.php 0000644 00000002254 15213400535 0017541 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; /** * @phpstan-type ValueType = DoctrineAnnotation|IdentifierTypeNode|DoctrineArray|ConstExprNode */ class DoctrineArgument implements Node { use NodeAttributes; public ?IdentifierTypeNode $key = null; /** @var ValueType */ public $value; /** * @param ValueType $value */ public function __construct(?IdentifierTypeNode $key, $value) { $this->key = $key; $this->value = $value; } public function __toString(): string { if ($this->key === null) { return (string) $this->value; } return $this->key . '=' . $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['key'], $properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineArrayItem.php 0000644 00000002551 15213400535 0017654 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; /** * @phpstan-import-type ValueType from DoctrineArgument * @phpstan-type KeyType = ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode|null */ class DoctrineArrayItem implements Node { use NodeAttributes; /** @var KeyType */ public $key; /** @var ValueType */ public $value; /** * @param KeyType $key * @param ValueType $value */ public function __construct($key, $value) { $this->key = $key; $this->value = $value; } public function __toString(): string { if ($this->key === null) { return (string) $this->value; } return $this->key . '=' . $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['key'], $properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineArray.php 0000644 00000001600 15213400535 0017027 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function implode; class DoctrineArray implements Node { use NodeAttributes; /** @var list<DoctrineArrayItem> */ public array $items; /** * @param list<DoctrineArrayItem> $items */ public function __construct(array $items) { $this->items = $items; } public function __toString(): string { $items = implode(', ', $this->items); return '{' . $items . '}'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['items']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/Doctrine/DoctrineTagValueNode.php 0000644 00000002024 15213400535 0020270 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use function trim; class DoctrineTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public DoctrineAnnotation $annotation; /** @var string (may be empty) */ public string $description; public function __construct( DoctrineAnnotation $annotation, string $description ) { $this->annotation = $annotation; $this->description = $description; } public function __toString(): string { return trim("{$this->annotation} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['annotation'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/TypeAliasImportTagValueNode.php 0000644 00000002306 15213400535 0020043 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use function trim; class TypeAliasImportTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public string $importedAlias; public IdentifierTypeNode $importedFrom; public ?string $importedAs = null; public function __construct(string $importedAlias, IdentifierTypeNode $importedFrom, ?string $importedAs) { $this->importedAlias = $importedAlias; $this->importedFrom = $importedFrom; $this->importedAs = $importedAs; } public function __toString(): string { return trim( "{$this->importedAlias} from {$this->importedFrom}" . ($this->importedAs !== null ? " as {$this->importedAs}" : ''), ); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['importedAlias'], $properties['importedFrom'], $properties['importedAs']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PhpDocTagValueNode.php 0000644 00000000241 15213400535 0016126 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\Node; interface PhpDocTagValueNode extends Node { } phpdoc-parser/src/Ast/PhpDoc/UsesTagValueNode.php 0000644 00000001721 15213400535 0015674 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use function trim; class UsesTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public GenericTypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(GenericTypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/DeprecatedTagValueNode.php 0000644 00000001457 15213400535 0017023 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function trim; class DeprecatedTagValueNode implements PhpDocTagValueNode { use NodeAttributes; /** @var string (may be empty) */ public string $description; public function __construct(string $description) { $this->description = $description; } public function __toString(): string { return trim($this->description); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ThrowsTagValueNode.php 0000644 00000001676 15213400535 0016254 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class ThrowsTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/InvalidTagValueNode.php 0000644 00000003112 15213400535 0016337 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Parser\ParserException; use function sprintf; use function trigger_error; use const E_USER_WARNING; /** * @property ParserException $exception */ class InvalidTagValueNode implements PhpDocTagValueNode { use NodeAttributes; /** @var string (may be empty) */ public string $value; /** @var mixed[] */ private array $exceptionArgs; public function __construct(string $value, ParserException $exception) { $this->value = $value; $this->exceptionArgs = [ $exception->getCurrentTokenValue(), $exception->getCurrentTokenType(), $exception->getCurrentOffset(), $exception->getExpectedTokenType(), $exception->getExpectedTokenValue(), $exception->getCurrentTokenLine(), ]; } public function __get(string $name): ?ParserException { if ($name !== 'exception') { trigger_error(sprintf('Undefined property: %s::$%s', self::class, $name), E_USER_WARNING); return null; } return new ParserException(...$this->exceptionArgs); } public function __toString(): string { return $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $exception = new ParserException(...$properties['exceptionArgs']); $instance = new self($properties['value'], $exception); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/ParamLaterInvokedCallableTagValueNode.php 0000644 00000001727 15213400535 0021753 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function trim; class ParamLaterInvokedCallableTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(string $parameterName, string $description) { $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { return trim("{$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['parameterName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PhpDocTextNode.php 0000644 00000001276 15213400535 0015353 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; class PhpDocTextNode implements PhpDocChildNode { use NodeAttributes; public string $text; public function __construct(string $text) { $this->text = $text; } public function __toString(): string { return $this->text; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['text']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PureUnlessCallableIsImpureTagValueNode.php 0000644 00000001730 15213400535 0022160 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function trim; class PureUnlessCallableIsImpureTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public string $parameterName; /** @var string (may be empty) */ public string $description; public function __construct(string $parameterName, string $description) { $this->parameterName = $parameterName; $this->description = $description; } public function __toString(): string { return trim("{$this->parameterName} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['parameterName'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/TemplateTagValueNode.php 0000644 00000003125 15213400535 0016530 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class TemplateTagValueNode implements PhpDocTagValueNode { use NodeAttributes; /** @var non-empty-string */ public string $name; public ?TypeNode $bound; public ?TypeNode $default; public ?TypeNode $lowerBound; /** @var string (may be empty) */ public string $description; /** * @param non-empty-string $name */ public function __construct(string $name, ?TypeNode $bound, string $description, ?TypeNode $default = null, ?TypeNode $lowerBound = null) { $this->name = $name; $this->bound = $bound; $this->lowerBound = $lowerBound; $this->default = $default; $this->description = $description; } public function __toString(): string { $upperBound = $this->bound !== null ? " of {$this->bound}" : ''; $lowerBound = $this->lowerBound !== null ? " super {$this->lowerBound}" : ''; $default = $this->default !== null ? " = {$this->default}" : ''; return trim("{$this->name}{$upperBound}{$lowerBound}{$default} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['name'], $properties['bound'], $properties['description'], $properties['default'] ?? null, $properties['lowerBound'] ?? null); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/MixinTagValueNode.php 0000644 00000001675 15213400535 0016051 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class MixinTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $description) { $this->type = $type; $this->description = $description; } public function __toString(): string { return trim("{$this->type} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['description']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/AssertTagMethodValueNode.php 0000644 00000002763 15213400535 0017366 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class AssertTagMethodValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public string $parameter; public string $method; public bool $isNegated; public bool $isEquality; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $parameter, string $method, bool $isNegated, string $description, bool $isEquality) { $this->type = $type; $this->parameter = $parameter; $this->method = $method; $this->isNegated = $isNegated; $this->isEquality = $isEquality; $this->description = $description; } public function __toString(): string { $isNegated = $this->isNegated ? '!' : ''; $isEquality = $this->isEquality ? '=' : ''; return trim("{$isNegated}{$isEquality}{$this->type} {$this->parameter}->{$this->method}() {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['parameter'], $properties['method'], $properties['isNegated'], $properties['description'], $properties['isEquality']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/AssertTagValueNode.php 0000644 00000002577 15213400535 0016230 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function trim; class AssertTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public TypeNode $type; public string $parameter; public bool $isNegated; public bool $isEquality; /** @var string (may be empty) */ public string $description; public function __construct(TypeNode $type, string $parameter, bool $isNegated, string $description, bool $isEquality) { $this->type = $type; $this->parameter = $parameter; $this->isNegated = $isNegated; $this->isEquality = $isEquality; $this->description = $description; } public function __toString(): string { $isNegated = $this->isNegated ? '!' : ''; $isEquality = $this->isEquality ? '=' : ''; return trim("{$isNegated}{$isEquality}{$this->type} {$this->parameter} {$this->description}"); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['parameter'], $properties['isNegated'], $properties['description'], $properties['isEquality']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/PhpDocNode.php 0000644 00000024753 15213400535 0014513 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function array_column; use function array_filter; use function array_map; use function implode; class PhpDocNode implements Node { use NodeAttributes; /** @var PhpDocChildNode[] */ public array $children; /** * @param PhpDocChildNode[] $children */ public function __construct(array $children) { $this->children = $children; } /** * @return PhpDocTagNode[] */ public function getTags(): array { return array_filter($this->children, static fn (PhpDocChildNode $child): bool => $child instanceof PhpDocTagNode); } /** * @return PhpDocTagNode[] */ public function getTagsByName(string $tagName): array { return array_filter($this->getTags(), static fn (PhpDocTagNode $tag): bool => $tag->name === $tagName); } /** * @return VarTagValueNode[] */ public function getVarTagValues(string $tagName = '@var'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof VarTagValueNode, ); } /** * @return ParamTagValueNode[] */ public function getParamTagValues(string $tagName = '@param'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamTagValueNode, ); } /** * @return TypelessParamTagValueNode[] */ public function getTypelessParamTagValues(string $tagName = '@param'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof TypelessParamTagValueNode, ); } /** * @return ParamImmediatelyInvokedCallableTagValueNode[] */ public function getParamImmediatelyInvokedCallableTagValues(string $tagName = '@param-immediately-invoked-callable'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamImmediatelyInvokedCallableTagValueNode, ); } /** * @return ParamLaterInvokedCallableTagValueNode[] */ public function getParamLaterInvokedCallableTagValues(string $tagName = '@param-later-invoked-callable'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamLaterInvokedCallableTagValueNode, ); } /** * @return ParamClosureThisTagValueNode[] */ public function getParamClosureThisTagValues(string $tagName = '@param-closure-this'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamClosureThisTagValueNode, ); } /** * @return PureUnlessCallableIsImpureTagValueNode[] */ public function getPureUnlessCallableIsImpureTagValues(string $tagName = '@pure-unless-callable-is-impure'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof PureUnlessCallableIsImpureTagValueNode, ); } /** * @return TemplateTagValueNode[] */ public function getTemplateTagValues(string $tagName = '@template'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof TemplateTagValueNode, ); } /** * @return ExtendsTagValueNode[] */ public function getExtendsTagValues(string $tagName = '@extends'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ExtendsTagValueNode, ); } /** * @return ImplementsTagValueNode[] */ public function getImplementsTagValues(string $tagName = '@implements'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ImplementsTagValueNode, ); } /** * @return UsesTagValueNode[] */ public function getUsesTagValues(string $tagName = '@use'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof UsesTagValueNode, ); } /** * @return ReturnTagValueNode[] */ public function getReturnTagValues(string $tagName = '@return'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ReturnTagValueNode, ); } /** * @return ThrowsTagValueNode[] */ public function getThrowsTagValues(string $tagName = '@throws'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ThrowsTagValueNode, ); } /** * @return MixinTagValueNode[] */ public function getMixinTagValues(string $tagName = '@mixin'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof MixinTagValueNode, ); } /** * @return RequireExtendsTagValueNode[] */ public function getRequireExtendsTagValues(string $tagName = '@phpstan-require-extends'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof RequireExtendsTagValueNode, ); } /** * @return RequireImplementsTagValueNode[] */ public function getRequireImplementsTagValues(string $tagName = '@phpstan-require-implements'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof RequireImplementsTagValueNode, ); } /** * @return SealedTagValueNode[] */ public function getSealedTagValues(string $tagName = '@phpstan-sealed'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof SealedTagValueNode, ); } /** * @return DeprecatedTagValueNode[] */ public function getDeprecatedTagValues(): array { return array_filter( array_column($this->getTagsByName('@deprecated'), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof DeprecatedTagValueNode, ); } /** * @return PropertyTagValueNode[] */ public function getPropertyTagValues(string $tagName = '@property'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof PropertyTagValueNode, ); } /** * @return PropertyTagValueNode[] */ public function getPropertyReadTagValues(string $tagName = '@property-read'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof PropertyTagValueNode, ); } /** * @return PropertyTagValueNode[] */ public function getPropertyWriteTagValues(string $tagName = '@property-write'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof PropertyTagValueNode, ); } /** * @return MethodTagValueNode[] */ public function getMethodTagValues(string $tagName = '@method'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof MethodTagValueNode, ); } /** * @return TypeAliasTagValueNode[] */ public function getTypeAliasTagValues(string $tagName = '@phpstan-type'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof TypeAliasTagValueNode, ); } /** * @return TypeAliasImportTagValueNode[] */ public function getTypeAliasImportTagValues(string $tagName = '@phpstan-import-type'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof TypeAliasImportTagValueNode, ); } /** * @return AssertTagValueNode[] */ public function getAssertTagValues(string $tagName = '@phpstan-assert'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof AssertTagValueNode, ); } /** * @return AssertTagPropertyValueNode[] */ public function getAssertPropertyTagValues(string $tagName = '@phpstan-assert'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof AssertTagPropertyValueNode, ); } /** * @return AssertTagMethodValueNode[] */ public function getAssertMethodTagValues(string $tagName = '@phpstan-assert'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof AssertTagMethodValueNode, ); } /** * @return SelfOutTagValueNode[] */ public function getSelfOutTypeTagValues(string $tagName = '@phpstan-this-out'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof SelfOutTagValueNode, ); } /** * @return ParamOutTagValueNode[] */ public function getParamOutTypeTagValues(string $tagName = '@param-out'): array { return array_filter( array_column($this->getTagsByName($tagName), 'value'), static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamOutTagValueNode, ); } public function __toString(): string { $children = array_map( static function (PhpDocChildNode $child): string { $s = (string) $child; return $s === '' ? '' : ' ' . $s; }, $this->children, ); return "/**\n *" . implode("\n *", $children) . "\n */"; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['children']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/PhpDoc/MethodTagValueNode.php 0000644 00000004002 15213400535 0016170 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\PhpDoc; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use function count; use function implode; class MethodTagValueNode implements PhpDocTagValueNode { use NodeAttributes; public bool $isStatic; public ?TypeNode $returnType = null; public string $methodName; /** @var TemplateTagValueNode[] */ public array $templateTypes; /** @var MethodTagValueParameterNode[] */ public array $parameters; /** @var string (may be empty) */ public string $description; /** * @param MethodTagValueParameterNode[] $parameters * @param TemplateTagValueNode[] $templateTypes */ public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, array $templateTypes) { $this->isStatic = $isStatic; $this->returnType = $returnType; $this->methodName = $methodName; $this->parameters = $parameters; $this->description = $description; $this->templateTypes = $templateTypes; } public function __toString(): string { $static = $this->isStatic ? 'static ' : ''; $returnType = $this->returnType !== null ? "{$this->returnType} " : ''; $parameters = implode(', ', $this->parameters); $description = $this->description !== '' ? " {$this->description}" : ''; $templateTypes = count($this->templateTypes) > 0 ? '<' . implode(', ', $this->templateTypes) . '>' : ''; return "{$static}{$returnType}{$this->methodName}{$templateTypes}({$parameters}){$description}"; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['isStatic'], $properties['returnType'], $properties['methodName'], $properties['parameters'], $properties['description'], $properties['templateTypes']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprFalseNode.php 0000644 00000001114 15213400535 0016770 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstExprFalseNode implements ConstExprNode { use NodeAttributes; public function __toString(): string { return 'false'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self(); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprStringNode.php 0000644 00000006166 15213400535 0017220 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function addcslashes; use function assert; use function dechex; use function ord; use function preg_replace_callback; use function sprintf; use function str_pad; use function strlen; use const STR_PAD_LEFT; class ConstExprStringNode implements ConstExprNode { public const SINGLE_QUOTED = 1; public const DOUBLE_QUOTED = 2; use NodeAttributes; public string $value; /** @var self::SINGLE_QUOTED|self::DOUBLE_QUOTED */ public $quoteType; /** * @param self::SINGLE_QUOTED|self::DOUBLE_QUOTED $quoteType */ public function __construct(string $value, int $quoteType) { $this->value = $value; $this->quoteType = $quoteType; } public function __toString(): string { if ($this->quoteType === self::SINGLE_QUOTED) { // from https://github.com/nikic/PHP-Parser/blob/0ffddce52d816f72d0efc4d9b02e276d3309ef01/lib/PhpParser/PrettyPrinter/Standard.php#L1007 return sprintf("'%s'", addcslashes($this->value, '\'\\')); } // from https://github.com/nikic/PHP-Parser/blob/0ffddce52d816f72d0efc4d9b02e276d3309ef01/lib/PhpParser/PrettyPrinter/Standard.php#L1010-L1040 return sprintf('"%s"', $this->escapeDoubleQuotedString()); } private function escapeDoubleQuotedString(): string { $quote = '"'; $escaped = addcslashes($this->value, "\n\r\t\f\v$" . $quote . '\\'); // Escape control characters and non-UTF-8 characters. // Regex based on https://stackoverflow.com/a/11709412/385378. $regex = '/( [\x00-\x08\x0E-\x1F] # Control characters | [\xC0-\xC1] # Invalid UTF-8 Bytes | [\xF5-\xFF] # Invalid UTF-8 Bytes | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2) )/x'; return preg_replace_callback($regex, static function ($matches) { assert(strlen($matches[0]) === 1); $hex = dechex(ord($matches[0])); return '\\x' . str_pad($hex, 2, '0', STR_PAD_LEFT); }, $escaped); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['value'], $properties['quoteType']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprArrayNode.php 0000644 00000001523 15213400535 0017020 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function implode; class ConstExprArrayNode implements ConstExprNode { use NodeAttributes; /** @var ConstExprArrayItemNode[] */ public array $items; /** * @param ConstExprArrayItemNode[] $items */ public function __construct(array $items) { $this->items = $items; } public function __toString(): string { return '[' . implode(', ', $this->items) . ']'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['items']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprIntegerNode.php 0000644 00000001313 15213400535 0017334 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstExprIntegerNode implements ConstExprNode { use NodeAttributes; public string $value; public function __construct(string $value) { $this->value = $value; } public function __toString(): string { return $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprTrueNode.php 0000644 00000001112 15213400535 0016653 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstExprTrueNode implements ConstExprNode { use NodeAttributes; public function __toString(): string { return 'true'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self(); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstFetchNode.php 0000644 00000001732 15213400535 0016136 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstFetchNode implements ConstExprNode { use NodeAttributes; /** @var string class name for class constants or empty string for non-class constants */ public string $className; public string $name; public function __construct(string $className, string $name) { $this->className = $className; $this->name = $name; } public function __toString(): string { if ($this->className === '') { return $this->name; } return "{$this->className}::{$this->name}"; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['className'], $properties['name']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprFloatNode.php 0000644 00000001311 15213400535 0017002 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstExprFloatNode implements ConstExprNode { use NodeAttributes; public string $value; public function __construct(string $value) { $this->value = $value; } public function __toString(): string { return $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprArrayItemNode.php 0000644 00000001671 15213400535 0017643 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; class ConstExprArrayItemNode implements ConstExprNode { use NodeAttributes; public ?ConstExprNode $key = null; public ConstExprNode $value; public function __construct(?ConstExprNode $key, ConstExprNode $value) { $this->key = $key; $this->value = $value; } public function __toString(): string { if ($this->key !== null) { return sprintf('%s => %s', $this->key, $this->value); } return (string) $this->value; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['key'], $properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/DoctrineConstExprStringNode.php 0000644 00000002535 15213400535 0020704 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; use function str_replace; use function strlen; use function substr; class DoctrineConstExprStringNode extends ConstExprStringNode { use NodeAttributes; public string $value; public function __construct(string $value) { parent::__construct($value, self::DOUBLE_QUOTED); $this->value = $value; } public function __toString(): string { return self::escape($this->value); } public static function unescape(string $value): string { // from https://github.com/doctrine/annotations/blob/a9ec7af212302a75d1f92fa65d3abfbd16245a2a/lib/Doctrine/Common/Annotations/DocLexer.php#L103-L107 return str_replace('""', '"', substr($value, 1, strlen($value) - 2)); } private static function escape(string $value): string { // from https://github.com/phpstan/phpdoc-parser/issues/205#issuecomment-1662323656 return sprintf('"%s"', str_replace('"', '""', $value)); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['value']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprNullNode.php 0000644 00000001112 15213400535 0016646 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstExprNullNode implements ConstExprNode { use NodeAttributes; public function __toString(): string { return 'null'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self(); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/ConstExpr/ConstExprNode.php 0000644 00000000237 15213400535 0016022 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\ConstExpr; use PHPStan\PhpDocParser\Ast\Node; interface ConstExprNode extends Node { } phpdoc-parser/src/Ast/Type/IntersectionTypeNode.php 0000644 00000001755 15213400535 0016407 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function array_map; use function implode; class IntersectionTypeNode implements TypeNode { use NodeAttributes; /** @var TypeNode[] */ public array $types; /** * @param TypeNode[] $types */ public function __construct(array $types) { $this->types = $types; } public function __toString(): string { return '(' . implode(' & ', array_map(static function (TypeNode $type): string { if ($type instanceof NullableTypeNode) { return '(' . $type . ')'; } return (string) $type; }, $this->types)) . ')'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['types']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ConditionalTypeForParameterNode.php 0000644 00000002406 15213400535 0020506 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; class ConditionalTypeForParameterNode implements TypeNode { use NodeAttributes; public string $parameterName; public TypeNode $targetType; public TypeNode $if; public TypeNode $else; public bool $negated; public function __construct(string $parameterName, TypeNode $targetType, TypeNode $if, TypeNode $else, bool $negated) { $this->parameterName = $parameterName; $this->targetType = $targetType; $this->if = $if; $this->else = $else; $this->negated = $negated; } public function __toString(): string { return sprintf( '(%s %s %s ? %s : %s)', $this->parameterName, $this->negated ? 'is not' : 'is', $this->targetType, $this->if, $this->else, ); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['parameterName'], $properties['targetType'], $properties['if'], $properties['else'], $properties['negated']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ThisTypeNode.php 0000644 00000001074 15213400535 0014642 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ThisTypeNode implements TypeNode { use NodeAttributes; public function __toString(): string { return '$this'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self(); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/NullableTypeNode.php 0000644 00000001301 15213400535 0015462 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; class NullableTypeNode implements TypeNode { use NodeAttributes; public TypeNode $type; public function __construct(TypeNode $type) { $this->type = $type; } public function __toString(): string { return '?' . $this->type; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ArrayShapeItemNode.php 0000644 00000002762 15213400535 0015754 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; class ArrayShapeItemNode implements Node { use NodeAttributes; /** @var ConstExprIntegerNode|ConstExprStringNode|ConstFetchNode|IdentifierTypeNode|null */ public $keyName; public bool $optional; public TypeNode $valueType; /** * @param ConstExprIntegerNode|ConstExprStringNode|ConstFetchNode|IdentifierTypeNode|null $keyName */ public function __construct($keyName, bool $optional, TypeNode $valueType) { $this->keyName = $keyName; $this->optional = $optional; $this->valueType = $valueType; } public function __toString(): string { if ($this->keyName !== null) { return sprintf( '%s%s: %s', (string) $this->keyName, $this->optional ? '?' : '', (string) $this->valueType, ); } return (string) $this->valueType; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['keyName'], $properties['optional'], $properties['valueType']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/IdentifierTypeNode.php 0000644 00000001271 15213400535 0016014 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; class IdentifierTypeNode implements TypeNode { use NodeAttributes; public string $name; public function __construct(string $name) { $this->name = $name; } public function __toString(): string { return $this->name; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['name']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/UnionTypeNode.php 0000644 00000001747 15213400535 0015032 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function array_map; use function implode; class UnionTypeNode implements TypeNode { use NodeAttributes; /** @var TypeNode[] */ public array $types; /** * @param TypeNode[] $types */ public function __construct(array $types) { $this->types = $types; } public function __toString(): string { return '(' . implode(' | ', array_map(static function (TypeNode $type): string { if ($type instanceof NullableTypeNode) { return '(' . $type . ')'; } return (string) $type; }, $this->types)) . ')'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['types']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ConditionalTypeNode.php 0000644 00000002362 15213400535 0016177 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; class ConditionalTypeNode implements TypeNode { use NodeAttributes; public TypeNode $subjectType; public TypeNode $targetType; public TypeNode $if; public TypeNode $else; public bool $negated; public function __construct(TypeNode $subjectType, TypeNode $targetType, TypeNode $if, TypeNode $else, bool $negated) { $this->subjectType = $subjectType; $this->targetType = $targetType; $this->if = $if; $this->else = $else; $this->negated = $negated; } public function __toString(): string { return sprintf( '(%s %s %s ? %s : %s)', $this->subjectType, $this->negated ? 'is not' : 'is', $this->targetType, $this->if, $this->else, ); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['subjectType'], $properties['targetType'], $properties['if'], $properties['else'], $properties['negated']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ObjectShapeItemNode.php 0000644 00000002455 15213400535 0016103 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; class ObjectShapeItemNode implements Node { use NodeAttributes; /** @var ConstExprStringNode|IdentifierTypeNode */ public $keyName; public bool $optional; public TypeNode $valueType; /** * @param ConstExprStringNode|IdentifierTypeNode $keyName */ public function __construct($keyName, bool $optional, TypeNode $valueType) { $this->keyName = $keyName; $this->optional = $optional; $this->valueType = $valueType; } public function __toString(): string { if ($this->keyName !== null) { return sprintf( '%s%s: %s', (string) $this->keyName, $this->optional ? '?' : '', (string) $this->valueType, ); } return (string) $this->valueType; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['keyName'], $properties['optional'], $properties['valueType']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/InvalidTypeNode.php 0000644 00000002261 15213400535 0015320 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Parser\ParserException; class InvalidTypeNode implements TypeNode { use NodeAttributes; /** @var mixed[] */ private array $exceptionArgs; public function __construct(ParserException $exception) { $this->exceptionArgs = [ $exception->getCurrentTokenValue(), $exception->getCurrentTokenType(), $exception->getCurrentOffset(), $exception->getExpectedTokenType(), $exception->getExpectedTokenValue(), $exception->getCurrentTokenLine(), ]; } public function getException(): ParserException { return new ParserException(...$this->exceptionArgs); } public function __toString(): string { return '*Invalid type*'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $exception = new ParserException(...$properties['exceptionArgs']); $instance = new self($exception); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/TypeNode.php 0000644 00000000225 15213400535 0014007 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\Node; interface TypeNode extends Node { } phpdoc-parser/src/Ast/Type/ArrayTypeNode.php 0000644 00000001572 15213400535 0015014 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ArrayTypeNode implements TypeNode { use NodeAttributes; public TypeNode $type; public function __construct(TypeNode $type) { $this->type = $type; } public function __toString(): string { if ( $this->type instanceof CallableTypeNode || $this->type instanceof ConstTypeNode || $this->type instanceof NullableTypeNode ) { return '(' . $this->type . ')[]'; } return $this->type . '[]'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ObjectShapeNode.php 0000644 00000001532 15213400535 0015257 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function implode; class ObjectShapeNode implements TypeNode { use NodeAttributes; /** @var ObjectShapeItemNode[] */ public array $items; /** * @param ObjectShapeItemNode[] $items */ public function __construct(array $items) { $this->items = $items; } public function __toString(): string { $items = $this->items; return 'object{' . implode(', ', $items) . '}'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['items']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/OffsetAccessTypeNode.php 0000644 00000001737 15213400535 0016311 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; class OffsetAccessTypeNode implements TypeNode { use NodeAttributes; public TypeNode $type; public TypeNode $offset; public function __construct(TypeNode $type, TypeNode $offset) { $this->type = $type; $this->offset = $offset; } public function __toString(): string { if ( $this->type instanceof CallableTypeNode || $this->type instanceof NullableTypeNode ) { return '(' . $this->type . ')[' . $this->offset . ']'; } return $this->type . '[' . $this->offset . ']'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['offset']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/GenericTypeNode.php 0000644 00000003365 15213400535 0015314 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function implode; use function sprintf; class GenericTypeNode implements TypeNode { public const VARIANCE_INVARIANT = 'invariant'; public const VARIANCE_COVARIANT = 'covariant'; public const VARIANCE_CONTRAVARIANT = 'contravariant'; public const VARIANCE_BIVARIANT = 'bivariant'; use NodeAttributes; public IdentifierTypeNode $type; /** @var TypeNode[] */ public array $genericTypes; /** @var (self::VARIANCE_*)[] */ public array $variances; /** * @param TypeNode[] $genericTypes * @param (self::VARIANCE_*)[] $variances */ public function __construct(IdentifierTypeNode $type, array $genericTypes, array $variances = []) { $this->type = $type; $this->genericTypes = $genericTypes; $this->variances = $variances; } public function __toString(): string { $genericTypes = []; foreach ($this->genericTypes as $index => $type) { $variance = $this->variances[$index] ?? self::VARIANCE_INVARIANT; if ($variance === self::VARIANCE_INVARIANT) { $genericTypes[] = (string) $type; } elseif ($variance === self::VARIANCE_BIVARIANT) { $genericTypes[] = '*'; } else { $genericTypes[] = sprintf('%s %s', $variance, $type); } } return $this->type . '<' . implode(', ', $genericTypes) . '>'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['genericTypes'], $properties['variances'] ?? []); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/CallableTypeNode.php 0000644 00000003222 15213400535 0015427 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use function implode; class CallableTypeNode implements TypeNode { use NodeAttributes; public IdentifierTypeNode $identifier; /** @var TemplateTagValueNode[] */ public array $templateTypes; /** @var CallableTypeParameterNode[] */ public array $parameters; public TypeNode $returnType; /** * @param CallableTypeParameterNode[] $parameters * @param TemplateTagValueNode[] $templateTypes */ public function __construct(IdentifierTypeNode $identifier, array $parameters, TypeNode $returnType, array $templateTypes) { $this->identifier = $identifier; $this->parameters = $parameters; $this->returnType = $returnType; $this->templateTypes = $templateTypes; } public function __toString(): string { $returnType = $this->returnType; if ($returnType instanceof self) { $returnType = "({$returnType})"; } $template = $this->templateTypes !== [] ? '<' . implode(', ', $this->templateTypes) . '>' : ''; $parameters = implode(', ', $this->parameters); return "{$this->identifier}{$template}({$parameters}): {$returnType}"; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['identifier'], $properties['parameters'], $properties['returnType'], $properties['templateTypes']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ArrayShapeNode.php 0000644 00000004000 15213400535 0015120 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function implode; class ArrayShapeNode implements TypeNode { public const KIND_ARRAY = 'array'; public const KIND_LIST = 'list'; public const KIND_NON_EMPTY_ARRAY = 'non-empty-array'; public const KIND_NON_EMPTY_LIST = 'non-empty-list'; use NodeAttributes; /** @var ArrayShapeItemNode[] */ public array $items; public bool $sealed; /** @var self::KIND_* */ public $kind; public ?ArrayShapeUnsealedTypeNode $unsealedType = null; /** * @param ArrayShapeItemNode[] $items * @param self::KIND_* $kind */ private function __construct( array $items, bool $sealed = true, ?ArrayShapeUnsealedTypeNode $unsealedType = null, string $kind = self::KIND_ARRAY ) { $this->items = $items; $this->sealed = $sealed; $this->unsealedType = $unsealedType; $this->kind = $kind; } /** * @param ArrayShapeItemNode[] $items * @param self::KIND_* $kind */ public static function createSealed(array $items, string $kind = self::KIND_ARRAY): self { return new self($items, true, null, $kind); } /** * @param ArrayShapeItemNode[] $items * @param self::KIND_* $kind */ public static function createUnsealed(array $items, ?ArrayShapeUnsealedTypeNode $unsealedType, string $kind = self::KIND_ARRAY): self { return new self($items, false, $unsealedType, $kind); } public function __toString(): string { $items = $this->items; if (! $this->sealed) { $items[] = '...' . $this->unsealedType; } return $this->kind . '{' . implode(', ', $items) . '}'; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['items'], $properties['sealed'], $properties['unsealedType'], $properties['kind']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/CallableTypeParameterNode.php 0000644 00000002712 15213400535 0017273 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function trim; class CallableTypeParameterNode implements Node { use NodeAttributes; public TypeNode $type; public bool $isReference; public bool $isVariadic; /** @var string (may be empty) */ public string $parameterName; public bool $isOptional; public function __construct(TypeNode $type, bool $isReference, bool $isVariadic, string $parameterName, bool $isOptional) { $this->type = $type; $this->isReference = $isReference; $this->isVariadic = $isVariadic; $this->parameterName = $parameterName; $this->isOptional = $isOptional; } public function __toString(): string { $type = "{$this->type} "; $isReference = $this->isReference ? '&' : ''; $isVariadic = $this->isVariadic ? '...' : ''; $isOptional = $this->isOptional ? '=' : ''; return trim("{$type}{$isReference}{$isVariadic}{$this->parameterName}") . $isOptional; } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['type'], $properties['isReference'], $properties['isVariadic'], $properties['parameterName'], $properties['isOptional']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ConstTypeNode.php 0000644 00000001444 15213400535 0015022 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\NodeAttributes; class ConstTypeNode implements TypeNode { use NodeAttributes; public ConstExprNode $constExpr; public function __construct(ConstExprNode $constExpr) { $this->constExpr = $constExpr; } public function __toString(): string { return $this->constExpr->__toString(); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['constExpr']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Type/ArrayShapeUnsealedTypeNode.php 0000644 00000001774 15213400535 0017462 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast\Type; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeAttributes; use function sprintf; class ArrayShapeUnsealedTypeNode implements Node { use NodeAttributes; public TypeNode $valueType; public ?TypeNode $keyType = null; public function __construct(TypeNode $valueType, ?TypeNode $keyType) { $this->valueType = $valueType; $this->keyType = $keyType; } public function __toString(): string { if ($this->keyType !== null) { return sprintf('<%s, %s>', $this->keyType, $this->valueType); } return sprintf('<%s>', $this->valueType); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { $instance = new self($properties['valueType'], $properties['keyType']); if (isset($properties['attributes'])) { foreach ($properties['attributes'] as $key => $value) { $instance->setAttribute($key, $value); } } return $instance; } } phpdoc-parser/src/Ast/Comment.php 0000644 00000001230 15213400535 0012736 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; use function trim; class Comment { public string $text; public int $startLine; public int $startIndex; public function __construct(string $text, int $startLine = -1, int $startIndex = -1) { $this->text = $text; $this->startLine = $startLine; $this->startIndex = $startIndex; } public function getReformattedText(): string { return trim($this->text); } /** * @param array<string, mixed> $properties */ public static function __set_state(array $properties): self { return new self($properties['text'], $properties['startLine'], $properties['startIndex']); } } phpdoc-parser/src/Ast/NodeAttributes.php 0000644 00000001252 15213400535 0014274 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Ast; use function array_key_exists; trait NodeAttributes { /** @var array<string, mixed> */ private array $attributes = []; /** * @param mixed $value */ public function setAttribute(string $key, $value): void { if ($value === null) { unset($this->attributes[$key]); return; } $this->attributes[$key] = $value; } public function hasAttribute(string $key): bool { return array_key_exists($key, $this->attributes); } /** * @return mixed */ public function getAttribute(string $key) { if ($this->hasAttribute($key)) { return $this->attributes[$key]; } return null; } } phpdoc-parser/src/Printer/Differ.php 0000644 00000010204 15213400535 0013430 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Printer; use Exception; use function array_reverse; use function count; /** * Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1 * * Copyright (c) 2011, Nikita Popov * All rights reserved. * * Implements the Myers diff algorithm. * * Myers, Eugene W. "An O (ND) difference algorithm and its variations." * Algorithmica 1.1 (1986): 251-266. * * @template T * @internal */ class Differ { /** @var callable(T, T): bool */ private $isEqual; /** * Create differ over the given equality relation. * * @param callable(T, T): bool $isEqual Equality relation */ public function __construct(callable $isEqual) { $this->isEqual = $isEqual; } /** * Calculate diff (edit script) from $old to $new. * * @param T[] $old Original array * @param T[] $new New array * * @return DiffElem[] Diff (edit script) */ public function diff(array $old, array $new): array { [$trace, $x, $y] = $this->calculateTrace($old, $new); return $this->extractDiff($trace, $x, $y, $old, $new); } /** * Calculate diff, including "replace" operations. * * If a sequence of remove operations is followed by the same number of add operations, these * will be coalesced into replace operations. * * @param T[] $old Original array * @param T[] $new New array * * @return DiffElem[] Diff (edit script), including replace operations */ public function diffWithReplacements(array $old, array $new): array { return $this->coalesceReplacements($this->diff($old, $new)); } /** * @param T[] $old * @param T[] $new * @return array{array<int, array<int, int>>, int, int} */ private function calculateTrace(array $old, array $new): array { $n = count($old); $m = count($new); $max = $n + $m; $v = [1 => 0]; $trace = []; for ($d = 0; $d <= $max; $d++) { $trace[] = $v; for ($k = -$d; $k <= $d; $k += 2) { if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { $x = $v[$k + 1]; } else { $x = $v[$k - 1] + 1; } $y = $x - $k; while ($x < $n && $y < $m && ($this->isEqual)($old[$x], $new[$y])) { $x++; $y++; } $v[$k] = $x; if ($x >= $n && $y >= $m) { return [$trace, $x, $y]; } } } throw new Exception('Should not happen'); } /** * @param array<int, array<int, int>> $trace * @param T[] $old * @param T[] $new * @return DiffElem[] */ private function extractDiff(array $trace, int $x, int $y, array $old, array $new): array { $result = []; for ($d = count($trace) - 1; $d >= 0; $d--) { $v = $trace[$d]; $k = $x - $y; if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { $prevK = $k + 1; } else { $prevK = $k - 1; } $prevX = $v[$prevK]; $prevY = $prevX - $prevK; while ($x > $prevX && $y > $prevY) { $result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x - 1], $new[$y - 1]); $x--; $y--; } if ($d === 0) { break; } while ($x > $prevX) { $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x - 1], null); $x--; } while ($y > $prevY) { $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y - 1]); $y--; } } return array_reverse($result); } /** * Coalesce equal-length sequences of remove+add into a replace operation. * * @param DiffElem[] $diff * @return DiffElem[] */ private function coalesceReplacements(array $diff): array { $newDiff = []; $c = count($diff); for ($i = 0; $i < $c; $i++) { $diffType = $diff[$i]->type; if ($diffType !== DiffElem::TYPE_REMOVE) { $newDiff[] = $diff[$i]; continue; } $j = $i; while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { $j++; } $k = $j; while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { $k++; } if ($j - $i === $k - $j) { $len = $j - $i; for ($n = 0; $n < $len; $n++) { $newDiff[] = new DiffElem( DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new, ); } } else { for (; $i < $k; $i++) { $newDiff[] = $diff[$i]; } } $i = $k - 1; } return $newDiff; } } phpdoc-parser/src/Printer/DiffElem.php 0000644 00000001553 15213400535 0013713 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Printer; /** * Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1 * * Copyright (c) 2011, Nikita Popov * All rights reserved. * * Implements the Myers diff algorithm. * * @internal */ class DiffElem { public const TYPE_KEEP = 0; public const TYPE_REMOVE = 1; public const TYPE_ADD = 2; public const TYPE_REPLACE = 3; /** @var self::TYPE_* */ public $type; /** @var mixed Is null for add operations */ public $old; /** @var mixed Is null for remove operations */ public $new; /** * @param self::TYPE_* $type * @param mixed $old Is null for add operations * @param mixed $new Is null for remove operations */ public function __construct(int $type, $old, $new) { $this->type = $type; $this->old = $old; $this->new = $new; } } phpdoc-parser/src/Printer/Printer.php 0000644 00000075640 15213400535 0013673 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser\Printer; use LogicException; use PHPStan\PhpDocParser\Ast\Attribute; use PHPStan\PhpDocParser\Ast\Comment; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineAnnotation; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArrayItem; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamClosureThisTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamOutTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\SealedTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeUnsealedTypeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\TokenIterator; use function array_keys; use function array_map; use function assert; use function count; use function get_class; use function get_object_vars; use function implode; use function in_array; use function is_array; use function preg_match_all; use function sprintf; use function str_replace; use function strlen; use function strpos; use function trim; use const PREG_SET_ORDER; /** * Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1 * * Copyright (c) 2011, Nikita Popov * All rights reserved. */ final class Printer { /** @var Differ<Node> */ private Differ $differ; /** * Map From "{$class}->{$subNode}" to string that should be inserted * between elements of this list subnode * * @var array<string, string> */ private array $listInsertionMap = [ PhpDocNode::class . '->children' => "\n * ", UnionTypeNode::class . '->types' => '|', IntersectionTypeNode::class . '->types' => '&', ArrayShapeNode::class . '->items' => ', ', ObjectShapeNode::class . '->items' => ', ', CallableTypeNode::class . '->parameters' => ', ', CallableTypeNode::class . '->templateTypes' => ', ', GenericTypeNode::class . '->genericTypes' => ', ', ConstExprArrayNode::class . '->items' => ', ', MethodTagValueNode::class . '->parameters' => ', ', DoctrineArray::class . '->items' => ', ', DoctrineAnnotation::class . '->arguments' => ', ', ]; /** * [$find, $extraLeft, $extraRight] * * @var array<string, array{string|null, string, string}> */ private array $emptyListInsertionMap = [ CallableTypeNode::class . '->parameters' => ['(', '', ''], ArrayShapeNode::class . '->items' => ['{', '', ''], ObjectShapeNode::class . '->items' => ['{', '', ''], DoctrineArray::class . '->items' => ['{', '', ''], DoctrineAnnotation::class . '->arguments' => ['(', '', ''], ]; /** @var array<string, list<class-string<TypeNode>>> */ private array $parenthesesMap = [ CallableTypeNode::class . '->returnType' => [ CallableTypeNode::class, UnionTypeNode::class, IntersectionTypeNode::class, ], ArrayTypeNode::class . '->type' => [ CallableTypeNode::class, UnionTypeNode::class, IntersectionTypeNode::class, ConstTypeNode::class, NullableTypeNode::class, ], OffsetAccessTypeNode::class . '->type' => [ CallableTypeNode::class, UnionTypeNode::class, IntersectionTypeNode::class, NullableTypeNode::class, ], ]; /** @var array<string, list<class-string<TypeNode>>> */ private array $parenthesesListMap = [ IntersectionTypeNode::class . '->types' => [ IntersectionTypeNode::class, UnionTypeNode::class, NullableTypeNode::class, ], UnionTypeNode::class . '->types' => [ IntersectionTypeNode::class, UnionTypeNode::class, NullableTypeNode::class, ], ]; public function printFormatPreserving(PhpDocNode $node, PhpDocNode $originalNode, TokenIterator $originalTokens): string { $this->differ = new Differ(static function ($a, $b) { if ($a instanceof Node && $b instanceof Node) { return $a === $b->getAttribute(Attribute::ORIGINAL_NODE); } return false; }); $tokenIndex = 0; $result = $this->printArrayFormatPreserving( $node->children, $originalNode->children, $originalTokens, $tokenIndex, PhpDocNode::class, 'children', ); if ($result !== null) { return $result . $originalTokens->getContentBetween($tokenIndex, $originalTokens->getTokenCount()); } return $this->print($node); } public function print(Node $node): string { if ($node instanceof PhpDocNode) { return "/**\n *" . implode("\n *", array_map( function (PhpDocChildNode $child): string { $s = $this->print($child); return $s === '' ? '' : ' ' . $s; }, $node->children, )) . "\n */"; } if ($node instanceof PhpDocTextNode) { return $node->text; } if ($node instanceof PhpDocTagNode) { if ($node->value instanceof DoctrineTagValueNode) { return $this->print($node->value); } return trim(sprintf('%s %s', $node->name, $this->print($node->value))); } if ($node instanceof PhpDocTagValueNode) { return $this->printTagValue($node); } if ($node instanceof TypeNode) { return $this->printType($node); } if ($node instanceof ConstExprNode) { return $this->printConstExpr($node); } if ($node instanceof MethodTagValueParameterNode) { $type = $node->type !== null ? $this->print($node->type) . ' ' : ''; $isReference = $node->isReference ? '&' : ''; $isVariadic = $node->isVariadic ? '...' : ''; $default = $node->defaultValue !== null ? ' = ' . $this->print($node->defaultValue) : ''; return "{$type}{$isReference}{$isVariadic}{$node->parameterName}{$default}"; } if ($node instanceof CallableTypeParameterNode) { $type = $this->print($node->type) . ' '; $isReference = $node->isReference ? '&' : ''; $isVariadic = $node->isVariadic ? '...' : ''; $isOptional = $node->isOptional ? '=' : ''; return trim("{$type}{$isReference}{$isVariadic}{$node->parameterName}") . $isOptional; } if ($node instanceof ArrayShapeUnsealedTypeNode) { if ($node->keyType !== null) { return sprintf('<%s, %s>', $this->printType($node->keyType), $this->printType($node->valueType)); } return sprintf('<%s>', $this->printType($node->valueType)); } if ($node instanceof DoctrineAnnotation) { return (string) $node; } if ($node instanceof DoctrineArgument) { return (string) $node; } if ($node instanceof DoctrineArray) { return (string) $node; } if ($node instanceof DoctrineArrayItem) { return (string) $node; } if ($node instanceof ArrayShapeItemNode) { if ($node->keyName !== null) { return sprintf( '%s%s: %s', $this->print($node->keyName), $node->optional ? '?' : '', $this->printType($node->valueType), ); } return $this->printType($node->valueType); } if ($node instanceof ObjectShapeItemNode) { if ($node->keyName !== null) { return sprintf( '%s%s: %s', $this->print($node->keyName), $node->optional ? '?' : '', $this->printType($node->valueType), ); } return $this->printType($node->valueType); } throw new LogicException(sprintf('Unknown node type %s', get_class($node))); } private function printTagValue(PhpDocTagValueNode $node): string { // only nodes that contain another node are handled here // the rest falls back on (string) $node if ($node instanceof AssertTagMethodValueNode) { $isNegated = $node->isNegated ? '!' : ''; $isEquality = $node->isEquality ? '=' : ''; $type = $this->printType($node->type); return trim("{$isNegated}{$isEquality}{$type} {$node->parameter}->{$node->method}() {$node->description}"); } if ($node instanceof AssertTagPropertyValueNode) { $isNegated = $node->isNegated ? '!' : ''; $isEquality = $node->isEquality ? '=' : ''; $type = $this->printType($node->type); return trim("{$isNegated}{$isEquality}{$type} {$node->parameter}->{$node->property} {$node->description}"); } if ($node instanceof AssertTagValueNode) { $isNegated = $node->isNegated ? '!' : ''; $isEquality = $node->isEquality ? '=' : ''; $type = $this->printType($node->type); return trim("{$isNegated}{$isEquality}{$type} {$node->parameter} {$node->description}"); } if ($node instanceof ExtendsTagValueNode || $node instanceof ImplementsTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof MethodTagValueNode) { $static = $node->isStatic ? 'static ' : ''; $returnType = $node->returnType !== null ? $this->printType($node->returnType) . ' ' : ''; $parameters = implode(', ', array_map(fn (MethodTagValueParameterNode $parameter): string => $this->print($parameter), $node->parameters)); $description = $node->description !== '' ? " {$node->description}" : ''; $templateTypes = count($node->templateTypes) > 0 ? '<' . implode(', ', array_map(fn (TemplateTagValueNode $templateTag): string => $this->print($templateTag), $node->templateTypes)) . '>' : ''; return "{$static}{$returnType}{$node->methodName}{$templateTypes}({$parameters}){$description}"; } if ($node instanceof MixinTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof RequireExtendsTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof RequireImplementsTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof SealedTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof ParamOutTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->parameterName} {$node->description}"); } if ($node instanceof ParamTagValueNode) { $reference = $node->isReference ? '&' : ''; $variadic = $node->isVariadic ? '...' : ''; $type = $this->printType($node->type); return trim("{$type} {$reference}{$variadic}{$node->parameterName} {$node->description}"); } if ($node instanceof ParamImmediatelyInvokedCallableTagValueNode) { return trim("{$node->parameterName} {$node->description}"); } if ($node instanceof ParamLaterInvokedCallableTagValueNode) { return trim("{$node->parameterName} {$node->description}"); } if ($node instanceof ParamClosureThisTagValueNode) { return trim("{$node->type} {$node->parameterName} {$node->description}"); } if ($node instanceof PureUnlessCallableIsImpureTagValueNode) { return trim("{$node->parameterName} {$node->description}"); } if ($node instanceof PropertyTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->propertyName} {$node->description}"); } if ($node instanceof ReturnTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof SelfOutTagValueNode) { $type = $this->printType($node->type); return trim($type . ' ' . $node->description); } if ($node instanceof TemplateTagValueNode) { $upperBound = $node->bound !== null ? ' of ' . $this->printType($node->bound) : ''; $lowerBound = $node->lowerBound !== null ? ' super ' . $this->printType($node->lowerBound) : ''; $default = $node->default !== null ? ' = ' . $this->printType($node->default) : ''; return trim("{$node->name}{$upperBound}{$lowerBound}{$default} {$node->description}"); } if ($node instanceof ThrowsTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof TypeAliasImportTagValueNode) { return trim( "{$node->importedAlias} from " . $this->printType($node->importedFrom) . ($node->importedAs !== null ? " as {$node->importedAs}" : ''), ); } if ($node instanceof TypeAliasTagValueNode) { $type = $this->printType($node->type); return trim("{$node->alias} {$type}"); } if ($node instanceof UsesTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->description}"); } if ($node instanceof VarTagValueNode) { $type = $this->printType($node->type); return trim("{$type} " . trim("{$node->variableName} {$node->description}")); } return (string) $node; } private function printType(TypeNode $node): string { if ($node instanceof ArrayShapeNode) { $items = array_map(fn (ArrayShapeItemNode $item): string => $this->print($item), $node->items); if (! $node->sealed) { $items[] = '...' . ($node->unsealedType === null ? '' : $this->print($node->unsealedType)); } return $node->kind . '{' . implode(', ', $items) . '}'; } if ($node instanceof ArrayTypeNode) { return $this->printOffsetAccessType($node->type) . '[]'; } if ($node instanceof CallableTypeNode) { if ($node->returnType instanceof CallableTypeNode || $node->returnType instanceof UnionTypeNode || $node->returnType instanceof IntersectionTypeNode) { $returnType = $this->wrapInParentheses($node->returnType); } else { $returnType = $this->printType($node->returnType); } $template = $node->templateTypes !== [] ? '<' . implode(', ', array_map(fn (TemplateTagValueNode $templateNode): string => $this->print($templateNode), $node->templateTypes)) . '>' : ''; $parameters = implode(', ', array_map(fn (CallableTypeParameterNode $parameterNode): string => $this->print($parameterNode), $node->parameters)); return "{$node->identifier}{$template}({$parameters}): {$returnType}"; } if ($node instanceof ConditionalTypeForParameterNode) { return sprintf( '(%s %s %s ? %s : %s)', $node->parameterName, $node->negated ? 'is not' : 'is', $this->printType($node->targetType), $this->printType($node->if), $this->printType($node->else), ); } if ($node instanceof ConditionalTypeNode) { return sprintf( '(%s %s %s ? %s : %s)', $this->printType($node->subjectType), $node->negated ? 'is not' : 'is', $this->printType($node->targetType), $this->printType($node->if), $this->printType($node->else), ); } if ($node instanceof ConstTypeNode) { return $this->printConstExpr($node->constExpr); } if ($node instanceof GenericTypeNode) { $genericTypes = []; foreach ($node->genericTypes as $index => $type) { $variance = $node->variances[$index] ?? GenericTypeNode::VARIANCE_INVARIANT; if ($variance === GenericTypeNode::VARIANCE_INVARIANT) { $genericTypes[] = $this->printType($type); } elseif ($variance === GenericTypeNode::VARIANCE_BIVARIANT) { $genericTypes[] = '*'; } else { $genericTypes[] = sprintf('%s %s', $variance, $this->print($type)); } } return $node->type . '<' . implode(', ', $genericTypes) . '>'; } if ($node instanceof IdentifierTypeNode) { return $node->name; } if ($node instanceof IntersectionTypeNode || $node instanceof UnionTypeNode) { $items = []; foreach ($node->types as $type) { if ( $type instanceof IntersectionTypeNode || $type instanceof UnionTypeNode || $type instanceof NullableTypeNode ) { $items[] = $this->wrapInParentheses($type); continue; } $items[] = $this->printType($type); } return implode($node instanceof IntersectionTypeNode ? '&' : '|', $items); } if ($node instanceof InvalidTypeNode) { return (string) $node; } if ($node instanceof NullableTypeNode) { if ($node->type instanceof IntersectionTypeNode || $node->type instanceof UnionTypeNode) { return '?(' . $this->printType($node->type) . ')'; } return '?' . $this->printType($node->type); } if ($node instanceof ObjectShapeNode) { $items = array_map(fn (ObjectShapeItemNode $item): string => $this->print($item), $node->items); return 'object{' . implode(', ', $items) . '}'; } if ($node instanceof OffsetAccessTypeNode) { return $this->printOffsetAccessType($node->type) . '[' . $this->printType($node->offset) . ']'; } if ($node instanceof ThisTypeNode) { return (string) $node; } throw new LogicException(sprintf('Unknown node type %s', get_class($node))); } private function wrapInParentheses(TypeNode $node): string { return '(' . $this->printType($node) . ')'; } private function printOffsetAccessType(TypeNode $type): string { if ( $type instanceof CallableTypeNode || $type instanceof UnionTypeNode || $type instanceof IntersectionTypeNode || $type instanceof NullableTypeNode ) { return $this->wrapInParentheses($type); } return $this->printType($type); } private function printConstExpr(ConstExprNode $node): string { // this is fine - ConstExprNode classes do not contain nodes that need smart printer logic return (string) $node; } /** * @param Node[] $nodes * @param Node[] $originalNodes */ private function printArrayFormatPreserving(array $nodes, array $originalNodes, TokenIterator $originalTokens, int &$tokenIndex, string $parentNodeClass, string $subNodeName): ?string { $diff = $this->differ->diffWithReplacements($originalNodes, $nodes); $mapKey = $parentNodeClass . '->' . $subNodeName; $insertStr = $this->listInsertionMap[$mapKey] ?? null; $result = ''; $beforeFirstKeepOrReplace = true; $delayedAdd = []; $insertNewline = false; [$isMultiline, $beforeAsteriskIndent, $afterAsteriskIndent] = $this->isMultiline($tokenIndex, $originalNodes, $originalTokens); if ($insertStr === "\n * ") { $insertStr = sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); } foreach ($diff as $i => $diffElem) { $diffType = $diffElem->type; $arrItem = $diffElem->new; $origArrayItem = $diffElem->old; if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { $beforeFirstKeepOrReplace = false; if (!$arrItem instanceof Node || !$origArrayItem instanceof Node) { return null; } /** @var int $itemStartPos */ $itemStartPos = $origArrayItem->getAttribute(Attribute::START_INDEX); /** @var int $itemEndPos */ $itemEndPos = $origArrayItem->getAttribute(Attribute::END_INDEX); if ($itemStartPos < 0 || $itemEndPos < 0 || $itemStartPos < $tokenIndex) { throw new LogicException(); } $comments = $arrItem->getAttribute(Attribute::COMMENTS) ?? []; $origComments = $origArrayItem->getAttribute(Attribute::COMMENTS) ?? []; $commentStartPos = count($origComments) > 0 ? $origComments[0]->startIndex : $itemStartPos; assert($commentStartPos >= 0); $result .= $originalTokens->getContentBetween($tokenIndex, $itemStartPos); if (count($delayedAdd) > 0) { foreach ($delayedAdd as $delayedAddNode) { $parenthesesNeeded = isset($this->parenthesesListMap[$mapKey]) && in_array(get_class($delayedAddNode), $this->parenthesesListMap[$mapKey], true); if ($parenthesesNeeded) { $result .= '('; } if ($insertNewline) { $delayedAddComments = $delayedAddNode->getAttribute(Attribute::COMMENTS) ?? []; if (count($delayedAddComments) > 0) { $result .= $this->printComments($delayedAddComments, $beforeAsteriskIndent, $afterAsteriskIndent); $result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); } } $result .= $this->printNodeFormatPreserving($delayedAddNode, $originalTokens); if ($parenthesesNeeded) { $result .= ')'; } if ($insertNewline) { $result .= $insertStr . sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); } else { $result .= $insertStr; } } $delayedAdd = []; } $parenthesesNeeded = isset($this->parenthesesListMap[$mapKey]) && in_array(get_class($arrItem), $this->parenthesesListMap[$mapKey], true) && !in_array(get_class($origArrayItem), $this->parenthesesListMap[$mapKey], true); $addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($itemStartPos, $itemEndPos); if ($addParentheses) { $result .= '('; } if ($comments !== $origComments) { if (count($comments) > 0) { $result .= $this->printComments($comments, $beforeAsteriskIndent, $afterAsteriskIndent); $result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); } } $result .= $this->printNodeFormatPreserving($arrItem, $originalTokens); if ($addParentheses) { $result .= ')'; } $tokenIndex = $itemEndPos + 1; } elseif ($diffType === DiffElem::TYPE_ADD) { if ($insertStr === null) { return null; } if (!$arrItem instanceof Node) { return null; } if ($insertStr === ', ' && $isMultiline || count($arrItem->getAttribute(Attribute::COMMENTS) ?? []) > 0) { $insertStr = ','; $insertNewline = true; } if ($beforeFirstKeepOrReplace) { // Will be inserted at the next "replace" or "keep" element $delayedAdd[] = $arrItem; continue; } /** @var int $itemEndPos */ $itemEndPos = $tokenIndex - 1; if ($insertNewline) { $comments = $arrItem->getAttribute(Attribute::COMMENTS) ?? []; $result .= $insertStr; if (count($comments) > 0) { $result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); $result .= $this->printComments($comments, $beforeAsteriskIndent, $afterAsteriskIndent); } $result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); } else { $result .= $insertStr; } $parenthesesNeeded = isset($this->parenthesesListMap[$mapKey]) && in_array(get_class($arrItem), $this->parenthesesListMap[$mapKey], true); if ($parenthesesNeeded) { $result .= '('; } $result .= $this->printNodeFormatPreserving($arrItem, $originalTokens); if ($parenthesesNeeded) { $result .= ')'; } $tokenIndex = $itemEndPos + 1; } elseif ($diffType === DiffElem::TYPE_REMOVE) { if (!$origArrayItem instanceof Node) { return null; } /** @var int $itemStartPos */ $itemStartPos = $origArrayItem->getAttribute(Attribute::START_INDEX); /** @var int $itemEndPos */ $itemEndPos = $origArrayItem->getAttribute(Attribute::END_INDEX); if ($itemStartPos < 0 || $itemEndPos < 0) { throw new LogicException(); } if ($i === 0) { // If we're removing from the start, keep the tokens before the node and drop those after it, // instead of the other way around. $originalTokensArray = $originalTokens->getTokens(); for ($j = $tokenIndex; $j < $itemStartPos; $j++) { if ($originalTokensArray[$j][Lexer::TYPE_OFFSET] === Lexer::TOKEN_PHPDOC_EOL) { break; } $result .= $originalTokensArray[$j][Lexer::VALUE_OFFSET]; } } $tokenIndex = $itemEndPos + 1; } } if (count($delayedAdd) > 0) { if (!isset($this->emptyListInsertionMap[$mapKey])) { return null; } [$findToken, $extraLeft, $extraRight] = $this->emptyListInsertionMap[$mapKey]; if ($findToken !== null) { $originalTokensArray = $originalTokens->getTokens(); for (; $tokenIndex < count($originalTokensArray); $tokenIndex++) { $result .= $originalTokensArray[$tokenIndex][Lexer::VALUE_OFFSET]; if ($originalTokensArray[$tokenIndex][Lexer::VALUE_OFFSET] !== $findToken) { continue; } $tokenIndex++; break; } } $first = true; $result .= $extraLeft; foreach ($delayedAdd as $delayedAddNode) { if (!$first) { $result .= $insertStr; if ($insertNewline) { $result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent); } } $result .= $this->printNodeFormatPreserving($delayedAddNode, $originalTokens); $first = false; } $result .= $extraRight; } return $result; } /** * @param list<Comment> $comments */ private function printComments(array $comments, string $beforeAsteriskIndent, string $afterAsteriskIndent): string { $formattedComments = []; foreach ($comments as $comment) { $formattedComments[] = str_replace("\n", "\n" . $beforeAsteriskIndent . '*' . $afterAsteriskIndent, $comment->getReformattedText()); } return implode("\n$beforeAsteriskIndent*$afterAsteriskIndent", $formattedComments); } /** * @param array<Node|null> $nodes * @return array{bool, string, string} */ private function isMultiline(int $initialIndex, array $nodes, TokenIterator $originalTokens): array { $isMultiline = count($nodes) > 1; $pos = $initialIndex; $allText = ''; /** @var Node|null $node */ foreach ($nodes as $node) { if (!$node instanceof Node) { continue; } $endPos = $node->getAttribute(Attribute::END_INDEX) + 1; $text = $originalTokens->getContentBetween($pos, $endPos); $allText .= $text; if (strpos($text, "\n") === false) { // We require that a newline is present between *every* item. If the formatting // is inconsistent, with only some items having newlines, we don't consider it // as multiline $isMultiline = false; } $pos = $endPos; } $c = preg_match_all('~\n(?<before>[\\x09\\x20]*)\*(?<after>\\x20*)~', $allText, $matches, PREG_SET_ORDER); if ($c === 0) { return [$isMultiline, ' ', ' ']; } $before = ''; $after = ''; foreach ($matches as $match) { if (strlen($match['before']) > strlen($before)) { $before = $match['before']; } if (strlen($match['after']) <= strlen($after)) { continue; } $after = $match['after']; } $before = strlen($before) === 0 ? ' ' : $before; $after = strlen($after) === 0 ? ' ' : $after; return [$isMultiline, $before, $after]; } private function printNodeFormatPreserving(Node $node, TokenIterator $originalTokens): string { /** @var Node|null $originalNode */ $originalNode = $node->getAttribute(Attribute::ORIGINAL_NODE); if ($originalNode === null) { return $this->print($node); } $class = get_class($node); if ($class !== get_class($originalNode)) { throw new LogicException(); } $startPos = $originalNode->getAttribute(Attribute::START_INDEX); $endPos = $originalNode->getAttribute(Attribute::END_INDEX); if ($startPos < 0 || $endPos < 0) { throw new LogicException(); } $result = ''; $pos = $startPos; $subNodeNames = array_keys(get_object_vars($node)); foreach ($subNodeNames as $subNodeName) { $subNode = $node->$subNodeName; $origSubNode = $originalNode->$subNodeName; if ( (!$subNode instanceof Node && $subNode !== null) || (!$origSubNode instanceof Node && $origSubNode !== null) ) { if ($subNode === $origSubNode) { // Unchanged, can reuse old code continue; } if (is_array($subNode) && is_array($origSubNode)) { // Array subnode changed, we might be able to reconstruct it $listResult = $this->printArrayFormatPreserving( $subNode, $origSubNode, $originalTokens, $pos, $class, $subNodeName, ); if ($listResult === null) { return $this->print($node); } $result .= $listResult; continue; } return $this->print($node); } if ($origSubNode === null) { if ($subNode === null) { // Both null, nothing to do continue; } return $this->print($node); } $subStartPos = $origSubNode->getAttribute(Attribute::START_INDEX); $subEndPos = $origSubNode->getAttribute(Attribute::END_INDEX); if ($subStartPos < 0 || $subEndPos < 0) { throw new LogicException(); } if ($subEndPos < $subStartPos) { return $this->print($node); } if ($subNode === null) { return $this->print($node); } $result .= $originalTokens->getContentBetween($pos, $subStartPos); $mapKey = get_class($node) . '->' . $subNodeName; $parenthesesNeeded = isset($this->parenthesesMap[$mapKey]) && in_array(get_class($subNode), $this->parenthesesMap[$mapKey], true); if ($subNode->getAttribute(Attribute::ORIGINAL_NODE) !== null) { $parenthesesNeeded = $parenthesesNeeded && !in_array(get_class($subNode->getAttribute(Attribute::ORIGINAL_NODE)), $this->parenthesesMap[$mapKey], true); } $addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($subStartPos, $subEndPos); if ($addParentheses) { $result .= '('; } $result .= $this->printNodeFormatPreserving($subNode, $originalTokens); if ($addParentheses) { $result .= ')'; } $pos = $subEndPos + 1; } return $result . $originalTokens->getContentBetween($pos, $endPos + 1); } } phpdoc-parser/src/ParserConfig.php 0000644 00000001047 15213400535 0013175 0 ustar 00 <?php declare(strict_types = 1); namespace PHPStan\PhpDocParser; class ParserConfig { public bool $useLinesAttributes; public bool $useIndexAttributes; public bool $useCommentsAttributes; /** * @param array{lines?: bool, indexes?: bool, comments?: bool} $usedAttributes */ public function __construct(array $usedAttributes) { $this->useLinesAttributes = $usedAttributes['lines'] ?? false; $this->useIndexAttributes = $usedAttributes['indexes'] ?? false; $this->useCommentsAttributes = $usedAttributes['comments'] ?? false; } } phpdoc-parser/UPGRADING.md 0000644 00000013443 15213400535 0011160 0 ustar 00 Upgrading from phpstan/phpdoc-parser 1.x to 2.0 ================================= ### PHP version requirements phpstan/phpdoc-parser now requires PHP 7.4 or newer to run. ### Changed constructors of parser classes Instead of different arrays and boolean values passed into class constructors during setup, parser classes now share a common ParserConfig object. Before: ```php use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; $usedAttributes = ['lines' => true, 'indexes' => true]; $lexer = new Lexer(); $constExprParser = new ConstExprParser(true, true, $usedAttributes); $typeParser = new TypeParser($constExprParser, true, $usedAttributes); $phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes); ``` After: ```php use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; $config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]); $lexer = new Lexer($config); $constExprParser = new ConstExprParser($config); $typeParser = new TypeParser($config, $constExprParser); $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); ``` The point of ParserConfig is that over the course of phpstan/phpdoc-parser 2.x development series it's most likely going to gain new optional parameters akin to PHPStan's [bleeding edge](https://phpstan.org/blog/what-is-bleeding-edge). These parameters will allow opting in to new behaviour which will become the default in 3.0. With ParserConfig object, it's now going to be impossible to configure parser classes inconsistently. Which [happened to users](https://github.com/phpstan/phpdoc-parser/issues/251#issuecomment-2333927959) when they were separate boolean values. ### Support for parsing Doctrine annotations This parser now supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes representing Doctrine Annotations live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/2.0.x/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html). ### Whitespace before description is required phpdoc-parser 1.x sometimes silently consumed invalid part of a PHPDoc type as description: ```php /** @return \Closure(...int, string): string */ ``` This became `IdentifierTypeNode` of `\Closure` and with `(...int, string): string` as description. (Valid callable syntax is: `\Closure(int ...$u, string): string`.) Another example: ```php /** @return array{foo: int}} */ ``` The extra `}` also became description. Both of these examples are now InvalidTagValueNode. If these parts are supposed to be PHPDoc descriptions, you need to put whitespace between the type and the description text: ```php /** @return \Closure (...int, string): string */ /** @return array{foo: int} } */ ``` ### Type aliases with invalid types are preserved In phpdoc-parser 1.x, invalid type alias syntax was represented as [`InvalidTagValueNode`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.PhpDoc.InvalidTagValueNode.html), losing information about a type alias being present. ```php /** * @phpstan-type TypeAlias */ ``` This `@phpstan-type` is missing the actual type to alias. In phpdoc-parser 2.0 this is now represented as [`TypeAliasTagValueNode`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.PhpDoc.TypeAliasTagValueNode.html) (instead of `InvalidTagValueNode`) with [`InvalidTypeNode`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.Type.InvalidTypeNode.html) in place of the type. ### Removal of QuoteAwareConstExprStringNode The class [QuoteAwareConstExprStringNode](https://phpstan.github.io/phpdoc-parser/1.23.x/PHPStan.PhpDocParser.Ast.ConstExpr.QuoteAwareConstExprStringNode.html) has been removed. Instead, [ConstExprStringNode](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.ConstExpr.ConstExprStringNode.html) gained information about the kind of quotes being used. ### Removed 2nd parameter of `ConstExprParser::parse()` (`$trimStrings`) `ConstExprStringNode::$value` now contains unescaped values without surrounding `''` or `""` quotes. Use `ConstExprStringNode::__toString()` or [`Printer`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Printer.Printer.html) to get the escaped value along with surrounding quotes. ### Text between tags always belongs to description Multi-line descriptions between tags were previously represented as separate [PhpDocTextNode](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.PhpDoc.PhpDocTextNode.html): ```php /** * @param Foo $foo 1st multi world description * some text in the middle * @param Bar $bar 2nd multi world description */ ``` The line with `some text in the middle` in phpdoc-parser 2.0 is now part of the description of the first `@param` tag. ### `ArrayShapeNode` construction changes `ArrayShapeNode` constructor made private, added public static methods `createSealed()` and `createUnsealed()`. ### Minor BC breaks * Constructor parameter `$isEquality` in `AssertTag*ValueNode` made required * Constructor parameter `$templateTypes` in `MethodTagValueNode` made required * Constructor parameter `$isReference` in `ParamTagValueNode` made required * Constructor parameter `$isReference` in `TypelessParamTagValueNode` made required * Constructor parameter `$templateTypes` in `CallableTypeNode` made required * Constructor parameters `$expectedTokenValue` and `$currentTokenLine` in `ParserException` made required * `ArrayShapeItemNode` and `ObjectShapeItemNode` are not standalone TypeNode, just Node
| ver. 1.4 |
Github
|
.
| PHP 8.2.31 | Generation time: 1.1 |
proxy
|
phpinfo
|
Settings