This file contains important instructions for working on the phpdoc-parser project.
phpstan/phpdoc-parser is a library that represents PHPDocs with an AST (Abstract Syntax Tree). It supports parsing and modifying PHPDocs, and is primarily used by PHPStan for static analysis.
- PHPDoc Basics (list of PHPDoc tags)
- PHPDoc Types (list of PHPDoc types)
- phpdoc-parser API Reference with all the AST node types
- Parses PHPDoc comments into an AST representation
- Supports all PHPDoc tags and types
- Format-preserving printer for modifying and printing AST nodes (inspired by nikic/PHP-Parser)
- Support for Doctrine Annotations parsing
- Nullable, intersection, generic, and conditional types support
- PHP ^7.4 || ^8.0
- Platform target: PHP 7.4.6 (set in composer.json config)
Note: While phpstan-src is on PHP 8.1+ thanks to PHAR build downgrade, this repository still supports PHP 7.4+.
The source code is organized into the following main components:
-
Lexer (
src/Lexer/)Lexer.php- Tokenizes PHPDoc strings into tokens
-
Parser (
src/Parser/)PhpDocParser.php- Main PHPDoc parser (parses tags and structure)TypeParser.php- Parses PHPDoc type expressionsConstExprParser.php- Parses constant expressionsTokenIterator.php- Iterator for consuming tokensStringUnescaper.php- Handles string unescapingParserException.php- Exception handling for parse errors
-
AST (
src/Ast/)Node.php- Base AST node interfaceNodeAttributes.php- Trait for node attributes (lines, indexes, comments)NodeTraverser.php- Traverses and transforms AST treesNodeVisitor.php- Visitor interface for AST traversalAbstractNodeVisitor.php- Abstract base class for node visitorsAttribute.php- Attribute constants (START_LINE, END_LINE, START_INDEX, END_INDEX, ORIGINAL_NODE, COMMENTS)Comment.php- Represents a comment within a PHPDocType/- Type nodes (GenericTypeNode, ArrayTypeNode, UnionTypeNode, IntersectionTypeNode, CallableTypeNode, ConditionalTypeNode, ArrayShapeNode, ObjectShapeNode, OffsetAccessTypeNode, etc.)PhpDoc/- PHPDoc tag nodes (ParamTagValueNode, ReturnTagValueNode, VarTagValueNode, ThrowsTagValueNode, MethodTagValueNode, PropertyTagValueNode, TemplateTagValueNode, ExtendsTagValueNode, ImplementsTagValueNode, AssertTagValueNode, TypeAliasTagValueNode, SealedTagValueNode, etc.)PhpDoc/Doctrine/- Doctrine annotation AST nodes (DoctrineAnnotation, DoctrineArgument, DoctrineArray, DoctrineArrayItem, DoctrineTagValueNode)ConstExpr/- Constant expression nodes (ConstExprIntegerNode, ConstExprStringNode, ConstExprArrayNode, ConstFetchNode, etc.)NodeVisitor/- Built-in visitors (CloningVisitor)
-
Printer (
src/Printer/)Printer.php- Prints AST back to PHPDoc format (supports format-preserving printing)Differ.php- Computes differences between AST node listsDiffElem.php- Represents diff elements (keep, remove, add, replace)
-
Configuration
ParserConfig.php- Parser configuration (used attributes: lines, indexes, comments)
Tests mirror the source structure and include:
-
Parser Tests (
tests/PHPStan/Parser/)TypeParserTest.php- Type parsing testsPhpDocParserTest.php- PHPDoc parsing testsConstExprParserTest.php- Constant expression parsing testsTokenIteratorTest.php- Token iterator testsFuzzyTest.php- Fuzzy testingDoctrine/- Doctrine annotation test fixtures
-
AST Tests (
tests/PHPStan/Ast/)NodeTraverserTest.php- Node traversal testsAttributes/- AST attribute testsToString/- Tests for converting AST to stringNodeVisitor/- Visitor pattern tests
-
Printer Tests (
tests/PHPStan/Printer/)PrinterTest.php- Printer unit testsDifferTest.php- Differ algorithm testsIntegrationPrinterWithPhpParserTest.php- Integration tests with nikic/php-parser
phpunit.xml- PHPUnit test configurationphpstan.neon- PHPStan static analysis configuration (level 8)phpstan-baseline.neon- PHPStan baseline (known issues)phpcs.xml- PHP CodeSniffer configuration (referencesbuild-cs/phpcs.xml)composer.json- Dependencies and autoloading
The parsing flow follows these steps:
- Lexing:
Lexertokenizes the PHPDoc string into tokens - Parsing:
PhpDocParserusesTypeParserandConstExprParserto build an AST - Traversal/Modification:
NodeTraverserwithNodeVisitorcan traverse and modify the AST - Printing:
Printerconverts the AST back to PHPDoc format (optionally preserving formatting)
$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);For format-preserving printing (used when modifying existing PHPDocs), enable these attributes:
lines- Preserve line informationindexes- Preserve token indexescomments- Preserve comments
$config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true, 'comments' => true]);
// ... setup lexer, parsers as above ...
$cloningTraverser = new NodeTraverser([new CloningVisitor()]);
[$newPhpDocNode] = $cloningTraverser->traverse([$phpDocNode]);
// modify $newPhpDocNode...
$printer = new Printer();
$newPhpDoc = $printer->printFormatPreserving($newPhpDocNode, $phpDocNode, $tokens);composer install
make cs-installThe make cs-install step clones the phpstan/build-cs repository and installs its dependencies. This is required before running code style checks (make cs or make cs-fix).
- Create a new
*TagValueNodeclass insrc/Ast/PhpDoc/ - Add parsing logic in
PhpDocParser.php - Add tests in
tests/PHPStan/Parser/PhpDocParserTest.php - Run tests and PHPStan
- Create a new
*TypeNodeclass insrc/Ast/Type/ - Add parsing logic in
TypeParser.php - Add printing logic in
Printer.php - Add tests in
tests/PHPStan/Parser/TypeParserTest.php - Run tests and PHPStan
- Update token generation in
Lexer.php - Update parsers that consume those tokens
- Add/update tests
- Run comprehensive checks with
make check
Tests are run using PHPUnit:
make testsOr directly:
php vendor/bin/phpunitPHPStan static analysis is run with:
make phpstanOr directly:
php vendor/bin/phpstanTo run all quality checks (lint, code style, tests, and PHPStan):
make checkThis runs:
lint- PHP syntax checking with parallel-lintcs- Code style checking with phpcs (requiresmake cs-installfirst)tests- PHPUnit test suitephpstan- Static analysis
You MUST run both tests and PHPStan after every code change:
make tests && make phpstanOr use the comprehensive check:
make checkDO NOT commit or consider work complete until both tests and PHPStan pass successfully.
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
make cs-install- Clone and install phpstan/build-cs (required beforemake csormake cs-fix)make cs-fix- Automatically fix code style issuesmake lint- Check PHP syntax onlymake cs- Check code style onlymake phpstan-generate-baseline- Generate PHPStan baseline (use sparingly)
- Run
composer install(initial setup) - Run
make cs-install(initial setup, required for code style checks) - Make code changes
- Run
make tests- ensure all tests pass - Run
make phpstan- ensure static analysis passes - Fix any issues found
- Commit only when both pass
Remember: Tests and PHPStan MUST pass before any commit.
- Code style is enforced by phpcs using rules from phpstan/build-cs
- Uses tabs for indentation (tab-width 4)
- Run
make cs-fixto automatically fix code style issues - Always run
make csto verify code style before committing
- Project uses PHPStan level 8
- All code must pass static analysis
- Avoid adding to phpstan-baseline.neon unless absolutely necessary
- Type hints are required for all public APIs
- 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
- All AST nodes implement the
Nodeinterface - Nodes should be immutable where possible
- Use
__toString()for debugging output - Follow the visitor pattern for AST traversal
- Parsers should be recursive descent style
- Use
TokenIteratorfor token consumption - Throw
ParserExceptionfor syntax errors - Support optional attributes through
ParserConfig - Maintain backwards compatibility when adding features
- 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
- The parser is performance-critical (runs on large codebases)
- Avoid unnecessary object allocations
- Be careful with regex patterns
- Consider memory usage in loops