lexer = new Lexer(); $this->phpDocParser = new PhpDocParser(new TypeParser(), new ConstExprParser()); } /** * @dataProvider provideParamTagsData * @dataProvider provideVarTagsData * @dataProvider provideReturnTagsData * @dataProvider provideThrowsTagsData * @dataProvider provideDeprecatedTagsData * @dataProvider providePropertyTagsData * @dataProvider provideMethodTagsData * @dataProvider provideSingleLinePhpDocData * @dataProvider provideMultiLinePhpDocData * @dataProvider provideTemplateTagsData * @dataProvider provideExtendsTagsData * @dataProvider provideRealWorldExampleData * @param string $label * @param string $input * @param PhpDocNode $expectedPhpDocNode * @param int $nextTokenType */ public function testParse(string $label, string $input, PhpDocNode $expectedPhpDocNode, int $nextTokenType = Lexer::TOKEN_END): void { $tokens = new TokenIterator($this->lexer->tokenize($input)); $actualPhpDocNode = $this->phpDocParser->parse($tokens); $this->assertEquals($expectedPhpDocNode, $actualPhpDocNode, $label); $this->assertSame((string) $expectedPhpDocNode, (string) $actualPhpDocNode); $this->assertSame($nextTokenType, $tokens->currentTokenType()); } public function provideParamTagsData(): \Iterator { yield [ 'OK without description', '/** @param Foo $foo */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), false, '$foo', '' ) ), ]), ]; yield [ 'OK with description', '/** @param Foo $foo optional description */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), false, '$foo', 'optional description' ) ), ]), ]; yield [ 'OK variadic without description', '/** @param Foo ...$foo */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), true, '$foo', '' ) ), ]), ]; yield [ 'OK variadic with description', '/** @param Foo ...$foo optional description */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), true, '$foo', 'optional description' ) ), ]), ]; yield [ 'invalid without type, parameter name and description', '/** @param */', new PhpDocNode([ new PhpDocTagNode( '@param', new InvalidTagValueNode( '', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 11, Lexer::TOKEN_IDENTIFIER ) ) ), ]), ]; yield [ 'invalid without type and parameter name and with description (1)', '/** @param #desc */', new PhpDocNode([ new PhpDocTagNode( '@param', new InvalidTagValueNode( '#desc', new \PHPStan\PhpDocParser\Parser\ParserException( '#desc', Lexer::TOKEN_OTHER, 11, Lexer::TOKEN_IDENTIFIER ) ) ), ]), ]; yield [ 'invalid without type and parameter name and with description (2)', '/** @param (Foo */', new PhpDocNode([ new PhpDocTagNode( '@param', new InvalidTagValueNode( '(Foo', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 16, Lexer::TOKEN_CLOSE_PARENTHESES ) ) ), ]), ]; yield [ 'invalid with broken type (1)', '/** @param (Foo $foo */', new PhpDocNode([ new PhpDocTagNode( '@param', new InvalidTagValueNode( '(Foo $foo', new \PHPStan\PhpDocParser\Parser\ParserException( '$foo', Lexer::TOKEN_VARIABLE, 16, Lexer::TOKEN_CLOSE_PARENTHESES ) ) ), ]), ]; yield [ 'invalid with broken type (2)', '/** @param Foo * * @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In * Drupal 9 there will be no way to set the status and in Drupal 8 this * ability has been removed because mb_*() functions are supplied using * Symfony\'s polyfill. */', new PhpDocNode([ new PhpDocTextNode('Sample class'), new PhpDocTextNode(''), new PhpDocTagNode( '@author', new GenericTagValueNode('Foo Baz ') ), new PhpDocTextNode(''), new PhpDocTagNode( '@deprecated', new DeprecatedTagValueNode('in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In Drupal 9 there will be no way to set the status and in Drupal 8 this ability has been removed because mb_*() functions are supplied using Symfony\'s polyfill.') ), ]), ]; } public function provideMethodTagsData(): \Iterator { yield [ 'OK non-static, without return type', '/** @method foo() */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, null, 'foo', [], '' ) ), ]), ]; yield [ 'OK non-static, with return type', '/** @method Foo foo() */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [], '' ) ), ]), ]; yield [ 'OK non-static, with return static type', '/** @method static foo() */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('static'), 'foo', [], '' ) ), ]), ]; yield [ 'OK static, with return type', '/** @method static Foo foo() */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('Foo'), 'foo', [], '' ) ), ]), ]; yield [ 'OK static, with return static type', '/** @method static static foo() */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('static'), 'foo', [], '' ) ), ]), ]; yield [ 'OK non-static, with return type and description', '/** @method Foo foo() optional description */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [], 'optional description' ) ), ]), ]; yield [ 'OK non-static, with return type and single parameter without type', '/** @method Foo foo($a) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( null, false, false, '$a', null ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and single parameter with type', '/** @method Foo foo(A $a) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), false, false, '$a', null ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and single parameter with type that is passed by reference', '/** @method Foo foo(A &$a) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), true, false, '$a', null ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and single variadic parameter with type', '/** @method Foo foo(A ...$a) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), false, true, '$a', null ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and single variadic parameter with type that is passed by reference', '/** @method Foo foo(A &...$a) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), true, true, '$a', null ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and single parameter with default value', '/** @method Foo foo($a = 123) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( null, false, false, '$a', new ConstExprIntegerNode('123') ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and single variadic parameter with type that is passed by reference and default value', '/** @method Foo foo(A &...$a = 123) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), true, true, '$a', new ConstExprIntegerNode('123') ), ], '' ) ), ]), ]; yield [ 'OK non-static, with return type and multiple parameters without type', '/** @method Foo foo($a, $b, $c) */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'foo', [ new MethodTagValueParameterNode( null, false, false, '$a', null ), new MethodTagValueParameterNode( null, false, false, '$b', null ), new MethodTagValueParameterNode( null, false, false, '$c', null ), ], '' ) ), ]), ]; yield [ 'invalid non-static method without parentheses', '/** @method a b */', new PhpDocNode([ new PhpDocTagNode( '@method', new InvalidTagValueNode( 'a b', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 16, Lexer::TOKEN_OPEN_PARENTHESES ) ) ), ]), ]; yield [ 'invalid static method without parentheses', '/** @method static a b */', new PhpDocNode([ new PhpDocTagNode( '@method', new InvalidTagValueNode( 'static a b', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 23, Lexer::TOKEN_OPEN_PARENTHESES ) ) ), ]), ]; yield [ 'invalid non-static method without parameter name', '/** @method a b(x) */', new PhpDocNode([ new PhpDocTagNode( '@method', new InvalidTagValueNode( 'a b(x)', new \PHPStan\PhpDocParser\Parser\ParserException( ')', Lexer::TOKEN_CLOSE_PARENTHESES, 17, Lexer::TOKEN_VARIABLE ) ) ), ]), ]; } public function provideSingleLinePhpDocData(): \Iterator { yield [ 'empty', '/** */', new PhpDocNode([]), ]; yield [ 'edge-case', '/** /**/', new PhpDocNode([ new PhpDocTextNode( '/*' ), ]), ]; yield [ 'single text node', '/** text */', new PhpDocNode([ new PhpDocTextNode( 'text' ), ]), ]; yield [ 'single text node with tag in the middle', '/** text @foo bar */', new PhpDocNode([ new PhpDocTextNode( 'text @foo bar' ), ]), ]; yield [ 'single tag node without value', '/** @foo */', new PhpDocNode([ new PhpDocTagNode( '@foo', new GenericTagValueNode('') ), ]), ]; yield [ 'single tag node with value', '/** @foo lorem ipsum */', new PhpDocNode([ new PhpDocTagNode( '@foo', new GenericTagValueNode('lorem ipsum') ), ]), ]; yield [ 'single tag node with tag in the middle of value', '/** @foo lorem @bar ipsum */', new PhpDocNode([ new PhpDocTagNode( '@foo', new GenericTagValueNode('lorem @bar ipsum') ), ]), ]; yield [ 'single tag node without space between tag name and its value', '/** @varFoo $foo */', new PhpDocNode([ new PhpDocTagNode( '@varFoo', new GenericTagValueNode( '$foo' ) ), ]), ]; } public function provideMultiLinePhpDocData(): array { return [ [ 'multi-line with two tags', '/** * @param Foo $foo 1st multi world description * @param Bar $bar 2nd multi world description */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), false, '$foo', '1st multi world description' ) ), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Bar'), false, '$bar', '2nd multi world description' ) ), ]), ], [ 'multi-line with two tags and text in the middle', '/** * @param Foo $foo 1st multi world description * some text in the middle * @param Bar $bar 2nd multi world description */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), false, '$foo', '1st multi world description some text in the middle' ) ), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Bar'), false, '$bar', '2nd multi world description' ) ), ]), ], [ 'multi-line with two tags, text in the middle and some empty lines', '/** * * * @param Foo $foo 1st multi world description with empty lines * * * some text in the middle * * * @param Bar $bar 2nd multi world description with empty lines * * * test */', new PhpDocNode([ new PhpDocTextNode(''), new PhpDocTextNode(''), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Foo'), false, '$foo', '1st multi world description with empty lines' ) ), new PhpDocTextNode(''), new PhpDocTextNode(''), new PhpDocTextNode('some text in the middle'), new PhpDocTextNode(''), new PhpDocTextNode(''), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Bar'), false, '$bar', '2nd multi world description with empty lines' ) ), new PhpDocTextNode(''), new PhpDocTextNode(''), new PhpDocTextNode('test'), ]), ], [ 'multi-line with just empty lines', '/** * * */', new PhpDocNode([ new PhpDocTextNode(''), new PhpDocTextNode(''), ]), ], [ 'multi-line with tag mentioned as part of text node', '/** * Lets talk about @param * @param int $foo @param string $bar */', new PhpDocNode([ new PhpDocTextNode('Lets talk about @param'), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('int'), false, '$foo', '@param string $bar' ) ), ]), ], [ 'multi-line with a lot of @method tags', '/** * @method int getInteger(int $a, int $b) * @method void doSomething(int $a, $b) * @method self|Bar getFooOrBar() * @method methodWithNoReturnType() * @method static int getIntegerStatically(int $a, int $b) * @method static void doSomethingStatically(int $a, $b) * @method static self|Bar getFooOrBarStatically() * @method static methodWithNoReturnTypeStatically() * @method int getIntegerWithDescription(int $a, int $b) Get an integer with a description. * @method void doSomethingWithDescription(int $a, $b) Do something with a description. * @method self|Bar getFooOrBarWithDescription() Get a Foo or a Bar with a description. * @method methodWithNoReturnTypeWithDescription() Do something with a description but what, who knows! * @method static int getIntegerStaticallyWithDescription(int $a, int $b) Get an integer with a description statically. * @method static void doSomethingStaticallyWithDescription(int $a, $b) Do something with a description statically. * @method static self|Bar getFooOrBarStaticallyWithDescription() Get a Foo or a Bar with a description statically. * @method static methodWithNoReturnTypeStaticallyWithDescription() Do something with a description statically, but what, who knows! * @method static bool aStaticMethodThatHasAUniqueReturnTypeInThisClass() * @method static string aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription() A Description. * @method int getIntegerNoParams() * @method void doSomethingNoParams() * @method self|Bar getFooOrBarNoParams() * @method methodWithNoReturnTypeNoParams() * @method static int getIntegerStaticallyNoParams() * @method static void doSomethingStaticallyNoParams() * @method static self|Bar getFooOrBarStaticallyNoParams() * @method static methodWithNoReturnTypeStaticallyNoParams() * @method int getIntegerWithDescriptionNoParams() Get an integer with a description. * @method void doSomethingWithDescriptionNoParams() Do something with a description. * @method self|Bar getFooOrBarWithDescriptionNoParams() Get a Foo or a Bar with a description. * @method static int getIntegerStaticallyWithDescriptionNoParams() Get an integer with a description statically. * @method static void doSomethingStaticallyWithDescriptionNoParams() Do something with a description statically. * @method static self|Bar getFooOrBarStaticallyWithDescriptionNoParams() Get a Foo or a Bar with a description statically. * @method static bool|string aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams() * @method static string|float aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams() A Description. * @method \Aws\Result publish(array $args) * @method Image rotate(float & ... $angle = array(), $backgroundColor) * @method Foo overridenMethod() */', new PhpDocNode([ new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('int'), 'getInteger', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$b', null ), ], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('void'), 'doSomething', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( null, false, false, '$b', null ), ], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBar', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, null, 'methodWithNoReturnType', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('int'), 'getIntegerStatically', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$b', null ), ], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('void'), 'doSomethingStatically', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( null, false, false, '$b', null ), ], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarStatically', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStatically', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('int'), 'getIntegerWithDescription', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$b', null ), ], 'Get an integer with a description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('void'), 'doSomethingWithDescription', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( null, false, false, '$b', null ), ], 'Do something with a description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarWithDescription', [], 'Get a Foo or a Bar with a description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, null, 'methodWithNoReturnTypeWithDescription', [], 'Do something with a description but what, who knows!' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('int'), 'getIntegerStaticallyWithDescription', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$b', null ), ], 'Get an integer with a description statically.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('void'), 'doSomethingStaticallyWithDescription', [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), false, false, '$a', null ), new MethodTagValueParameterNode( null, false, false, '$b', null ), ], 'Do something with a description statically.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarStaticallyWithDescription', [], 'Get a Foo or a Bar with a description statically.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStaticallyWithDescription', [], 'Do something with a description statically, but what, who knows!' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('bool'), 'aStaticMethodThatHasAUniqueReturnTypeInThisClass', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('string'), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription', [], 'A Description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('int'), 'getIntegerNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('void'), 'doSomethingNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, null, 'methodWithNoReturnTypeNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('int'), 'getIntegerStaticallyNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('void'), 'doSomethingStaticallyNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarStaticallyNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStaticallyNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('int'), 'getIntegerWithDescriptionNoParams', [], 'Get an integer with a description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('void'), 'doSomethingWithDescriptionNoParams', [], 'Do something with a description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarWithDescriptionNoParams', [], 'Get a Foo or a Bar with a description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('int'), 'getIntegerStaticallyWithDescriptionNoParams', [], 'Get an integer with a description statically.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new IdentifierTypeNode('void'), 'doSomethingStaticallyWithDescriptionNoParams', [], 'Do something with a description statically.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new UnionTypeNode([ new IdentifierTypeNode('self'), new IdentifierTypeNode('Bar'), ]), 'getFooOrBarStaticallyWithDescriptionNoParams', [], 'Get a Foo or a Bar with a description statically.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new UnionTypeNode([ new IdentifierTypeNode('bool'), new IdentifierTypeNode('string'), ]), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams', [], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( true, new UnionTypeNode([ new IdentifierTypeNode('string'), new IdentifierTypeNode('float'), ]), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams', [], 'A Description.' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('\\Aws\\Result'), 'publish', [ new MethodTagValueParameterNode( new IdentifierTypeNode('array'), false, false, '$args', null ), ], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Image'), 'rotate', [ new MethodTagValueParameterNode( new IdentifierTypeNode('float'), true, true, '$angle', new ConstExprArrayNode([]) ), new MethodTagValueParameterNode( null, false, false, '$backgroundColor', null ), ], '' ) ), new PhpDocTagNode( '@method', new MethodTagValueNode( false, new IdentifierTypeNode('Foo'), 'overridenMethod', [], '' ) ), ]), ], ]; } public function provideTemplateTagsData(): \Iterator { yield [ 'OK without bound and description', '/** @template T */', new PhpDocNode([ new PhpDocTagNode( '@template', new TemplateTagValueNode( 'T', null, '' ) ), ]), ]; yield [ 'OK without bound', '/** @template T the value type*/', new PhpDocNode([ new PhpDocTagNode( '@template', new TemplateTagValueNode( 'T', null, 'the value type' ) ), ]), ]; yield [ 'OK without description', '/** @template T of DateTime */', new PhpDocNode([ new PhpDocTagNode( '@template', new TemplateTagValueNode( 'T', new IdentifierTypeNode('DateTime'), '' ) ), ]), ]; yield [ 'OK with bound and description', '/** @template T of DateTime the value type */', new PhpDocNode([ new PhpDocTagNode( '@template', new TemplateTagValueNode( 'T', new IdentifierTypeNode('DateTime'), 'the value type' ) ), ]), ]; yield [ 'invalid without bound and description', '/** @template */', new PhpDocNode([ new PhpDocTagNode( '@template', new InvalidTagValueNode( '', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 14, Lexer::TOKEN_IDENTIFIER ) ) ), ]), ]; yield [ 'invalid without bound and with description', '/** @template #desc */', new PhpDocNode([ new PhpDocTagNode( '@template', new InvalidTagValueNode( '#desc', new \PHPStan\PhpDocParser\Parser\ParserException( '#desc', Lexer::TOKEN_OTHER, 14, Lexer::TOKEN_IDENTIFIER ) ) ), ]), ]; } public function provideExtendsTagsData(): \Iterator { yield [ 'OK with one argument', '/** @extends Foo */', new PhpDocNode([ new PhpDocTagNode( '@extends', new ExtendsTagValueNode( new GenericTypeNode( new IdentifierTypeNode('Foo'), [ new IdentifierTypeNode('A'), ] ), '' ) ), ]), ]; yield [ 'OK with two arguments', '/** @extends Foo */', new PhpDocNode([ new PhpDocTagNode( '@extends', new ExtendsTagValueNode( new GenericTypeNode( new IdentifierTypeNode('Foo'), [ new IdentifierTypeNode('A'), new IdentifierTypeNode('B'), ] ), '' ) ), ]), ]; yield [ 'OK @implements', '/** @implements Foo */', new PhpDocNode([ new PhpDocTagNode( '@implements', new ImplementsTagValueNode( new GenericTypeNode( new IdentifierTypeNode('Foo'), [ new IdentifierTypeNode('A'), new IdentifierTypeNode('B'), ] ), '' ) ), ]), ]; yield [ 'OK @uses', '/** @uses Foo */', new PhpDocNode([ new PhpDocTagNode( '@uses', new UsesTagValueNode( new GenericTypeNode( new IdentifierTypeNode('Foo'), [ new IdentifierTypeNode('A'), new IdentifierTypeNode('B'), ] ), '' ) ), ]), ]; yield [ 'OK with description', '/** @extends Foo extends foo*/', new PhpDocNode([ new PhpDocTagNode( '@extends', new ExtendsTagValueNode( new GenericTypeNode( new IdentifierTypeNode('Foo'), [new IdentifierTypeNode('A')] ), 'extends foo' ) ), ]), ]; yield [ 'invalid without type', '/** @extends */', new PhpDocNode([ new PhpDocTagNode( '@extends', new InvalidTagValueNode( '', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 13, Lexer::TOKEN_IDENTIFIER ) ) ), ]), ]; yield [ 'invalid without arguments', '/** @extends Foo */', new PhpDocNode([ new PhpDocTagNode( '@extends', new InvalidTagValueNode( 'Foo', new \PHPStan\PhpDocParser\Parser\ParserException( '*/', Lexer::TOKEN_CLOSE_PHPDOC, 17, Lexer::TOKEN_OPEN_ANGLE_BRACKET ) ) ), ]), ]; yield [ 'class-string in @return', '/** @return class-string */', new PhpDocNode([ new PhpDocTagNode( '@return', new ReturnTagValueNode( new IdentifierTypeNode('class-string'), '' ) ), ]), ]; yield [ 'class-string in @return with description', '/** @return class-string some description */', new PhpDocNode([ new PhpDocTagNode( '@return', new ReturnTagValueNode( new IdentifierTypeNode('class-string'), 'some description' ) ), ]), ]; yield [ 'class-string in @param', '/** @param class-string $test */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('class-string'), false, '$test', '' ) ), ]), ]; yield [ 'class-string in @param with description', '/** @param class-string $test some description */', new PhpDocNode([ new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('class-string'), false, '$test', 'some description' ) ), ]), ]; } public function providerDebug(): \Iterator { $sample = '/** * Returns the schema for the field. * * This method is static because the field schema information is needed on * creation of the field. FieldItemInterface objects instantiated at that * time are not reliable as field settings might be missing. * * Computed fields having no schema should return an empty array. */'; yield [ 'OK class line', $sample, new PhpDocNode([ new PhpDocTextNode('Returns the schema for the field.'), new PhpDocTextNode(''), new PhpDocTextNode('This method is static because the field schema information is needed on creation of the field. FieldItemInterface objects instantiated at that time are not reliable as field settings might be missing.'), new PhpDocTextNode(''), new PhpDocTextNode('Computed fields having no schema should return an empty array.'), ]), ]; } public function provideRealWorldExampleData(): \Iterator { $sample = "/** * Returns the schema for the field. * * This method is static because the field schema information is needed on * creation of the field. FieldItemInterface objects instantiated at that * time are not reliable as field settings might be missing. * * Computed fields having no schema should return an empty array. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface \$field_definition * The field definition. * * @return array * An empty array if there is no schema, or an associative array with the * following key/value pairs: * - columns: An array of Schema API column specifications, keyed by column * name. The columns need to be a subset of the properties defined in * propertyDefinitions(). The 'not null' property is ignored if present, * as it is determined automatically by the storage controller depending * on the table layout and the property definitions. It is recommended to * avoid having the column definitions depend on field settings when * possible. No assumptions should be made on how storage engines * internally use the original column name to structure their storage. * - unique keys: (optional) An array of Schema API unique key definitions. * Only columns that appear in the 'columns' array are allowed. * - indexes: (optional) An array of Schema API index definitions. Only * columns that appear in the 'columns' array are allowed. Those indexes * will be used as default indexes. Field definitions can specify * additional indexes or, at their own risk, modify the default indexes * specified by the field-type module. Some storage engines might not * support indexes. * - foreign keys: (optional) An array of Schema API foreign key * definitions. Note, however, that the field data is not necessarily * stored in SQL. Also, the possible usage is limited, as you cannot * specify another field as related, only existing SQL tables, * such as {taxonomy_term_data}. */"; yield [ 'OK FieldItemInterface::schema', $sample, new PhpDocNode([ new PhpDocTextNode('Returns the schema for the field.'), new PhpDocTextNode(''), new PhpDocTextNode('This method is static because the field schema information is needed on creation of the field. FieldItemInterface objects instantiated at that time are not reliable as field settings might be missing.'), new PhpDocTextNode(''), new PhpDocTextNode('Computed fields having no schema should return an empty array.'), new PhpDocTextNode(''), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('\Drupal\Core\Field\FieldStorageDefinitionInterface'), false, '$field_definition', '' ) ), new PhpDocTextNode('The field definition.'), new PhpDocTextNode(''), new PhpDocTagNode( '@return', new ReturnTagValueNode( new IdentifierTypeNode('array'), '' ) ), new PhpDocTextNode('An empty array if there is no schema, or an associative array with the following key/value pairs:'), new PhpDocTextNode('- columns: An array of Schema API column specifications, keyed by column name. The columns need to be a subset of the properties defined in propertyDefinitions(). The \'not null\' property is ignored if present, as it is determined automatically by the storage controller depending on the table layout and the property definitions. It is recommended to avoid having the column definitions depend on field settings when possible. No assumptions should be made on how storage engines internally use the original column name to structure their storage.'), new PhpDocTextNode('- unique keys: (optional) An array of Schema API unique key definitions. Only columns that appear in the \'columns\' array are allowed.'), new PhpDocTextNode('- indexes: (optional) An array of Schema API index definitions. Only columns that appear in the \'columns\' array are allowed. Those indexes will be used as default indexes. Field definitions can specify additional indexes or, at their own risk, modify the default indexes specified by the field-type module. Some storage engines might not support indexes.'), new PhpDocTextNode('- foreign keys: (optional) An array of Schema API foreign key definitions. Note, however, that the field data is not necessarily stored in SQL. Also, the possible usage is limited, as you cannot specify another field as related, only existing SQL tables, such as {taxonomy_term_data}.'), ]), ]; $sample = '/** * Parses a chunked request and return relevant information. * * This function must return an array containing the following * keys and their corresponding values: * - last: Wheter this is the last chunk of the uploaded file * - uuid: A unique id which distinguishes two uploaded files * This uuid must stay the same among the task of * uploading a chunked file. * - index: A numerical representation of the currently uploaded * chunk. Must be higher that in the previous request. * - orig: The original file name. * * @param Request $request - The request object * * @return array */'; yield [ 'OK AbstractChunkedController::parseChunkedRequest', $sample, new PhpDocNode([ new PhpDocTextNode('Parses a chunked request and return relevant information.'), new PhpDocTextNode(''), new PhpDocTextNode('This function must return an array containing the following keys and their corresponding values:'), new PhpDocTextNode('- last: Wheter this is the last chunk of the uploaded file'), new PhpDocTextNode('- uuid: A unique id which distinguishes two uploaded files This uuid must stay the same among the task of uploading a chunked file.'), new PhpDocTextNode('- index: A numerical representation of the currently uploaded chunk. Must be higher that in the previous request.'), new PhpDocTextNode('- orig: The original file name.'), new PhpDocTextNode(''), new PhpDocTagNode( '@param', new ParamTagValueNode( new IdentifierTypeNode('Request'), false, '$request', '- The request object' ) ), new PhpDocTextNode(''), new PhpDocTagNode( '@return', new ReturnTagValueNode( new IdentifierTypeNode('array'), '' ) ), ]), ]; } }