Skip to content

Commit b085a61

Browse files
committed
Second attempt to fix docblock descriptions
1 parent 88a07d2 commit b085a61

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ class AbstractPHPStanFactory implements Factory
4242

4343
public function __construct(PHPStanFactory ...$factories)
4444
{
45-
$this->lexer = new Lexer();
46-
$constParser = new ConstExprParser();
47-
$this->parser = new PhpDocParser(new TypeParser($constParser), $constParser);
45+
$this->lexer = new Lexer(true);
46+
$constParser = new ConstExprParser(true, true, ['lines' => true, 'indexes' => true]);
47+
$this->parser = new PhpDocParser(new TypeParser($constParser, true, ['lines' => true, 'indexes' => true]), $constParser, true, true, ['lines' => true, 'indexes' => true], true);
4848
$this->factories = $factories;
4949
}
5050

5151
public function create(string $tagLine, ?TypeContext $context = null): Tag
5252
{
53-
$tokens = new TokenIterator($this->lexer->tokenize($tagLine));
53+
$tokens = $this->tokenizeLine($tagLine);
5454
$ast = $this->parser->parseTag($tokens);
5555
if (property_exists($ast->value, 'description') === true) {
5656
$ast->value->setAttribute('description', $ast->value->description . $tokens->joinUntil(Lexer::TOKEN_END));
@@ -75,4 +75,29 @@ public function create(string $tagLine, ?TypeContext $context = null): Tag
7575
$ast->name
7676
);
7777
}
78+
79+
/**
80+
* Solve the issue with the lexer not tokenizing the line correctly
81+
*
82+
* This method is a workaround for the lexer that includes newline tokens with spaces. For
83+
* phpstan this isn't an issue, as it doesn't do a lot of things with the indentation of descriptions.
84+
* But for us is important to keep the indentation of the descriptions, so we need to fix the lexer output.
85+
*/
86+
private function tokenizeLine(string $tagLine): TokenIterator
87+
{
88+
$tokens = $this->lexer->tokenize($tagLine);
89+
$fixed = [];
90+
foreach ($tokens as $token) {
91+
if ($token[1] === Lexer::TOKEN_PHPDOC_EOL) {
92+
if (rtrim($token[0], " \t") !== $token[0]) {
93+
$fixed[] = [rtrim($token[Lexer::VALUE_OFFSET], " \t"), Lexer::TOKEN_PHPDOC_EOL, $token[2] ?? null];
94+
$fixed[] = [ltrim($token[Lexer::VALUE_OFFSET], "\n\r"), Lexer::TOKEN_HORIZONTAL_WS, ($token[2] ?? null) + 1];
95+
continue;
96+
}
97+
}
98+
99+
$fixed[] = $token;
100+
}
101+
return new TokenIterator($fixed);;
102+
}
78103
}

tests/integration/InterpretingDocBlocksTest.php

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ public function testRegressionWordpressDocblocks(): void
338338
false,
339339
new Description(
340340
'{' . "\n" .
341-
'Optional. Array or string of arguments for installing a package. Default empty array.' . "\n" .
341+
' Optional. Array or string of arguments for installing a package. Default empty array.' . "\n" .
342342
"\n" .
343343
' @type string $source Required path to the package source. Default empty.' . "\n" .
344344
' @type string $destination Required path to a folder to install the package in.' . "\n" .
@@ -364,4 +364,71 @@ public function testRegressionWordpressDocblocks(): void
364364
$docblock
365365
);
366366
}
367+
368+
public function testIndentationIsKept(): void
369+
{
370+
$docComment = <<<DOC
371+
/**
372+
* Registers the script module if no script module with that script module
373+
* identifier has already been registered.
374+
*
375+
* @since 6.5.0
376+
*
377+
* @param array \$deps {
378+
* Optional. List of dependencies.
379+
*
380+
* @type string|array ...$0 {
381+
* An array of script module identifiers of the dependencies of this script
382+
* module. The dependencies can be strings or arrays. If they are arrays,
383+
* they need an `id` key with the script module identifier, and can contain
384+
* an `import` key with either `static` or `dynamic`. By default,
385+
* dependencies that don't contain an `import` key are considered static.
386+
*
387+
* @type string \$id The script module identifier.
388+
* @type string \$import Optional. Import type. May be either `static` or
389+
* `dynamic`. Defaults to `static`.
390+
* }
391+
* }
392+
*/
393+
DOC;
394+
395+
$factory = DocBlockFactory::createInstance();
396+
$docblock = $factory->create($docComment);
397+
398+
self::assertEquals(
399+
new DocBlock(
400+
'Registers the script module if no script module with that script module
401+
identifier has already been registered.',
402+
new Description(
403+
''
404+
),
405+
[
406+
new Since('6.5.0', new Description('')),
407+
new Param(
408+
'deps',
409+
new Array_(new Mixed_()),
410+
false,
411+
new Description("{
412+
Optional. List of dependencies.
413+
414+
@type string|array ...$0 {
415+
An array of script module identifiers of the dependencies of this script
416+
module. The dependencies can be strings or arrays. If they are arrays,
417+
they need an `id` key with the script module identifier, and can contain
418+
an `import` key with either `static` or `dynamic`. By default,
419+
dependencies that don't contain an `import` key are considered static.
420+
421+
@type string \$id The script module identifier.
422+
@type string \$import Optional. Import type. May be either `static` or
423+
`dynamic`. Defaults to `static`.
424+
}
425+
}"
426+
)
427+
),
428+
],
429+
new Context('\\')
430+
),
431+
$docblock
432+
);
433+
}
367434
}

0 commit comments

Comments
 (0)