Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9cd6143
gh-2706: Switch to PHPStan docblock parser
bart-jaskulski Oct 11, 2024
563d374
Misc fixes and improvements of phpstan parser integration
bart-jaskulski Oct 14, 2024
4c7cab9
Add support for rendering docblock with phpstan
bart-jaskulski Nov 1, 2024
bc446bc
Extract common functionality and provide "basic" strategy
bart-jaskulski Nov 1, 2024
6ad0438
Minor fixes in phpstan docblock parsing
bart-jaskulski Nov 1, 2024
f212fd6
Changes in tests for phpstan compatibility (breaks default docblock p…
bart-jaskulski Nov 1, 2024
c898ac8
Attempt to make UnusedImportProvider work with phpstan
bart-jaskulski Nov 1, 2024
4f7bc19
Fix unhandled match case
bart-jaskulski Nov 1, 2024
4b6ee72
Support parsing multiple docblocks from one text fragment
bart-jaskulski Nov 2, 2024
126fe57
Change expected test result for phpstan doc parser
bart-jaskulski Nov 6, 2024
041101e
Grouping with phpstan/doc-parser is not simple task
bart-jaskulski Nov 7, 2024
2e01a58
Remove trimming of content -- handled by parent template
bart-jaskulski Nov 7, 2024
724495e
Get rid of unused method
bart-jaskulski Nov 7, 2024
ad62112
Add property-read/write in phpdoc parsing
bart-jaskulski Nov 12, 2024
6d704cb
Restore native parser in tests
bart-jaskulski Nov 12, 2024
951bf7b
Move phpstan docblock usage flag to constructor
bart-jaskulski Nov 12, 2024
a7bd0e3
Add basic tests failing with phpstan parser
bart-jaskulski Nov 13, 2024
2d100c4
Fix false-positive unused import on array types
bart-jaskulski Nov 13, 2024
9f7d8d9
Rely on docblock type for return tag
bart-jaskulski Nov 13, 2024
54465e2
Add return type to keep phpstan happy
bart-jaskulski Nov 13, 2024
ba10ea5
Update phpstan/docblock-parser to v2
bart-jaskulski Nov 13, 2024
85f0d52
Update composer.lock file
bart-jaskulski Nov 13, 2024
deb6995
Control used phpdoc parser with config parameter
bart-jaskulski Nov 13, 2024
4f1ac6b
Change method visibility
bart-jaskulski Nov 13, 2024
109dd82
style: move public method higher in class hierarchy
bart-jaskulski Nov 14, 2024
087ede8
Add test for synthetic types usage
bart-jaskulski Nov 19, 2024
0096488
Merge remote-tracking branch 'upstream/master' into gh-2706
bart-jaskulski Nov 19, 2024
e5bae22
Add test for false-positive unused import
bart-jaskulski Nov 19, 2024
0f22d0f
Add missing test sources
bart-jaskulski Nov 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"jetbrains/phpstorm-stubs": "dev-master",
"phpactor/tolerant-php-parser": "dev-main",
"phpactor/map-resolver": "^1.5.0",
"webmozart/assert": "^1.11"
"webmozart/assert": "^1.11",
"phpstan/phpdoc-parser": "^2"
},
"require-dev": {
"dantleech/what-changed": "~0.4",
Expand Down
98 changes: 49 additions & 49 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public function provideExtractAccessor(): Generator
'get',
false,
];
yield 'synthetic types in phpdoc' => [
'generateAccessor9.test',
$propertyName,
'get',
false,
];
}

public function testNonProperty(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public function provideExtractMutator(): Generator
false,
true,
];
yield 'synthetic types' => [
'generateMutator10.test',
$propertyName,
];
}

public function testNonProperty(): void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// File: source
<?php

class generateMethod
{
/** @var non-empty-string */
pr<>ivate $method;
}
// File: expected
<?php

class generateMethod
{
/** @var non-empty-string */
private $method;

public function getmethod(): string
{
return $this->method;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// File: source

<?php

class generateMethod
{
/** @var non-empty-string */
pr<>ivate $method;
}
// File: expected
<?php

class generateMethod
{
/** @var non-empty-string */
private $method;

public function method(string $method): void
{
$this->method = $method;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Computes the intersection of arrays using a callback function on the keys for comparison

- **$array**: *array* <p> Initial array for comparison of the arrays. </p>
- **$array2**: *array* <p> First array to compare keys against. </p>
- **$key_compare_func**: *callable* <p> User supplied callback function to do the comparison. </p>
- ...$rest [optional]
- **$array**: *array* <p> Initial array for comparison of the arrays. </p>
- **$array2**: *array* <p> First array to compare keys against. </p>
- **$key_compare_func**: *callable* <p> User supplied callback function to do the comparison. </p>
- ...$rest [optional]

**Return** *array*: the values of array1 whose keys exist in all the arguments.
**Return** *array*: the values of array1 whose keys exist in all the arguments.

7 changes: 7 additions & 0 deletions lib/Extension/WorseReflection/WorseReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class WorseReflectionExtension implements Extension
const TAG_FRAME_WALKER = 'worse_reflection.frame_walker';
const TAG_MEMBER_PROVIDER = 'worse_reflection.member_provider';
const PARAM_ENABLE_CACHE = 'worse_reflection.enable_cache';
const PARAM_PHPDOC_PARSER = 'worse_reflection.phpdoc_parser';
const PARAM_STUB_DIR = 'worse_reflection.stub_dir';
const PARAM_STUB_CACHE_DIR = 'worse_reflection.cache_dir';
const PARAM_CACHE_LIFETIME = 'worse_reflection.cache_lifetime';
Expand All @@ -60,6 +61,7 @@ public function configure(Resolver $schema): void
$schema->setDefaults([
self::PARAM_IMPORT_GLOBALS => false,
self::PARAM_ENABLE_CACHE => true,
self::PARAM_PHPDOC_PARSER => 'native',
self::PARAM_CACHE_LIFETIME => 1.0,
self::PARAM_ENABLE_CONTEXT_LOCATION => true,
self::PARAM_STUB_CACHE_DIR => '%cache%/worse-reflection',
Expand All @@ -68,6 +70,7 @@ public function configure(Resolver $schema): void
]);
$schema->setDescriptions([
self::PARAM_ENABLE_CACHE => 'If reflection caching should be enabled',
self::PARAM_PHPDOC_PARSER => 'Which PHPDoc parser to use: "native" or "phpstan"',
self::PARAM_CACHE_LIFETIME => 'If caching is enabled, limit the amount of time a cache entry can stay alive',
self::PARAM_UNDEFINED_VAR_LEVENSHTEIN => 'Levenshtein distance to use when suggesting corrections for variable names',
self::PARAM_ENABLE_CONTEXT_LOCATION => <<<'EOT'
Expand Down Expand Up @@ -111,6 +114,10 @@ private function registerReflection(ContainerBuilder $container): void
$builder->withCacheForDocument($container->get(CacheForDocument::class));
}

if ($container->parameter(self::PARAM_PHPDOC_PARSER)->string() === 'phpstan') {
$builder->enablePHPStanDocblockParser();
}

foreach ($container->getServiceIdsForTag(self::TAG_SOURCE_LOCATOR) as $serviceId => $attrs) {
$builder->addLocator($container->get($serviceId), $attrs['priority'] ?? 0);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Phpactor\WorseReflection\Bridge\PHPStan\DocblockParser;

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\ParserConfig;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\ParserException;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use Phpactor\WorseReflection\Core\DocBlock\DocBlock;
use Phpactor\WorseReflection\Core\DocBlock\DocBlockFactory;
use Phpactor\WorseReflection\Core\DocBlock\PlainDocblock;
use Phpactor\WorseReflection\Core\Reflection\ReflectionScope;
use Phpactor\WorseReflection\Reflector;

class PHPStanDocblockParserFactory implements DocBlockFactory
{
private PhpDocParser $parser;

private Lexer $lexer;

public function __construct(
private Reflector $reflector,
?Lexer $lexer = null,
?PhpDocParser $parser = null,
) {
$config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]);
$this->lexer = $lexer ?? new Lexer($config);
$constExprParser = new ConstExprParser($config);
$typeParser = new TypeParser($config, $constExprParser);
$this->parser = $parser ?? new PhpDocParser($config, $typeParser, $constExprParser);
}

public function create(string $docblock, ReflectionScope $scope): DocBlock
{
$docblock = $this->sanitizeDocblock($docblock);
$node = new PhpDocNode([]);
try {
$tokens = $this->lexer->tokenize($docblock);
$docblockBeginnings = array_filter(
$tokens,
/** @param array{string, int, int} $token */
static fn (array $token) => $token[1] === Lexer::TOKEN_OPEN_PHPDOC
);
// _force_ phpstan to iterate all docblocks found in current fragment
foreach ($docblockBeginnings as $i => $docblockBeginning) {
$iterator = new TokenIterator($tokens, $i);
array_push($node->children, ...$this->parser->parse($iterator)->children);
}
} catch (ParserException) {
return new PlainDocblock($docblock);
}

return new PHPStanParsedDocblock(
$node,
new PHPStanTypeConverter($this->reflector, $scope),
$docblock
);
}

/**
* phpstan/docblock-parser is pretty strict about the doc it parses -- any short comments or
* excessive new lines lead to parser exception, so try to get rid of them before parsing.
*/
private function sanitizeDocblock(string $docblock): string
{
$docblock = preg_replace('~\h*\/\/.*~', '', $docblock) ?? $docblock;
return trim($docblock);
}
}
Loading