From f2a94a0da3fe2a3c49a9c247dd9e1e83e857c51e Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 20:36:18 +0000 Subject: [PATCH 01/63] Start again --- lib/DefaultValue.php | 40 ------- lib/Docblock.php | 42 ------- lib/DocblockException.php | 9 -- lib/DocblockFactory.php | 143 ----------------------- lib/DocblockType.php | 82 ------------- lib/DocblockTypes.php | 42 ------- lib/InheritTag.php | 11 -- lib/Method/Parameter.php | 46 -------- lib/Parser.php | 62 ---------- lib/Parser/MethodParser.php | 79 ------------- lib/Parser/ParameterParser.php | 81 ------------- lib/Parser/TypesParser.php | 53 --------- lib/Tag.php | 8 -- lib/Tag/AbstractTag.php | 0 lib/Tag/DeprecatedTag.php | 28 ----- lib/Tag/MethodTag.php | 66 ----------- lib/Tag/MixinTag.php | 29 ----- lib/Tag/ParamTag.php | 11 -- lib/Tag/PropertyTag.php | 37 ------ lib/Tag/ReturnTag.php | 29 ----- lib/Tag/VarTag.php | 40 ------- lib/Tags.php | 46 -------- tests/DocblockFactoryTest.php | 111 ------------------ tests/DocblockTest.php | 44 ------- tests/DocblockTypeTest.php | 18 --- tests/Parser/MethodParserTest.php | 169 --------------------------- tests/Parser/ParameterParserTest.php | 59 ---------- tests/Parser/TypesParserTest.php | 54 --------- tests/ParserTest.php | 140 ---------------------- tests/Tag/MethodTagTest.php | 17 --- tests/Tag/PropertyTagTest.php | 17 --- tests/Tag/VarTagTest.php | 19 --- tests/TagsTest.php | 31 ----- 33 files changed, 1663 deletions(-) delete mode 100644 lib/DefaultValue.php delete mode 100644 lib/Docblock.php delete mode 100644 lib/DocblockException.php delete mode 100644 lib/DocblockFactory.php delete mode 100644 lib/DocblockType.php delete mode 100644 lib/DocblockTypes.php delete mode 100644 lib/InheritTag.php delete mode 100644 lib/Method/Parameter.php delete mode 100644 lib/Parser.php delete mode 100644 lib/Parser/MethodParser.php delete mode 100644 lib/Parser/ParameterParser.php delete mode 100644 lib/Parser/TypesParser.php delete mode 100644 lib/Tag.php delete mode 100644 lib/Tag/AbstractTag.php delete mode 100644 lib/Tag/DeprecatedTag.php delete mode 100644 lib/Tag/MethodTag.php delete mode 100644 lib/Tag/MixinTag.php delete mode 100644 lib/Tag/ParamTag.php delete mode 100644 lib/Tag/PropertyTag.php delete mode 100644 lib/Tag/ReturnTag.php delete mode 100644 lib/Tag/VarTag.php delete mode 100644 lib/Tags.php delete mode 100644 tests/DocblockFactoryTest.php delete mode 100644 tests/DocblockTest.php delete mode 100755 tests/DocblockTypeTest.php delete mode 100644 tests/Parser/MethodParserTest.php delete mode 100644 tests/Parser/ParameterParserTest.php delete mode 100644 tests/Parser/TypesParserTest.php delete mode 100644 tests/ParserTest.php delete mode 100644 tests/Tag/MethodTagTest.php delete mode 100644 tests/Tag/PropertyTagTest.php delete mode 100644 tests/Tag/VarTagTest.php delete mode 100644 tests/TagsTest.php diff --git a/lib/DefaultValue.php b/lib/DefaultValue.php deleted file mode 100644 index 6e05838f..00000000 --- a/lib/DefaultValue.php +++ /dev/null @@ -1,40 +0,0 @@ -value = $value; - $this->none = $none; - } - - public function isDefined(): bool - { - return $this->value !== null; - } - - public static function none() - { - return new self(null, true); - } - - public static function ofValue($value) - { - return new self($value); - } - - public function value() - { - return $this->value; - } -} diff --git a/lib/Docblock.php b/lib/Docblock.php deleted file mode 100644 index d2a47267..00000000 --- a/lib/Docblock.php +++ /dev/null @@ -1,42 +0,0 @@ -tags = $tags; - $this->prose = $prose; - } - - public static function fromTags(array $tags) - { - return new self(Tags::fromArray($tags)); - } - - public static function fromProseAndTags(string $prose, array $tags) - { - return new self(Tags::fromArray($tags), $prose); - } - - public function tags(): Tags - { - return $this->tags; - } - - public function prose(): string - { - return $this->prose; - } -} diff --git a/lib/DocblockException.php b/lib/DocblockException.php deleted file mode 100644 index 521f50e7..00000000 --- a/lib/DocblockException.php +++ /dev/null @@ -1,9 +0,0 @@ -parser = $parser ?: new Parser(); - $this->typesParser = $typesParser ?: new TypesParser(); - $this->methodParser = $methodParser ?: new MethodParser($this->typesParser); - } - - public function create(string $docblock): Docblock - { - $tags = []; - list($prose, $tagData) = $this->parser->parse($docblock); - foreach ($tagData as $tagName => $metadatas) { - foreach ($metadatas as $metadata) { - switch (strtolower(trim($tagName))) { - case 'var': - $tags[] = $this->createVarTag($metadata); - break; - case 'param': - $tags[] = $this->createParamTag($metadata); - break; - case 'method': - $tags[] = $this->createMethodTag($metadata); - break; - case 'mixin': - $tags[] = $this->createMixinTag($metadata); - break; - case 'property': - $tags[] = $this->createPropertyTag($metadata); - break; - case 'deprecated': - $tags[] = $this->createDeprecatedTag($metadata); - break; - case 'return': - $tags[] = $this->createReturnTag($metadata); - break; - case 'inheritdoc': - $tags[] = new InheritTag(); - } - } - } - - return Docblock::fromProseAndTags(implode(PHP_EOL, $prose), array_filter( - $tags, - function (Tag $tag) { - return $tag !== null; - } - )); - } - - private function createVarTag(array $metadata): VarTag - { - if (null === $types = array_shift($metadata)) { - $types = ''; - } - - $varName = array_shift($metadata); - - return new VarTag($this->typesParser->parseTypes($types), $varName); - } - - private function createParamTag(array $metadata): ParamTag - { - if (null === $types = array_shift($metadata)) { - $types = ''; - } - - $varName = array_shift($metadata); - - return new ParamTag($this->typesParser->parseTypes($types), $varName); - } - - private function createMethodTag(array $metadata): MethodTag - { - return $this->methodParser->parseMethod($metadata); - } - - private function createReturnTag(array $metadata): ReturnTag - { - if (null === $types = array_shift($metadata)) { - $types = ''; - } - - $methodName = array_shift($metadata); - - return new ReturnTag($this->typesParser->parseTypes($types)); - } - - private function createPropertyTag($metadata) - { - if (null === $types = array_shift($metadata)) { - $types = ''; - } - - $propertyName = array_shift($metadata); - - return new PropertyTag($this->typesParser->parseTypes($types), ltrim($propertyName, '$')); - } - - private function createDeprecatedTag(array $metadata): DeprecatedTag - { - return new DeprecatedTag(implode(' ', $metadata)); - } - - private function createMixinTag(array $metadata): ?MixinTag - { - $fqn = array_shift($metadata); - if (null === $fqn) { - return null; - } - return new MixinTag($fqn); - } -} diff --git a/lib/DocblockType.php b/lib/DocblockType.php deleted file mode 100644 index 30e867f0..00000000 --- a/lib/DocblockType.php +++ /dev/null @@ -1,82 +0,0 @@ -type = $type; - $this->iteratedType = $iteratedType; - } - - public static function collectionOf(string $type, string $iteratedType): DocblockType - { - return new self($type, $iteratedType); - } - - public static function of(string $type): DocblockType - { - return new self($type); - } - - public static function fullyQualifiedNameOf(string $string): self - { - $type = static::of($string); - $type->isFullyQualified = true; - - return $type; - } - - public static function arrayOf(string $type): DocblockType - { - return new self('array', $type); - } - - /** - * @return string|null - */ - public function iteratedType() - { - return $this->iteratedType; - } - - public function isArray(): bool - { - return $this->type === 'array'; - } - - public function isCollection(): bool - { - return $this->iteratedType && $this->type !== 'array'; - } - - public function __toString() - { - if ($this->isFullyQualified) { - return '\\' . $this->type; - } - - return $this->type; - } - - public function isFullyQualified(): bool - { - return $this->isFullyQualified; - } -} diff --git a/lib/DocblockTypes.php b/lib/DocblockTypes.php deleted file mode 100644 index 343c7abc..00000000 --- a/lib/DocblockTypes.php +++ /dev/null @@ -1,42 +0,0 @@ -add($item); - } - } - - public static function empty(): DocblockTypes - { - return new self([]); - } - - public static function fromStringTypes($types) - { - return new self(array_map(function (string $type) { - return DocblockType::of($type); - }, $types)); - } - - public static function fromDocblockTypes(array $docblocktypes): DocblockTypes - { - return new self($docblocktypes); - } - - public function getIterator() - { - return new \ArrayIterator($this->docblocktypes); - } - - private function add(DocblockType $item) - { - $this->docblocktypes[] = $item; - } -} diff --git a/lib/InheritTag.php b/lib/InheritTag.php deleted file mode 100644 index 652ac6aa..00000000 --- a/lib/InheritTag.php +++ /dev/null @@ -1,11 +0,0 @@ -name = $name; - $this->types = $types ?: DocblockTypes::empty(); - $this->defaultValue = $defaultValue ?: DefaultValue::none(); - } - - public function name(): string - { - return $this->name; - } - - public function types(): DocblockTypes - { - return $this->types; - } - - public function defaultValue(): DefaultValue - { - return $this->defaultValue; - } -} diff --git a/lib/Parser.php b/lib/Parser.php deleted file mode 100644 index 64c94885..00000000 --- a/lib/Parser.php +++ /dev/null @@ -1,62 +0,0 @@ -?\[\\\\\]|\w\s]+)?}'; - - public function parse($docblock): array - { - $lines = explode(PHP_EOL, $docblock); - $tags = []; - $prose = []; - - foreach ($lines as $line) { - if (0 === preg_match(self::TAG, $line, $matches)) { - if (null !== $line = $this->extractProse($line)) { - $prose[] = $line; - } - continue; - } - - $tagName = $matches[1]; - - if (!isset($tags[$tagName])) { - $tags[$tagName] = []; - } - - $metadata = array_values(array_filter(explode(' ', trim($matches[2] ?? '')))); - $tags[$tagName][] = $metadata; - } - - return [$prose, $tags ]; - } - - private function extractProse(string $line) - { - $line = trim($line); - - if (empty($line)) { - return; - } - - if ($line == '/**') { - return; - } - - if ($line == '*') { - return ''; - } - - if (substr($line, 0, 2) == '* ') { - $line = substr($line, 2); - } - - if (substr($line, 0, 2) == '*/') { - return; - } - - return $line; - } -} diff --git a/lib/Parser/MethodParser.php b/lib/Parser/MethodParser.php deleted file mode 100644 index ea0fb5fc..00000000 --- a/lib/Parser/MethodParser.php +++ /dev/null @@ -1,79 +0,0 @@ -typesParser = $typesParser ?: new TypesParser(); - $this->parameterParser = $parameterParser ?: new ParameterParser($typesParser); - } - - public function parseMethod(array $parts): MethodTag - { - $method = implode(' ', $parts); - - list($static, $types, $methodName, $parameters) = $this->methodInfo($method, $parts); - - return new MethodTag( - $this->typesParser->parseTypes($types), - $methodName, - $parameters, - $static - ); - } - - private function methodInfo(string $method, array $parts): array - { - if (empty($method)) { - return [ false , '', $method, [] ]; - } - - if (substr($method, -1) !== ')') { - $method .= '()'; - } - - if (preg_match('{(static)?\s*([\w\\\]+)?\s+(\w*?)\s*\((.*)\)}', $method, $parts)) { - $static = $parts[1]; - $types = $parts[2]; - $methodName = $parts[3]; - $paramString = $parts[4]; - - return [ - $static === 'static', - $types, - $methodName, - $this->parseParameters($paramString), - ]; - } - - return [ false, '', $method, [] ]; - } - - private function parseParameters(string $paramString): array - { - $parameters = array_map(function (string $param) { - return trim($param); - }, explode(', ', $paramString)); - - $parameters = array_filter(array_map(function (string $paramString) { - return $this->parameterParser->parse($paramString); - }, $parameters)); - - return $parameters; - } -} diff --git a/lib/Parser/ParameterParser.php b/lib/Parser/ParameterParser.php deleted file mode 100644 index f7157b71..00000000 --- a/lib/Parser/ParameterParser.php +++ /dev/null @@ -1,81 +0,0 @@ -typesParser = $typesParser ?: new TypesParser(); - } - - /** - * @return Parameter|null - */ - public function parse(string $parameterString) - { - list($parameterName, $types, $defaultValue) = $this->extractParts($parameterString); - - if (!$parameterName) { - return null; - } - - return new Parameter($parameterName, $types, $defaultValue); - } - - private function extractParts(string $parameterString) - { - $parts = array_map('trim', explode(' ', $parameterString)); - - $types = DocblockTypes::empty(); - $parameterName = null; - $defaultValue = null; - - foreach ($parts as $index => $part) { - if (substr($part, 0, 1) === '$') { - $parameterName = substr($part, 1); - continue; - } - - if (substr($part, 0, 3) === '...') { - $parameterName = substr($part, 4); - continue; - } - - if ($index === 0) { - $types = $this->typesParser->parseTypes($part); - continue; - } - - if ($part === '=' && isset($parts[$index + 1])) { - $defaultValue = $this->parseDefaultValue($parts[$index + 1]); - break; - } - } - - return [$parameterName, $types, $defaultValue]; - } - - private function parseDefaultValue(string $defaultValueString): DefaultValue - { - if (is_numeric($defaultValueString)) { - // hack to cast to either a float or an int - return DefaultValue::ofValue($defaultValueString + 0); - } - - if (in_array(substr($defaultValueString, 0, 1), ['"', '\''])) { - return DefaultValue::ofValue(trim($defaultValueString, '"\'')); - } - - return DefaultValue::none(); - } -} diff --git a/lib/Parser/TypesParser.php b/lib/Parser/TypesParser.php deleted file mode 100644 index bc7cdd61..00000000 --- a/lib/Parser/TypesParser.php +++ /dev/null @@ -1,53 +0,0 @@ -$}', $type, $matches)) { - $type = $matches[1]; - $collectionType = trim($matches[2], self::MASK_WHITESPACE_AND_IGNORED_PREFIX); - $docblockTypes[] = DocblockType::collectionOf($type, $collectionType); - continue; - } - - if (substr($type, -2) == '[]') { - $type = trim(substr($type, 0, -2), self::MASK_WHITESPACE_AND_IGNORED_PREFIX); - $docblockTypes[] = DocblockType::arrayOf($type); - continue; - } - - if (substr($type, 0, 1) === '\\') { - $docblockTypes[] = DocblockType::fullyQualifiedNameOf(substr($type, 1)); - continue; - } - - $docblockTypes[] = DocblockType::of($type); - } - - return DocblockTypes::fromDocblockTypes($docblockTypes); - } -} diff --git a/lib/Tag.php b/lib/Tag.php deleted file mode 100644 index 729ffd54..00000000 --- a/lib/Tag.php +++ /dev/null @@ -1,8 +0,0 @@ -message = $message; - } - - public function name() - { - return 'deprecated'; - } - - public function message(): ?string - { - return $this->message; - } -} diff --git a/lib/Tag/MethodTag.php b/lib/Tag/MethodTag.php deleted file mode 100644 index 8656f3ba..00000000 --- a/lib/Tag/MethodTag.php +++ /dev/null @@ -1,66 +0,0 @@ -types = $types; - $this->methodName = $methodName; - $this->parameters = $parameters; - $this->isStatic = $isStatic; - } - - public function name() - { - return 'method'; - } - - public function types(): DocblockTypes - { - return $this->types; - } - - public function methodName() - { - return $this->methodName; - } - - public function parameters(): array - { - return $this->parameters; - } - - public function isStatic(): bool - { - return $this->isStatic; - } -} diff --git a/lib/Tag/MixinTag.php b/lib/Tag/MixinTag.php deleted file mode 100644 index 5247759b..00000000 --- a/lib/Tag/MixinTag.php +++ /dev/null @@ -1,29 +0,0 @@ -fqn = $fqn; - } - - public function fqn(): string - { - return $this->fqn; - } - - public function name(): string - { - return 'mixin'; - } -} - diff --git a/lib/Tag/ParamTag.php b/lib/Tag/ParamTag.php deleted file mode 100644 index e18de696..00000000 --- a/lib/Tag/ParamTag.php +++ /dev/null @@ -1,11 +0,0 @@ -types = $types; - $this->propertyName = $propertyName; - } - - public function name() - { - return 'property'; - } - - public function propertyName() - { - return $this->propertyName; - } - - public function types(): DocblockTypes - { - return $this->types; - } -} diff --git a/lib/Tag/ReturnTag.php b/lib/Tag/ReturnTag.php deleted file mode 100644 index 680cc62b..00000000 --- a/lib/Tag/ReturnTag.php +++ /dev/null @@ -1,29 +0,0 @@ -types = $types; - } - - public function name() - { - return 'return'; - } - - public function types(): DocblockTypes - { - return $this->types; - } -} diff --git a/lib/Tag/VarTag.php b/lib/Tag/VarTag.php deleted file mode 100644 index c41a96db..00000000 --- a/lib/Tag/VarTag.php +++ /dev/null @@ -1,40 +0,0 @@ -types = $types; - $this->varName = $varName; - } - - public function types(): DocblockTypes - { - return $this->types; - } - - public function varName() - { - return $this->varName; - } -} diff --git a/lib/Tags.php b/lib/Tags.php deleted file mode 100644 index e9535838..00000000 --- a/lib/Tags.php +++ /dev/null @@ -1,46 +0,0 @@ -add($item); - } - } - - public static function fromArray(array $tags): Tags - { - return new self($tags); - } - - public function getIterator() - { - return new \ArrayIterator($this->tags); - } - - public function byName(string $name): Tags - { - $name = strtolower($name); - return new self(array_filter($this->tags, function (Tag $tag) use ($name) { - return $name == strtolower($tag->name()); - })); - } - - /** - * {@inheritDoc} - */ - public function count() - { - return count($this->tags); - } - - private function add(Tag $item) - { - $this->tags[] = $item; - } -} diff --git a/tests/DocblockFactoryTest.php b/tests/DocblockFactoryTest.php deleted file mode 100644 index c893836c..00000000 --- a/tests/DocblockFactoryTest.php +++ /dev/null @@ -1,111 +0,0 @@ -create($docblock); - $this->assertEquals($expected->tags(), $docblock->tags()); - } - - public function provideCreate() - { - return [ - 'no tags' => [ - '/** */', - Docblock::fromTags([]), - ], - 'var single type' => [ - '** @var Foobar */', - Docblock::fromTags([ new VarTag(DocblockTypes::fromStringTypes([ 'Foobar' ])), ]), - ], - 'var multiple types' => [ - '/** @var Foobar|string|null */', - Docblock::fromTags([ new VarTag(DocblockTypes::fromStringTypes([ 'Foobar', 'string', 'null' ])) ]), - ], - 'var union types' => [ - '/** @var Foobar&string */', - Docblock::fromTags([ new VarTag(DocblockTypes::fromStringTypes([ 'Foobar', 'string' ])) ]), - ], - 'param single type' => [ - '/** @param Foobar $foobar */', - Docblock::fromTags([ new ParamTag(DocblockTypes::fromStringTypes([ 'Foobar' ]), '$foobar') ]), - ], - 'method single type' => [ - '/** @method Foobar foobar() */', - Docblock::fromTags([ new MethodTag(DocblockTypes::fromStringTypes([ 'Foobar' ]), 'foobar') ]), - ], - 'property' => [ - '/** @property string $foo */', - Docblock::fromTags([ new PropertyTag(DocblockTypes::fromStringTypes(['string']), 'foo') ]), - ], - - 'property no type' => [ - '/** @property Foo */', - Docblock::fromTags([ new PropertyTag(DocblockTypes::fromStringTypes(['Foo']), '') ]), - ], - - 'property with nothing' => [ - '/** @property */', - Docblock::fromTags([ new PropertyTag(DocblockTypes::empty(), '') ]), - ], - - 'return single type' => [ - '/** @return Foobar foobar() */', - Docblock::fromTags([ new ReturnTag(DocblockTypes::fromStringTypes([ 'Foobar' ])) ]), - ], - 'return single nullable type' => [ - '/** @return ?Foobar foobar() */', - Docblock::fromTags([ new ReturnTag(DocblockTypes::fromStringTypes([ 'Foobar' ])) ]), - ], - 'inheritdoc' => [ - '/** {@inheritDoc} */', - Docblock::fromTags([ new InheritTag() ]), - ], - - 'var no type' => [ - '/** @var */', - Docblock::fromTags([ new VarTag(DocblockTypes::empty()) ]), - ], - 'param no type' => [ - '/** @param */', - Docblock::fromTags([ new ParamTag(DocblockTypes::empty(), '') ]), - ], - 'method no type' => [ - '/** @method */', - Docblock::fromTags([ new MethodTag(DocblockTypes::empty(), '') ]), - ], - 'return no type' => [ - '/** @return */', - Docblock::fromTags([ new ReturnTag(DocblockTypes::empty()) ]), - ], - 'deprecated' => [ - '/** @deprecated This is deprecated */', - Docblock::fromTags([ new DeprecatedTag('This is deprecated') ]), - ], - 'mixin' => [ - '/** @mixin Foobar\\Barfoo */', - Docblock::fromTags([ new MixinTag('Foobar\\Barfoo') ]), - ], - ]; - } -} diff --git a/tests/DocblockTest.php b/tests/DocblockTest.php deleted file mode 100644 index 83a888ea..00000000 --- a/tests/DocblockTest.php +++ /dev/null @@ -1,44 +0,0 @@ -tag1 = $this->prophesize(Tag::class); - } - - public function testFromTags() - { - $docblock = Docblock::fromTags([ - $this->tag1->reveal() - ]); - - $this->assertEquals(Tags::fromArray([$this->tag1->reveal()]), $docblock->tags()); - } - - public function testFromProseAndTags() - { - $docblock = Docblock::fromProseAndTags( - 'Hello this is prose', - [ - $this->tag1->reveal() - ] - ); - - $this->assertEquals(Tags::fromArray([$this->tag1->reveal()]), $docblock->tags()); - $this->assertEquals('Hello this is prose', $docblock->prose()); - } -} diff --git a/tests/DocblockTypeTest.php b/tests/DocblockTypeTest.php deleted file mode 100755 index e46f7112..00000000 --- a/tests/DocblockTypeTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue($type->isCollection()); - $this->assertFalse($type->isArray()); - $this->assertEquals('Foobar', $type->__toString()); - $this->assertEquals('Item', $type->iteratedType()); - } -} diff --git a/tests/Parser/MethodParserTest.php b/tests/Parser/MethodParserTest.php deleted file mode 100644 index bafc44ce..00000000 --- a/tests/Parser/MethodParserTest.php +++ /dev/null @@ -1,169 +0,0 @@ -assertEquals($expected, $parser->parseMethod($parts)); - } - - public function provideCreate() - { - return [ - 'no parts' => [ - [ ], - new MethodTag(DocblockTypes::empty(), ''), - ], - //'type only parts' => [ - // [ 'Foobar' ], - // new MethodTag(DocblockTypes::fromStringTypes(['Foobar']), ''), - //], - 'no parenthesis' => [ - [ 'Foobar', 'foobar' ], - new MethodTag(DocblockTypes::fromStringTypes([ 'Foobar' ]), 'foobar'), - ], - 'single type' => [ - [ 'Foobar', 'foobar()' ], - new MethodTag(DocblockTypes::fromStringTypes([ 'Foobar' ]), 'foobar'), - ], - 'with parameters' => [ - [ 'Foobar', 'foobar(Foobar $foobar, string $foo, $bar)' ], - new MethodTag( - DocblockTypes::fromStringTypes([ 'Foobar' ]), - 'foobar', - [ - new Parameter('foobar', DocblockTypes::fromDocblockTypes([ DocblockType::of('Foobar') ])), - new Parameter('foo', DocblockTypes::fromDocblockTypes([ DocblockType::of('string') ])), - new Parameter('bar', DocblockTypes::fromDocblockTypes([ ])), - ] - ), - ], - 'with parameters and default value' => [ - [ 'Foobar', 'foobar(string $foo = "hello", $bar)' ], - new MethodTag( - DocblockTypes::fromStringTypes([ 'Foobar' ]), - 'foobar', - [ - new Parameter( - 'foo', - DocblockTypes::fromDocblockTypes([ DocblockType::of('string') ]), - DefaultValue::ofValue('hello') - ), - new Parameter('bar', DocblockTypes::fromDocblockTypes([ ])), - ] - ), - ], - 'static method' => [ - [ 'static', 'Foobar', 'foobar()' ], - new MethodTag( - DocblockTypes::fromStringTypes([ 'Foobar' ]), - 'foobar', - [], - true - ), - ], - 'phpspec be constructed with' => [ - [ 'void', 'foobar(...$arguments)' ], - new MethodTag( - DocblockTypes::fromStringTypes(['void']), - 'foobar', - [ - new Parameter( - 'arguments', - DocblockTypes::fromDocblockTypes([]) - ), - ] - ), - ], - 'phpspec be constructed through' => [ - [ 'void', 'beConstructedThrough($factoryMethod, array $constructorArguments = array())' ], - new MethodTag( - DocblockTypes::fromStringTypes(['void']), - 'beConstructedThrough', - [ - new Parameter( - 'factoryMethod', - DocblockTypes::fromDocblockTypes([]) - ), - new Parameter( - 'constructorArguments', - DocblockTypes::fromDocblockTypes([ DocblockType::of('array') ]), - DefaultValue::none() - ), - ] - ), - ], - 'phpspec should have count' => [ - [ 'void', 'shouldHaveCount($count)' ], - new MethodTag( - DocblockTypes::fromStringTypes(['void']), - 'shouldHaveCount', - [ - new Parameter( - 'count', - DocblockTypes::empty() - ), - ] - ), - - ], - 'laravel db' => [ - [ 'static', '\\Illuminate\\Database\\Query\\Builder', 'table(string $table)' ], - new MethodTag( - DocblockTypes::fromDocblockTypes([DocblockType::fullyQualifiedNameOf('Illuminate\\Database\\Query\\Builder')]), - 'table', - [ - new Parameter( - 'table', - DocblockTypes::fromStringTypes(['string']) - ), - ], - true - ), - - ], - 'laravel route' => [ - [ - 'static', - '\\Illuminate\\Routing\\Route', - 'get(string $uri, \\Closure|array|string|callable|null $action = null)' - ], - new MethodTag( - DocblockTypes::fromDocblockTypes([DocblockType::fullyQualifiedNameOf('Illuminate\\Routing\\Route')]), - 'get', - [ - new Parameter( - 'uri', - DocblockTypes::fromStringTypes(['string']) - ), - new Parameter( - 'action', - DocblockTypes::fromDocblockTypes([ - DocblockType::fullyQualifiedNameOf('Closure'), - DocblockType::of('array'), - DocblockType::of('string'), - DocblockType::of('callable'), - DocblockType::of('null'), - ]) - ), - ], - true - ) - ], - ]; - } -} diff --git a/tests/Parser/ParameterParserTest.php b/tests/Parser/ParameterParserTest.php deleted file mode 100644 index 18f8c9e5..00000000 --- a/tests/Parser/ParameterParserTest.php +++ /dev/null @@ -1,59 +0,0 @@ -assertEquals($expected, $parser->parse($paramString)); - } - - public function provideCreate() - { - return [ - 'no parts' => [ - '', - null - ], - 'lone variable' => [ - '$foobar', - new Parameter('foobar'), - ], - 'typed variable' => [ - 'Foobar $foobar', - new Parameter('foobar', DocblockTypes::fromStringTypes(['Foobar'])), - ], - 'typed variable with string default' => [ - 'Foobar $foobar = "foobar"', - new Parameter('foobar', DocblockTypes::fromStringTypes(['Foobar']), DefaultValue::ofValue('foobar')), - ], - 'typed variable with single quoted default string' => [ - 'Foobar $foobar = \'foobar\'', - new Parameter('foobar', DocblockTypes::fromStringTypes(['Foobar']), DefaultValue::ofValue('foobar')), - ], - 'typed variable with float' => [ - 'Foobar $foobar = 1.234', - new Parameter('foobar', DocblockTypes::fromStringTypes(['Foobar']), DefaultValue::ofValue(1.234)), - ], - 'typed variable with int' => [ - 'Foobar $foobar = 1234', - new Parameter('foobar', DocblockTypes::fromStringTypes(['Foobar']), DefaultValue::ofValue(1234)), - ], - 'typed variable with static call (not supported)' => [ - 'Foobar $foobar = Barfoo::FOOBAR', - new Parameter('foobar', DocblockTypes::fromStringTypes(['Foobar'])), - ], - ]; - } -} diff --git a/tests/Parser/TypesParserTest.php b/tests/Parser/TypesParserTest.php deleted file mode 100644 index 669371c0..00000000 --- a/tests/Parser/TypesParserTest.php +++ /dev/null @@ -1,54 +0,0 @@ -assertEquals($expected, $parser->parseTypes($types)); - } - - public function provideParseTypes() - { - return [ - [ - 'Foobar', - DocblockTypes::fromStringTypes(['Foobar']), - ], - [ - 'Foobar[]', - DocblockTypes::fromDocblockTypes([ DocblockType::arrayOf('Foobar') ]), - ], - [ - 'Foobar', - DocblockTypes::fromDocblockTypes([ DocblockType::collectionOf('Foobar', 'Item') ]), - ], - [ - '\Foobar\Foobar', - DocblockTypes::fromDocblockTypes([ DocblockType::fullyQualifiedNameOf('Foobar\Foobar') ]), - ], - [ - '?Foobar', - DocblockTypes::fromStringTypes(['Foobar']), - ], - [ - '?Foobar[]', - DocblockTypes::fromDocblockTypes([ DocblockType::arrayOf('Foobar') ]), - ], - [ - 'Foobar', - DocblockTypes::fromDocblockTypes([ DocblockType::collectionOf('Foobar', 'Item') ]), - ], - ]; - } -} diff --git a/tests/ParserTest.php b/tests/ParserTest.php deleted file mode 100644 index 7a911617..00000000 --- a/tests/ParserTest.php +++ /dev/null @@ -1,140 +0,0 @@ -parse($docblock); - $this->assertEquals($expected, $tags); - } - - public function provideParseTags() - { - yield [ - '/** @var Foobar */', - [ 'var' => [ [ 'Foobar' ] ] ], - ]; - - yield [ - '/** @var Foobar[] */', - [ 'var' => [ [ 'Foobar[]' ] ] ], - ]; - - yield 'for collection' => [ - '/** @var Foobar */', - [ 'var' => [ [ 'Foobar' ] ] ], - ]; - - yield [ - '/** @var Foobar $foobar */', - [ 'var' => [ [ 'Foobar', '$foobar' ] ] ], - ]; - - yield 'named var with irregular spacing' => [ - '/** @var Foobar $foobar */', - [ 'var' => [ [ 'Foobar', '$foobar' ] ] ], - ]; - - yield [ - <<<'EOT' -/** - * @var Foobar $foobar - * @var Barfoo $barfoo - **/ -EOT - , - ['var' => [ - [ 'Foobar', '$foobar' ], - [ 'Barfoo', '$barfoo' ] - ]], - ]; - - yield [ - <<<'EOT' -/** - * @var Foobar $foobar Hello this is description - **/ -EOT - , - ['var' => [ - [ 'Foobar', '$foobar', 'Hello', 'this', 'is', 'description' ], - ]], - ]; - - yield 'method with fully qualified type' => [ - <<<'EOT' -/** - * @method \Foobar\Barfoo foobar() - **/ -EOT - , - ['method' => [ - [ '\Foobar\Barfoo', 'foobar()' ], - ]], - ]; - - yield 'method with parameters' => [ - '/** @method \Barfoo foobar($foobar, string $foo) */', - [ 'method' => [ [ - '\Barfoo', - 'foobar($foobar,', 'string', '$foo)' - ] ] ], - ]; - - yield 'method with array type' => [ - '/** @method Foobar[] */', - [ 'method' => [ [ 'Foobar[]' ] ] ], - ]; - - yield 'phpspec should have count' => [ - '* @method void shouldHaveCount($count)', - [ 'method' => [ [ 'void', 'shouldHaveCount($count)' ] ] ], - ]; - - yield 'argument with default value' => [ - '* @method void shouldHaveCount($count = 5)', - [ 'method' => [ [ - 'void', - 'shouldHaveCount($count', '=', '5)' - ] ] ], - ]; - } - - /** - * @dataProvider provideParseProse - */ - public function testParseProse($docblock, $expected) - { - $parser = new Parser(); - list($prose, $tags) = $parser->parse($docblock); - $this->assertEquals($expected, $prose); - } - - public function provideParseProse() - { - return [ - [ - <<<'EOT' -/** - * Hello - * - * This is a description - * - * @return Foo - */ -EOT - , - [ 'Hello', '', 'This is a description', '' ], - ], - ]; - } -} diff --git a/tests/Tag/MethodTagTest.php b/tests/Tag/MethodTagTest.php deleted file mode 100644 index 2f663016..00000000 --- a/tests/Tag/MethodTagTest.php +++ /dev/null @@ -1,17 +0,0 @@ -assertEquals('foobar', $tag->methodName()); - $this->assertEquals(DocblockTypes::fromStringTypes([ 'Foobar' ]), $tag->types()); - } -} diff --git a/tests/Tag/PropertyTagTest.php b/tests/Tag/PropertyTagTest.php deleted file mode 100644 index c4f41faa..00000000 --- a/tests/Tag/PropertyTagTest.php +++ /dev/null @@ -1,17 +0,0 @@ -assertEquals('foobar', $tag->propertyName()); - $this->assertEquals(DocblockTypes::fromStringTypes([ 'Foobar' ]), $tag->types()); - } -} diff --git a/tests/Tag/VarTagTest.php b/tests/Tag/VarTagTest.php deleted file mode 100644 index c8e92620..00000000 --- a/tests/Tag/VarTagTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertEquals(DocblockTypes::fromStringTypes(['Foobar']), $tag->types()); - - $tag = new VarTag(DocblockTypes::fromStringTypes([ 'Foobar' ]), '$foobar'); - $this->assertEquals('$foobar', $tag->varName()); - } -} diff --git a/tests/TagsTest.php b/tests/TagsTest.php deleted file mode 100644 index 38cc1ee7..00000000 --- a/tests/TagsTest.php +++ /dev/null @@ -1,31 +0,0 @@ -assertEquals($expected, $original->byName('foobar')); - } -} From a816265de653dff806a523b69d5788bf3a635487 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 20:36:25 +0000 Subject: [PATCH 02/63] Initial --- composer.json | 3 +- lib/Lexer.php | 103 +++++++++++++++++++++++++++++++++++++++ lib/Token.php | 50 +++++++++++++++++++ tests/Unit/LexerTest.php | 68 ++++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 lib/Lexer.php create mode 100644 lib/Token.php create mode 100644 tests/Unit/LexerTest.php diff --git a/composer.json b/composer.json index eda3af40..7405546d 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "friendsofphp/php-cs-fixer": "^2.17", "phpstan/phpstan": "~0.12.0", "phpunit/phpunit": "^9.0", - "phpspec/prophecy-phpunit": "^2.0" + "phpspec/prophecy-phpunit": "^2.0", + "symfony/var-dumper": "^5.2" }, "extra": { "branch-alias": { diff --git a/lib/Lexer.php b/lib/Lexer.php new file mode 100644 index 00000000..92c0c2d0 --- /dev/null +++ b/lib/Lexer.php @@ -0,0 +1,103 @@ +', + + // variable name + '\$[a-zA-Z0-9_\x80-\xff]+', + + // name + '[^a-zA-Z0-9_\x80-\xff]+', + ]; + + /** + * @var string[] + */ + private $ignorePatterns = [ + '\s+' + ]; + + /** + * @return Token[] + */ + public function lex(string $docblock): array + { + $pattern = sprintf( + '{(%s)|%s}', + implode(')|(', $this->patterns), + implode('|', $this->ignorePatterns) + ); + $chunks = (array)preg_split( + $pattern, + $docblock, + null, + PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE + ); + + $tokens = []; + foreach ($chunks as $chunk) { + [ $value, $offset ] = $chunk; + $tokens[] = new Token($offset, $this->resolveType($value), $value); + } + + return $tokens; + } + + private function resolveType($value): string + { + if (0 === strpos($value, '$')) { + return Token::T_VARIABLE; + } + + if (0 === strpos($value, '@')) { + return Token::T_TAG; + } + + if (trim($value) === '') { + return Token::T_WHITESPACE; + } + + if (trim($value) === '') { + return Token::T_WHITESPACE; + } + + if (ctype_alpha($value) || $value === '_') { + return Token::T_LABEL; + } + + if ($value === ']') { + return Token::T_BRACKET_SQUARE_CLOSE; + } + + if ($value === '[') { + return Token::T_BRACKET_SQUARE_OPEN; + } + + if ($value === '<') { + return Token::T_BRACKET_ANGLE_OPEN; + } + + if ($value === '>') { + return Token::T_BRACKET_ANGLE_CLOSE; + } + + return Token::T_UNKNOWN; + } + +} diff --git a/lib/Token.php b/lib/Token.php new file mode 100644 index 00000000..29d31dab --- /dev/null +++ b/lib/Token.php @@ -0,0 +1,50 @@ +byteOffset = $byteOffset; + $this->type = $type; + $this->value = $value; + } + + public function byteOffset(): int + { + return $this->byteOffset; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php new file mode 100644 index 00000000..986c9cbd --- /dev/null +++ b/tests/Unit/LexerTest.php @@ -0,0 +1,68 @@ + $expectedTokens + */ + public function testLex(string $lex, array $expectedTokens): void + { + $tokens = (new Lexer())->lex($lex); + + self::assertCount(count($expectedTokens), $tokens, 'Expected number of tokens'); + + foreach ($tokens as $index => $token) { + [$type, $value] = $expectedTokens[$index]; + $expectedToken = new Token($token->byteOffset(), $type, $value); + self::assertEquals($expectedToken, $token); + } + } + + /** + * @return Generator + */ + public function provideLex(): Generator + { + yield [ '', [] ]; + yield [ + 'Foobar', + [ + [Token::T_LABEL, "Foobar"], + ] + ]; + yield [ + 'Foobar[]', + [ + [Token::T_LABEL, "Foobar"], + [Token::T_BRACKET_SQUARE_OPEN, "["], + [Token::T_BRACKET_SQUARE_CLOSE, "]"], + ] + ]; + yield [ + 'Foobar', + [ + [Token::T_LABEL, "Foobar"], + [Token::T_BRACKET_ANGLE_OPEN, "<"], + [Token::T_LABEL, "Barfoo"], + [Token::T_BRACKET_ANGLE_CLOSE, ">"], + ] + ]; + yield [ + 'Foobar', + [ + [Token::T_LABEL, "Foobar"], + [Token::T_BRACKET_ANGLE_OPEN, "<"], + [Token::T_LABEL, "Barfoo"], + [Token::T_BRACKET_ANGLE_CLOSE, ">"], + ] + ]; + } +} From 7c803edad1185606ec2f249357c4b3f5b8651477 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 20:53:03 +0000 Subject: [PATCH 03/63] Progress --- lib/Lexer.php | 35 +++++++++++++++++++---------------- lib/Token.php | 4 ++++ tests/Unit/LexerTest.php | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index 92c0c2d0..cfb40b4f 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -4,26 +4,17 @@ class Lexer { - private $position = 0; - /** * @var string[] */ private $patterns = [ - // param tag - '@[\w]+', - - // param tag - '\s', - - // brackets - '\[', '\]', '<', '>', - - // variable name - '\$[a-zA-Z0-9_\x80-\xff]+', - - // name - '[^a-zA-Z0-9_\x80-\xff]+', + '/\*+\s*\*?', // border + '@[\w]+', //tag + '\s+', // whitespace + ',', // comma + '\{', '\}', '\[', '\]', '<', '>', // brackets + '\$[a-zA-Z0-9_\x80-\xff]+', // variable + '[^a-zA-Z0-9_\x80-\xff]+', // label ]; /** @@ -97,6 +88,18 @@ private function resolveType($value): string return Token::T_BRACKET_ANGLE_CLOSE; } + if ($value === '{') { + return Token::T_BRACKET_CURLY_OPEN; + } + + if ($value === '}') { + return Token::T_BRACKET_CURLY_CLOSE; + } + + if ($value === ',') { + return Token::T_COMMA; + } + return Token::T_UNKNOWN; } diff --git a/lib/Token.php b/lib/Token.php index 29d31dab..e3fbb362 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -4,17 +4,21 @@ class Token { + public const T_PHPDOC_BORDER= 'PHPDOC_BORDER'; public const T_BORDER = 'BORDER'; public const T_TEXT = 'TEXT'; public const T_VARIABLE = 'VARIABLE'; public const T_UNKNOWN = 'UNKNOWN'; public const T_TAG = 'TAG'; + public const T_COMMA = 'COMMA'; public const T_LABEL = 'LABEL'; public const T_WHITESPACE = 'WHITESPACE'; public const T_BRACKET_SQUARE_OPEN = 'BRACKET_SQUARE_OPEN'; public const T_BRACKET_SQUARE_CLOSE = 'BRACKET_SQUARE_CLOSE'; public const T_BRACKET_ANGLE_OPEN = 'BRACKET_ANGLE_OPEN'; public const T_BRACKET_ANGLE_CLOSE = 'BRACKET_ANGLE_CLOSE'; + public const T_BRACKET_CURLY_OPEN = 'BRACKET_CURLY_OPEN'; + public const T_BRACKET_CURLY_CLOSE = 'BRACKET_CURLY_CLOSE'; /** * @var int diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 986c9cbd..0d4ffc33 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -32,6 +32,26 @@ public function testLex(string $lex, array $expectedTokens): void public function provideLex(): Generator { yield [ '', [] ]; + yield [ + <<<'EOT' +/** + * Hello this is + */ +EOT + + ,[ + [Token::T_UNKNOWN, "/**\n *"], + [Token::T_WHITESPACE, " "], + [Token::T_LABEL, "Hello"], + [Token::T_WHITESPACE, " "], + [Token::T_LABEL, "this"], + [Token::T_WHITESPACE, " "], + [Token::T_LABEL, "is"], + [Token::T_WHITESPACE, "\n "], + [Token::T_UNKNOWN, "*/"], + ] + ]; + yield [ 'Foobar', [ @@ -64,5 +84,17 @@ public function provideLex(): Generator [Token::T_BRACKET_ANGLE_CLOSE, ">"], ] ]; + yield [ + 'Foobar{Barfoo, Foobar}', + [ + [Token::T_LABEL, "Foobar"], + [Token::T_BRACKET_CURLY_OPEN, "{"], + [Token::T_LABEL, "Barfoo"], + [Token::T_COMMA, ","], + [Token::T_WHITESPACE, " "], + [Token::T_LABEL, "Foobar"], + [Token::T_BRACKET_CURLY_CLOSE, "}"], + ] + ]; } } From 817a5ee97b82c5781acca42ded1ef6d2f0d78706 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 21:24:12 +0000 Subject: [PATCH 04/63] open, close and leading phpdoc --- lib/Lexer.php | 25 ++++++++++++++++++++++--- lib/Parser.php | 0 lib/Token.php | 3 +++ tests/Unit/LexerTest.php | 8 ++++++-- 4 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 lib/Parser.php diff --git a/lib/Lexer.php b/lib/Lexer.php index cfb40b4f..7b35d635 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -8,7 +8,7 @@ class Lexer * @var string[] */ private $patterns = [ - '/\*+\s*\*?', // border + '^/\*+\s*\*?', // start tag '@[\w]+', //tag '\s+', // whitespace ',', // comma @@ -42,16 +42,35 @@ public function lex(string $docblock): array ); $tokens = []; + $prevChunk = [null,null]; foreach ($chunks as $chunk) { [ $value, $offset ] = $chunk; - $tokens[] = new Token($offset, $this->resolveType($value), $value); + [ $prevValue, $_ ] = $prevChunk; + $tokens[] = new Token( + $offset, + $this->resolveType($value, $prevValue), + $value + ); + $prevChunk = $chunk; } return $tokens; } - private function resolveType($value): string + private function resolveType(string $value, ?string $prevValue): string { + if (false !== strpos($value, '/*')) { + return Token::T_PHPDOC_OPEN; + } + + if (false !== strpos($value, '*/')) { + return Token::T_PHPDOC_CLOSE; + } + + if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { + return Token::T_PHPDOC_LEADING; + } + if (0 === strpos($value, '$')) { return Token::T_VARIABLE; } diff --git a/lib/Parser.php b/lib/Parser.php new file mode 100644 index 00000000..e69de29b diff --git a/lib/Token.php b/lib/Token.php index e3fbb362..8784e07e 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -5,6 +5,9 @@ class Token { public const T_PHPDOC_BORDER= 'PHPDOC_BORDER'; + public const T_PHPDOC_OPEN = 'PHPDOC_OPEN'; + public const T_PHPDOC_LEADING = 'PHPDOC_LEADING'; + public const T_PHPDOC_CLOSE = 'PHPDOC_CLOSE'; public const T_BORDER = 'BORDER'; public const T_TEXT = 'TEXT'; public const T_VARIABLE = 'VARIABLE'; diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 0d4ffc33..9f43b205 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -36,11 +36,12 @@ public function provideLex(): Generator <<<'EOT' /** * Hello this is + * Multi */ EOT ,[ - [Token::T_UNKNOWN, "/**\n *"], + [Token::T_PHPDOC_OPEN, "/**\n *"], [Token::T_WHITESPACE, " "], [Token::T_LABEL, "Hello"], [Token::T_WHITESPACE, " "], @@ -48,7 +49,10 @@ public function provideLex(): Generator [Token::T_WHITESPACE, " "], [Token::T_LABEL, "is"], [Token::T_WHITESPACE, "\n "], - [Token::T_UNKNOWN, "*/"], + [Token::T_PHPDOC_LEADING, "* "], + [Token::T_LABEL, "Multi"], + [Token::T_WHITESPACE, "\n "], + [Token::T_PHPDOC_CLOSE, "*/"], ] ]; From afe8a54e2bde397851c6d3af25b3796d804db2b6 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 21:30:13 +0000 Subject: [PATCH 05/63] Return collection --- lib/Lexer.php | 7 ++----- lib/Token.php | 2 +- tests/Unit/LexerTest.php | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index 7b35d635..cdc693f8 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -24,10 +24,7 @@ class Lexer '\s+' ]; - /** - * @return Token[] - */ - public function lex(string $docblock): array + public function lex(string $docblock): Tokens { $pattern = sprintf( '{(%s)|%s}', @@ -54,7 +51,7 @@ public function lex(string $docblock): array $prevChunk = $chunk; } - return $tokens; + return new Tokens($tokens); } private function resolveType(string $value, ?string $prevValue): string diff --git a/lib/Token.php b/lib/Token.php index 8784e07e..e40e19f7 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -2,7 +2,7 @@ namespace Phpactor\Docblock; -class Token +final class Token { public const T_PHPDOC_BORDER= 'PHPDOC_BORDER'; public const T_PHPDOC_OPEN = 'PHPDOC_OPEN'; diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 9f43b205..624b6e5f 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -15,7 +15,7 @@ class LexerTest extends TestCase */ public function testLex(string $lex, array $expectedTokens): void { - $tokens = (new Lexer())->lex($lex); + $tokens = (new Lexer())->lex($lex)->toArray(); self::assertCount(count($expectedTokens), $tokens, 'Expected number of tokens'); From 1e9b7042335d9fe6c6c54470745932b9a0f8a3ec Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 23:15:39 +0000 Subject: [PATCH 06/63] Parser test --- lib/Ast/Docblock.php | 14 +++++++ lib/Ast/Element.php | 7 ++++ lib/Ast/NameNode.php | 18 +++++++++ lib/Ast/Node.php | 11 ++++++ lib/Ast/ParamNode.php | 21 ++++++++++ lib/Ast/TypeNode.php | 7 ++++ lib/Ast/VariableNode.php | 19 +++++++++ lib/Parser.php | 65 ++++++++++++++++++++++++++++++ lib/Token.php | 9 ++++- lib/Tokens.php | 83 +++++++++++++++++++++++++++++++++++++++ tests/Unit/ParserTest.php | 56 ++++++++++++++++++++++++++ 11 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 lib/Ast/Docblock.php create mode 100644 lib/Ast/Element.php create mode 100644 lib/Ast/NameNode.php create mode 100644 lib/Ast/Node.php create mode 100644 lib/Ast/ParamNode.php create mode 100644 lib/Ast/TypeNode.php create mode 100644 lib/Ast/VariableNode.php create mode 100644 lib/Tokens.php create mode 100644 tests/Unit/ParserTest.php diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php new file mode 100644 index 00000000..61bf1f37 --- /dev/null +++ b/lib/Ast/Docblock.php @@ -0,0 +1,14 @@ +children = $children; + } +} diff --git a/lib/Ast/Element.php b/lib/Ast/Element.php new file mode 100644 index 00000000..94ed2a77 --- /dev/null +++ b/lib/Ast/Element.php @@ -0,0 +1,7 @@ +name = $name; + } +} diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php new file mode 100644 index 00000000..1501db8e --- /dev/null +++ b/lib/Ast/Node.php @@ -0,0 +1,11 @@ +type = $type; + $this->variable = $variable; + } +} diff --git a/lib/Ast/TypeNode.php b/lib/Ast/TypeNode.php new file mode 100644 index 00000000..195602c1 --- /dev/null +++ b/lib/Ast/TypeNode.php @@ -0,0 +1,7 @@ +name = $name; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index e69de29b..cabeb595 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -0,0 +1,65 @@ +current()]; + while ($token = $tokens->next()) { + assert($token instanceof Token); + if ($token->type() === Token::T_TAG) { + $children[] = $this->parseTag($token, $tokens); + continue; + } + $children[] = $token; + } + + return new Docblock($children); + } + + private function parseTag(Token $token, Tokens $tokens) + { + if ($token->value() === '@param') { + return $this->parseParam($tokens); + } + } + + private function parseParam(Tokens $tokens): ParamNode + { + $type = $this->parseType($tokens->skip(Token::T_WHITESPACE)); + $variable = $this->parseVariable($tokens->skip(Token::T_WHITESPACE)); + + return new ParamNode($type, $variable); + } + + private function parseType(Tokens $tokens): ?TypeNode + { + if (!$tokens->isType(Token::T_LABEL)) { + return null; + } + + $type = $tokens->current(); + + return new NameNode($type); + } + + private function parseVariable(Tokens $tokens): ?VariableNode + { + if (!$tokens->isType(Token::T_VARIABLE)) { + return null; + } + + $name = $tokens->current(); + + return new VariableNode($name); + } +} diff --git a/lib/Token.php b/lib/Token.php index e40e19f7..91d15b4f 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -2,7 +2,9 @@ namespace Phpactor\Docblock; -final class Token +use Phpactor\Docblock\Ast\Element; + +final class Token implements Element { public const T_PHPDOC_BORDER= 'PHPDOC_BORDER'; public const T_PHPDOC_OPEN = 'PHPDOC_OPEN'; @@ -54,4 +56,9 @@ public function value(): string { return $this->value; } + + public function type(): string + { + return $this->type; + } } diff --git a/lib/Tokens.php b/lib/Tokens.php new file mode 100644 index 00000000..10a91d0a --- /dev/null +++ b/lib/Tokens.php @@ -0,0 +1,83 @@ +tokens = $tokens; + } + + /** + * @return Token[] + */ + public function toArray(): array + { + return $this->tokens; + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->tokens); + } + + public function next(): ?Token + { + if (!isset($this->tokens[$this->position + 1])) { + return null; + } + + return $this->tokens[++$this->position]; + } + + public function current(): Token + { + if (!isset($this->tokens[$this->position])) { + throw new RuntimeException(sprintf( + 'No token at current position "%s"', + $this->position + )); + } + + return $this->tokens[$this->position]; + } + + /** + * Skip until all tokens of the given type + */ + public function skip(string $type): self + { + while (null !== $current = $this->next()) { + if ($current->type() === $type) { + continue; + } + + return $this; + } + + return $this; + } + + public function isType(string $type): bool + { + return $this->current()->type() === $type; + } +} diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php new file mode 100644 index 00000000..ad6466f1 --- /dev/null +++ b/tests/Unit/ParserTest.php @@ -0,0 +1,56 @@ +parse((new Lexer())->lex($text)); + dump($node); + self::assertEquals($expected, $node); + } + + /** + * @return Generator + */ + public function provideParse(): Generator + { + yield [ + '/** Hello */', + new Docblock([ + new Token(0, Token::T_PHPDOC_OPEN, '/** '), + new Token(4, Token::T_LABEL, 'Hello'), + new Token(9, Token::T_WHITESPACE, ' '), + new Token(10, Token::T_PHPDOC_CLOSE, '*/'), + ]) + ]; + + yield [ + '/** @param Foobar $foobar */', + new Docblock([ + new Token(0, Token::T_PHPDOC_OPEN, '/** '), + new ParamNode( + new NameNode(new Token(11, Token::T_LABEL, 'Foobar')), + new VariableNode(new Token(18, Token::T_VARIABLE, '$foobar')) + ), + new Token(25, Token::T_WHITESPACE, ' '), + new Token(26, Token::T_PHPDOC_CLOSE, '*/'), + ]) + ]; + } +} From efa1e2a83c2409cd411dd7308566b591e2e173bf Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 20 Jan 2021 23:22:29 +0000 Subject: [PATCH 07/63] Updated README --- README.md | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 19a8b093..21267499 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,11 @@ Docblock Parser =============== -Sub-standard docblock parser. +PHP Docblock parser with the following aims: -```php -$docblock = (new DocblockFactory())->create('/** @var Foobar */'); -$vars = $docblock->tags()->byName('var'); - -foreach ($vars as $var) { - $var->type(); - $var->varName(); -} -``` - -Why? ----- - -There is already a [standards-compliant -library](https://github.com/phpDocumentor/ReflectionDocBlock) for -PHP-Documentor, however it is coupled to the PHPDocumentor type reflection -library. This library only cares about parsing docblocks badly for -[Phpactor](https://github.com/phpactor/phpactor). +- Fast: has to be fast enough for IDE analysis. +- Isomorphic: can be transformed back to the original text representation. +- Positional: the positions of nodes are captured. Contributing ------------ From 6c5ea45260e3933f6e8e7019944bd064b0796d98 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Thu, 21 Jan 2021 22:50:59 +0000 Subject: [PATCH 08/63] Parse lists --- lib/Ast/Docblock.php | 13 +++ lib/Ast/Node.php | 8 +- lib/Ast/ParamNode.php | 19 +++- lib/Ast/TagNode.php | 7 ++ lib/Ast/Type/ClassNode.php | 24 +++++ lib/Ast/Type/ListNode.php | 35 +++++++ lib/Ast/Type/ScalarNode.php | 24 +++++ lib/Ast/TypeNode.php | 4 +- lib/Ast/{NameNode.php => UnknownTag.php} | 2 +- lib/Ast/VariableNode.php | 5 + lib/Lexer.php | 8 +- lib/Parser.php | 34 ++++++- lib/Printer.php | 10 ++ lib/Printer/TestPrinter.php | 112 +++++++++++++++++++++++ lib/Token.php | 6 ++ lib/Tokens.php | 9 ++ tests/Printer/PrinterTest.php | 50 ++++++++++ tests/Printer/examples/list1.test | 9 ++ tests/Printer/examples/param1.test | 7 ++ tests/Printer/examples/param2.test | 9 ++ tests/Printer/examples/param3.test | 19 ++++ tests/Printer/examples/text1.test | 11 +++ tests/Unit/ParserTest.php | 4 +- 23 files changed, 413 insertions(+), 16 deletions(-) create mode 100644 lib/Ast/TagNode.php create mode 100644 lib/Ast/Type/ClassNode.php create mode 100644 lib/Ast/Type/ListNode.php create mode 100644 lib/Ast/Type/ScalarNode.php rename lib/Ast/{NameNode.php => UnknownTag.php} (86%) create mode 100644 lib/Printer.php create mode 100644 lib/Printer/TestPrinter.php create mode 100644 tests/Printer/PrinterTest.php create mode 100644 tests/Printer/examples/list1.test create mode 100644 tests/Printer/examples/param1.test create mode 100644 tests/Printer/examples/param2.test create mode 100644 tests/Printer/examples/param3.test create mode 100644 tests/Printer/examples/text1.test diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index 61bf1f37..1aec2f21 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -4,6 +4,11 @@ class Docblock extends Node { + /** + * @var Element[] + */ + private $children = []; + /** * @param Element[] $children */ @@ -11,4 +16,12 @@ public function __construct(array $children) { $this->children = $children; } + + /** + * @return Element[] + */ + public function children(): array + { + return $this->children; + } } diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 1501db8e..8ecbfb98 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -4,8 +4,8 @@ class Node implements Element { - /** - * @var Element[] - */ - protected $children = []; + public function shortName(): string + { + return substr(get_class($this), strrpos(get_class($this), '\\') + 1); + } } diff --git a/lib/Ast/ParamNode.php b/lib/Ast/ParamNode.php index db3d280b..e2bac69e 100644 --- a/lib/Ast/ParamNode.php +++ b/lib/Ast/ParamNode.php @@ -2,20 +2,31 @@ namespace Phpactor\Docblock\Ast; -class ParamNode extends Node +class ParamNode extends TagNode { /** - * @var TypeNode + * @var ?TypeNode */ private $type; + /** - * @var VariableNode + * @var ?VariableNode */ private $variable; - public function __construct(TypeNode $type, VariableNode $variable) + public function __construct(?TypeNode $type, ?VariableNode $variable) { $this->type = $type; $this->variable = $variable; } + + public function type(): ?TypeNode + { + return $this->type; + } + + public function variable(): ?VariableNode + { + return $this->variable; + } } diff --git a/lib/Ast/TagNode.php b/lib/Ast/TagNode.php new file mode 100644 index 00000000..93ebc1c8 --- /dev/null +++ b/lib/Ast/TagNode.php @@ -0,0 +1,7 @@ +name = $name; + } + + public function name(): Token + { + return $this->name; + } +} diff --git a/lib/Ast/Type/ListNode.php b/lib/Ast/Type/ListNode.php new file mode 100644 index 00000000..aef076f2 --- /dev/null +++ b/lib/Ast/Type/ListNode.php @@ -0,0 +1,35 @@ +type = $type; + $this->listChars = $listChars; + } + + public function type(): TypeNode + { + return $this->type; + } + + public function listChars(): Token + { + return $this->listChars; + } +} diff --git a/lib/Ast/Type/ScalarNode.php b/lib/Ast/Type/ScalarNode.php new file mode 100644 index 00000000..f7e41397 --- /dev/null +++ b/lib/Ast/Type/ScalarNode.php @@ -0,0 +1,24 @@ +name = $name; + } + + public function name(): Token + { + return $this->name; + } +} diff --git a/lib/Ast/TypeNode.php b/lib/Ast/TypeNode.php index 195602c1..2ca7a693 100644 --- a/lib/Ast/TypeNode.php +++ b/lib/Ast/TypeNode.php @@ -2,6 +2,8 @@ namespace Phpactor\Docblock\Ast; -class TypeNode extends Node +use Phpactor\Docblock\Token; + +abstract class TypeNode extends Node { } diff --git a/lib/Ast/NameNode.php b/lib/Ast/UnknownTag.php similarity index 86% rename from lib/Ast/NameNode.php rename to lib/Ast/UnknownTag.php index 3ef61d93..3679740c 100644 --- a/lib/Ast/NameNode.php +++ b/lib/Ast/UnknownTag.php @@ -4,7 +4,7 @@ use Phpactor\Docblock\Token; -class NameNode extends TypeNode +class UnknownTag extends TagNode { /** * @var Token diff --git a/lib/Ast/VariableNode.php b/lib/Ast/VariableNode.php index 1178dac6..6bcca641 100644 --- a/lib/Ast/VariableNode.php +++ b/lib/Ast/VariableNode.php @@ -16,4 +16,9 @@ public function __construct(Token $name) { $this->name = $name; } + + public function name(): Token + { + return $this->name; + } } diff --git a/lib/Lexer.php b/lib/Lexer.php index cdc693f8..a6d65ffb 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -9,7 +9,9 @@ class Lexer */ private $patterns = [ '^/\*+\s*\*?', // start tag - '@[\w]+', //tag + '\s*\*', // border + '\[\]', //tag + '@\w+', //tag '\s+', // whitespace ',', // comma '\{', '\}', '\[', '\]', '<', '>', // brackets @@ -116,6 +118,10 @@ private function resolveType(string $value, ?string $prevValue): string return Token::T_COMMA; } + if ($value === '[]') { + return Token::T_LIST; + } + return Token::T_UNKNOWN; } diff --git a/lib/Parser.php b/lib/Parser.php index cabeb595..a5d932fe 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -3,17 +3,26 @@ namespace Phpactor\Docblock; use Phpactor\Docblock\Ast\Docblock; -use Phpactor\Docblock\Ast\NameNode; +use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Ast\ParamNode; +use Phpactor\Docblock\Ast\TagNode; use Phpactor\Docblock\Ast\TypeNode; +use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\ScalarNode; +use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\VariableNode; final class Parser { + private const SCALAR_TYPES = [ + 'int', 'float', 'bool', 'string' + ]; + public function parse(Tokens $tokens): Node { $children = [$tokens->current()]; + while ($token = $tokens->next()) { assert($token instanceof Token); if ($token->type() === Token::T_TAG) { @@ -26,11 +35,13 @@ public function parse(Tokens $tokens): Node return new Docblock($children); } - private function parseTag(Token $token, Tokens $tokens) + private function parseTag(Token $token, Tokens $tokens): TagNode { if ($token->value() === '@param') { return $this->parseParam($tokens); } + + return new UnknownTag($token); } private function parseParam(Tokens $tokens): ParamNode @@ -43,13 +54,30 @@ private function parseParam(Tokens $tokens): ParamNode private function parseType(Tokens $tokens): ?TypeNode { + $isList = false; + if (!$tokens->isType(Token::T_LABEL)) { return null; } + $type = $tokens->current(); - return new NameNode($type); + if ($tokens->peek()->type() === Token::T_LIST) { + $tokens->next(); + return new ListNode($this->createTypeFromToken($type), $tokens->current()); + } + + return $this->createTypeFromToken($type); + } + + private function createTypeFromToken(Token $type): TypeNode + { + if (in_array($type->value(), self::SCALAR_TYPES)) { + return new ScalarNode($type); + } + + return new ClassNode($type); } private function parseVariable(Tokens $tokens): ?VariableNode diff --git a/lib/Printer.php b/lib/Printer.php new file mode 100644 index 00000000..f7459b3e --- /dev/null +++ b/lib/Printer.php @@ -0,0 +1,10 @@ +out = []; + + $this->render($node); + + return implode("", $this->out); + } + + private function render(?Element $node): void + { + if (null === $node) { + $this->out[] = '#missing#'; + return; + } + + if ($node instanceof Docblock) { + $this->renderDocblock($node); + return; + } + + if ($node instanceof Token) { + $this->out[] = $node->value(); + return; + } + + if ($node instanceof ParamNode) { + $this->renderParam($node); + return; + } + + if ($node instanceof ListNode) { + $this->renderListNode($node); + return; + } + + if ($node instanceof TypeNode) { + $this->renderTypeNode($node); + return; + } + + if ($node instanceof VariableNode) { + $this->renderVariableNode($node); + return; + } + + throw new RuntimeException(sprintf( + 'Do not know how to render "%s"', + get_class($node) + )); + } + + private function renderDocblock(Docblock $node): void + { + foreach ($node->children() as $child) { + $this->render($child); + } + } + + private function renderParam(ParamNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + $this->out[] = ','; + $this->render($node->variable()); + $this->out[] = ')'; + } + + private function renderTypeNode(TypeNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->name()); + $this->out[] = ')'; + } + + private function renderVariableNode(VariableNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->name()); + $this->out[] = ')'; + } + + private function renderListNode(ListNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + $this->out[] = ')'; + } +} diff --git a/lib/Token.php b/lib/Token.php index 91d15b4f..5350327b 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -16,6 +16,7 @@ final class Token implements Element public const T_UNKNOWN = 'UNKNOWN'; public const T_TAG = 'TAG'; public const T_COMMA = 'COMMA'; + public const T_LIST = '[]'; public const T_LABEL = 'LABEL'; public const T_WHITESPACE = 'WHITESPACE'; public const T_BRACKET_SQUARE_OPEN = 'BRACKET_SQUARE_OPEN'; @@ -61,4 +62,9 @@ public function type(): string { return $this->type; } + + public function __toString(): string + { + return $this->value; + } } diff --git a/lib/Tokens.php b/lib/Tokens.php index 10a91d0a..69bb0326 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -39,6 +39,15 @@ public function getIterator(): ArrayIterator return new ArrayIterator($this->tokens); } + public function peek(): ?Token + { + if (!isset($this->tokens[$this->position + 1])) { + return null; + } + + return $this->tokens[$this->position + 1]; + } + public function next(): ?Token { if (!isset($this->tokens[$this->position + 1])) { diff --git a/tests/Printer/PrinterTest.php b/tests/Printer/PrinterTest.php new file mode 100644 index 00000000..3d0ca500 --- /dev/null +++ b/tests/Printer/PrinterTest.php @@ -0,0 +1,50 @@ +parse((new Lexer())->lex($parts[0])); + $rendered = (new TestPrinter())->print($node); + + /** + * @phpstan-ignore-next-line + */ + if (!isset($parts[1]) || $update) { + file_put_contents($path, implode("---\n", [$parts[0], $rendered])); + $this->markTestSkipped('Generated output'); + return; + } + + self::assertEquals(ltrim($parts[1]), $rendered); + } + + /** + * @return Generator + */ + public function provideExamples(): Generator + { + foreach ((array)glob(__DIR__ . '/examples/*.test') as $path) { + yield [ + $path + ]; + } + } +} diff --git a/tests/Printer/examples/list1.test b/tests/Printer/examples/list1.test new file mode 100644 index 00000000..94d5be11 --- /dev/null +++ b/tests/Printer/examples/list1.test @@ -0,0 +1,9 @@ +/** + * @param Foobar[] $foobar + * @param string[] $strings + */ +--- +/** + * ParamNode(ListNode(ClassNode(Foobar)),VariableNode($foobar)) + * ParamNode(ListNode(ScalarNode(string)),VariableNode($strings)) + */ diff --git a/tests/Printer/examples/param1.test b/tests/Printer/examples/param1.test new file mode 100644 index 00000000..ee64e32a --- /dev/null +++ b/tests/Printer/examples/param1.test @@ -0,0 +1,7 @@ +/** + * @param Foobar $foobar + */ +--- +/** + * ParamNode(ClassNode(Foobar),VariableNode($foobar)) + */ diff --git a/tests/Printer/examples/param2.test b/tests/Printer/examples/param2.test new file mode 100644 index 00000000..2eb6c34d --- /dev/null +++ b/tests/Printer/examples/param2.test @@ -0,0 +1,9 @@ +/** + * @param Foobar $foobar + * @param string $barfoo + */ +--- +/** + * ParamNode(ClassNode(Foobar),VariableNode($foobar)) + * ParamNode(ScalarNode(string),VariableNode($barfoo)) + */ diff --git a/tests/Printer/examples/param3.test b/tests/Printer/examples/param3.test new file mode 100644 index 00000000..eb7e6809 --- /dev/null +++ b/tests/Printer/examples/param3.test @@ -0,0 +1,19 @@ +/** + * Hello World + * + * @param Foobar $foobar + * + * @param string $barfoo + * + * @param bool $bool + */ +--- +/** + * Hello World + * + * ParamNode(ClassNode(Foobar),VariableNode($foobar)) + * + * ParamNode(ScalarNode(string),VariableNode($barfoo)) + * + * ParamNode(ScalarNode(bool),VariableNode($bool)) + */ diff --git a/tests/Printer/examples/text1.test b/tests/Printer/examples/text1.test new file mode 100644 index 00000000..bf18af6d --- /dev/null +++ b/tests/Printer/examples/text1.test @@ -0,0 +1,11 @@ +/** + * Hello World + * + * This is some text + */ +--- +/** + * Hello World + * + * This is some text + */ diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index ad6466f1..b9661e6c 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -5,7 +5,7 @@ use Generator; use PHPUnit\Framework\TestCase; use Phpactor\Docblock\Ast\Docblock; -use Phpactor\Docblock\Ast\NameNode; +use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\VariableNode; @@ -45,7 +45,7 @@ public function provideParse(): Generator new Docblock([ new Token(0, Token::T_PHPDOC_OPEN, '/** '), new ParamNode( - new NameNode(new Token(11, Token::T_LABEL, 'Foobar')), + new ClassNode(new Token(11, Token::T_LABEL, 'Foobar')), new VariableNode(new Token(18, Token::T_VARIABLE, '$foobar')) ), new Token(25, Token::T_WHITESPACE, ' '), From 11cb6c1e53b1a4430a17ae8edc005f7982016e5d Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Fri, 22 Jan 2021 00:07:59 +0000 Subject: [PATCH 09/63] Printer tests OK, docblock start / end broken --- lib/Ast/Type/GenericNode.php | 58 ++++++++++++++++++++++++++++ lib/Ast/TypeList.php | 42 ++++++++++++++++++++ lib/Lexer.php | 1 + lib/Parser.php | 40 ++++++++++++++++++- lib/Printer/TestPrinter.php | 26 +++++++++++++ lib/Token.php | 2 +- lib/Tokens.php | 4 ++ tests/Printer/examples/generic1.test | 7 ++++ tests/Printer/examples/generic2.test | 7 ++++ tests/Printer/examples/generic3.test | 7 ++++ tests/Printer/examples/generic4.test | 7 ++++ tests/Unit/ParserTest.php | 25 +++++------- 12 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 lib/Ast/Type/GenericNode.php create mode 100644 lib/Ast/TypeList.php create mode 100644 tests/Printer/examples/generic1.test create mode 100644 tests/Printer/examples/generic2.test create mode 100644 tests/Printer/examples/generic3.test create mode 100644 tests/Printer/examples/generic4.test diff --git a/lib/Ast/Type/GenericNode.php b/lib/Ast/Type/GenericNode.php new file mode 100644 index 00000000..66fd2bf2 --- /dev/null +++ b/lib/Ast/Type/GenericNode.php @@ -0,0 +1,58 @@ +open = $open; + $this->close = $close; + $this->parameters = $parameters; + $this->type = $type; + } + + public function close(): Token + { + return $this->close; + } + + public function open(): Token + { + return $this->open; + } + + public function parameters(): TypeList + { + return $this->parameters; + } + + public function type(): TypeNode + { + return $this->type; + } +} diff --git a/lib/Ast/TypeList.php b/lib/Ast/TypeList.php new file mode 100644 index 00000000..811f4f40 --- /dev/null +++ b/lib/Ast/TypeList.php @@ -0,0 +1,42 @@ + + */ +class TypeList implements IteratorAggregate, Countable +{ + /** + * @var TypeNode[] + */ + private $typeList; + + /** + * @param TypeNode[] $typeList + */ + public function __construct(array $typeList) + { + $this->typeList = $typeList; + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->typeList); + } + + /** + * {@inheritDoc} + */ + public function count() + { + return count($this->typeList); + } +} diff --git a/lib/Lexer.php b/lib/Lexer.php index a6d65ffb..f23f91cc 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -9,6 +9,7 @@ class Lexer */ private $patterns = [ '^/\*+\s*\*?', // start tag + '\*/', // close tag '\s*\*', // border '\[\]', //tag '@\w+', //tag diff --git a/lib/Parser.php b/lib/Parser.php index a5d932fe..4f751df6 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -3,11 +3,13 @@ namespace Phpactor\Docblock; use Phpactor\Docblock\Ast\Docblock; +use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\TagNode; use Phpactor\Docblock\Ast\TypeNode; +use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\Type\ScalarNode; use Phpactor\Docblock\Ast\UnknownTag; @@ -46,7 +48,8 @@ private function parseTag(Token $token, Tokens $tokens): TagNode private function parseParam(Tokens $tokens): ParamNode { - $type = $this->parseType($tokens->skip(Token::T_WHITESPACE)); + $tokens->next(); + $type = $this->parseType($tokens); $variable = $this->parseVariable($tokens->skip(Token::T_WHITESPACE)); return new ParamNode($type, $variable); @@ -54,6 +57,7 @@ private function parseParam(Tokens $tokens): ParamNode private function parseType(Tokens $tokens): ?TypeNode { + $tokens->skip(Token::T_WHITESPACE); $isList = false; if (!$tokens->isType(Token::T_LABEL)) { @@ -64,10 +68,30 @@ private function parseType(Tokens $tokens): ?TypeNode $type = $tokens->current(); if ($tokens->peek()->type() === Token::T_LIST) { + $tokens->next(); $tokens->next(); return new ListNode($this->createTypeFromToken($type), $tokens->current()); } + if ($tokens->peek()->type() === Token::T_BRACKET_ANGLE_OPEN) { + $open = $tokens->next(); + $tokens->next(); + $typeList = $this->parseTypeList($tokens); + + if (!$tokens->isType(Token::T_BRACKET_ANGLE_CLOSE)) { + return null; + } + $tokens->next(); + + return new GenericNode( + $open, + $this->createTypeFromToken($type), + $typeList, + $tokens->current() + ); + } + + $tokens->next(); return $this->createTypeFromToken($type); } @@ -90,4 +114,18 @@ private function parseVariable(Tokens $tokens): ?VariableNode return new VariableNode($name); } + + private function parseTypeList(Tokens $tokens): TypeList + { + $types = []; + while (true) { + $types[] = $this->parseType($tokens); + if ($tokens->current()->type() !== Token::T_COMMA) { + break; + } + $tokens->next(); + } + + return new TypeList($types); + } } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 9cc3aeff..0edeabe0 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -4,10 +4,12 @@ use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; +use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Ast\ParamNode; +use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Printer; @@ -57,6 +59,11 @@ private function render(?Element $node): void return; } + if ($node instanceof GenericNode) { + $this->renderGenericNode($node); + return; + } + if ($node instanceof TypeNode) { $this->renderTypeNode($node); return; @@ -109,4 +116,23 @@ private function renderListNode(ListNode $node): void $this->render($node->type()); $this->out[] = ')'; } + + private function renderGenericNode(GenericNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + $this->out[] = ','; + $this->renderTypeList($node->parameters()); + $this->out[] = ')'; + } + + private function renderTypeList(TypeList $typeList): void + { + foreach ($typeList as $i => $param) { + $this->render($param); + if ($i + 1 !== $typeList->count()) { + $this->out[] = ','; + } + } + } } diff --git a/lib/Token.php b/lib/Token.php index 5350327b..517ca24d 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -16,7 +16,7 @@ final class Token implements Element public const T_UNKNOWN = 'UNKNOWN'; public const T_TAG = 'TAG'; public const T_COMMA = 'COMMA'; - public const T_LIST = '[]'; + public const T_LIST = 'LIST'; public const T_LABEL = 'LABEL'; public const T_WHITESPACE = 'WHITESPACE'; public const T_BRACKET_SQUARE_OPEN = 'BRACKET_SQUARE_OPEN'; diff --git a/lib/Tokens.php b/lib/Tokens.php index 69bb0326..107c67ee 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -74,6 +74,10 @@ public function current(): Token */ public function skip(string $type): self { + if ($this->current()->type() !== $type) { + return $this; + } + while (null !== $current = $this->next()) { if ($current->type() === $type) { continue; diff --git a/tests/Printer/examples/generic1.test b/tests/Printer/examples/generic1.test new file mode 100644 index 00000000..8b3b802d --- /dev/null +++ b/tests/Printer/examples/generic1.test @@ -0,0 +1,7 @@ +/** + * @param Foobar $foobar + */ +--- +/** + * ParamNode(GenericNode(ClassNode(Foobar),ListNode(ClassNode(Barfoo))),VariableNode($foobar)) + */ diff --git a/tests/Printer/examples/generic2.test b/tests/Printer/examples/generic2.test new file mode 100644 index 00000000..94017cf8 --- /dev/null +++ b/tests/Printer/examples/generic2.test @@ -0,0 +1,7 @@ +/** + * @param Foobar $foobar + */ +--- +/** + * ParamNode(GenericNode(ClassNode(Foobar),ListNode(ClassNode(Barfoo)),ScalarNode(string)),VariableNode($foobar)) + */ diff --git a/tests/Printer/examples/generic3.test b/tests/Printer/examples/generic3.test new file mode 100644 index 00000000..13b5eba0 --- /dev/null +++ b/tests/Printer/examples/generic3.test @@ -0,0 +1,7 @@ +/** + * @param Foobar,string> $foobar + */ +--- +/** + * ParamNode(GenericNode(ClassNode(Foobar),GenericNode(ClassNode(Barfoo),ScalarNode(int),ScalarNode(string)),ScalarNode(string)),VariableNode($foobar)) + */ diff --git a/tests/Printer/examples/generic4.test b/tests/Printer/examples/generic4.test new file mode 100644 index 00000000..d2e633da --- /dev/null +++ b/tests/Printer/examples/generic4.test @@ -0,0 +1,7 @@ +/** + * @param Foobar,string, Baz> $foobar + */ +--- +/** + * ParamNode(GenericNode(ClassNode(Foobar),GenericNode(ClassNode(Barfoo),ListNode(ScalarNode(int)),ListNode(ScalarNode(int))),ScalarNode(string),ClassNode(Baz)),VariableNode($foobar)) + */ diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index b9661e6c..dd223d7b 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -21,7 +21,6 @@ class ParserTest extends TestCase public function testParse(string $text, Node $expected): void { $node = (new Parser())->parse((new Lexer())->lex($text)); - dump($node); self::assertEquals($expected, $node); } @@ -31,25 +30,21 @@ public function testParse(string $text, Node $expected): void public function provideParse(): Generator { yield [ - '/** Hello */', + '/** */', new Docblock([ - new Token(0, Token::T_PHPDOC_OPEN, '/** '), - new Token(4, Token::T_LABEL, 'Hello'), - new Token(9, Token::T_WHITESPACE, ' '), - new Token(10, Token::T_PHPDOC_CLOSE, '*/'), + new Token(0, Token::T_PHPDOC_OPEN, '/**'), + new Token(3, Token::T_WHITESPACE, ' '), + new Token(4, Token::T_PHPDOC_CLOSE, '*/'), ]) ]; - yield [ - '/** @param Foobar $foobar */', + '/** Hello */', new Docblock([ - new Token(0, Token::T_PHPDOC_OPEN, '/** '), - new ParamNode( - new ClassNode(new Token(11, Token::T_LABEL, 'Foobar')), - new VariableNode(new Token(18, Token::T_VARIABLE, '$foobar')) - ), - new Token(25, Token::T_WHITESPACE, ' '), - new Token(26, Token::T_PHPDOC_CLOSE, '*/'), + new Token(0, Token::T_PHPDOC_OPEN, '/**'), + new Token(3, Token::T_WHITESPACE, ' '), + new Token(4, Token::T_LABEL, 'Hello'), + new Token(9, Token::T_WHITESPACE, ' '), + new Token(10, Token::T_PHPDOC_CLOSE, '*/'), ]) ]; } From a36dae509b2327188cbf2fc7bf393ac99c8818b8 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Fri, 22 Jan 2021 21:04:59 +0000 Subject: [PATCH 10/63] Fixed tests --- lib/Lexer.php | 8 ++++++-- tests/Unit/LexerTest.php | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index f23f91cc..0c4493ff 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -8,9 +8,9 @@ class Lexer * @var string[] */ private $patterns = [ - '^/\*+\s*\*?', // start tag + '^/\*+', // start tag '\*/', // close tag - '\s*\*', // border + '\*', // leading tag '\[\]', //tag '@\w+', //tag '\s+', // whitespace @@ -71,6 +71,10 @@ private function resolveType(string $value, ?string $prevValue): string return Token::T_PHPDOC_LEADING; } + if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { + return Token::T_PHPDOC_LEADING; + } + if (0 === strpos($value, '$')) { return Token::T_VARIABLE; } diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 624b6e5f..f086a376 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -41,7 +41,9 @@ public function provideLex(): Generator EOT ,[ - [Token::T_PHPDOC_OPEN, "/**\n *"], + [Token::T_PHPDOC_OPEN, "/**"], + [Token::T_WHITESPACE, "\n "], + [Token::T_PHPDOC_LEADING, "*"], [Token::T_WHITESPACE, " "], [Token::T_LABEL, "Hello"], [Token::T_WHITESPACE, " "], @@ -49,7 +51,8 @@ public function provideLex(): Generator [Token::T_WHITESPACE, " "], [Token::T_LABEL, "is"], [Token::T_WHITESPACE, "\n "], - [Token::T_PHPDOC_LEADING, "* "], + [Token::T_PHPDOC_LEADING, "*"], + [Token::T_WHITESPACE, " "], [Token::T_LABEL, "Multi"], [Token::T_WHITESPACE, "\n "], [Token::T_PHPDOC_CLOSE, "*/"], @@ -66,8 +69,7 @@ public function provideLex(): Generator 'Foobar[]', [ [Token::T_LABEL, "Foobar"], - [Token::T_BRACKET_SQUARE_OPEN, "["], - [Token::T_BRACKET_SQUARE_CLOSE, "]"], + [Token::T_LIST, "[]"], ] ]; yield [ From a507778144a44bee52e94cb7901c15db3a69d731 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Fri, 22 Jan 2021 23:59:55 +0000 Subject: [PATCH 11/63] Refactored --- lib/Ast/VarNode.php | 32 +++++++++ lib/Parser.php | 102 ++++++++++++++++++----------- lib/Printer/TestPrinter.php | 17 +++++ lib/Tokens.php | 72 +++++++++++++------- tests/Printer/PrinterTest.php | 5 +- tests/Printer/examples/param4.test | 7 ++ tests/Printer/examples/var1.test | 7 ++ 7 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 lib/Ast/VarNode.php create mode 100644 tests/Printer/examples/param4.test create mode 100644 tests/Printer/examples/var1.test diff --git a/lib/Ast/VarNode.php b/lib/Ast/VarNode.php new file mode 100644 index 00000000..aa25ee69 --- /dev/null +++ b/lib/Ast/VarNode.php @@ -0,0 +1,32 @@ +type = $type; + $this->variable = $variable; + } + + public function type(): ?TypeNode + { + return $this->type; + } + + public function variable(): ?VariableNode + { + return $this->variable; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index 4f751df6..5c383807 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -13,85 +13,105 @@ use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\Type\ScalarNode; use Phpactor\Docblock\Ast\UnknownTag; +use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; final class Parser { + /** + * @var Tokens + */ + private $tokens; + private const SCALAR_TYPES = [ 'int', 'float', 'bool', 'string' ]; public function parse(Tokens $tokens): Node { - $children = [$tokens->current()]; + $this->tokens = $tokens; + $children = []; - while ($token = $tokens->next()) { - assert($token instanceof Token); - if ($token->type() === Token::T_TAG) { - $children[] = $this->parseTag($token, $tokens); + while ($tokens->hasAnother()) { + if ($tokens->current()->type() === Token::T_TAG) { + $children[] = $this->parseTag(); continue; } - $children[] = $token; + $children[] = $tokens->chomp(); } return new Docblock($children); } - private function parseTag(Token $token, Tokens $tokens): TagNode + private function parseTag(): TagNode { + $token = $this->tokens->current(); + if ($token->value() === '@param') { - return $this->parseParam($tokens); + return $this->parseParam(); + } + + if ($token->value() === '@var') { + return $this->parseVar(); } return new UnknownTag($token); } - private function parseParam(Tokens $tokens): ParamNode + private function parseParam(): ParamNode { - $tokens->next(); - $type = $this->parseType($tokens); - $variable = $this->parseVariable($tokens->skip(Token::T_WHITESPACE)); + $type = $variable = null; + $this->tokens->chomp(Token::T_TAG); + + if ($this->tokens->ifNextIs(Token::T_LABEL)) { + $type = $this->parseType(); + } + if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { + $variable = $this->parseVariable(); + } return new ParamNode($type, $variable); } - private function parseType(Tokens $tokens): ?TypeNode + private function parseVar(): VarNode { - $tokens->skip(Token::T_WHITESPACE); - $isList = false; - - if (!$tokens->isType(Token::T_LABEL)) { - return null; + $this->tokens->chomp(Token::T_TAG); + $type = null; + if ($this->tokens->if(Token::T_LABEL)) { + $type = $this->parseType(); } + return new VarNode($type, null); + } - $type = $tokens->current(); + private function parseType(): ?TypeNode + { + $type = $this->tokens->chomp(Token::T_LABEL); + $isList = false; - if ($tokens->peek()->type() === Token::T_LIST) { - $tokens->next(); - $tokens->next(); - return new ListNode($this->createTypeFromToken($type), $tokens->current()); + if ($this->tokens->current()->type() === Token::T_LIST) { + $list = $this->tokens->chomp(); + return new ListNode($this->createTypeFromToken($type), $list); } - if ($tokens->peek()->type() === Token::T_BRACKET_ANGLE_OPEN) { - $open = $tokens->next(); - $tokens->next(); - $typeList = $this->parseTypeList($tokens); + if ($this->tokens->current()->type() === Token::T_BRACKET_ANGLE_OPEN) { + $open = $this->tokens->chomp(); + if ($this->tokens->if(Token::T_LABEL)) { + $typeList = $this->parseTypeList(); + } - if (!$tokens->isType(Token::T_BRACKET_ANGLE_CLOSE)) { + if ($this->tokens->current()->type() !== Token::T_BRACKET_ANGLE_CLOSE) { return null; } - $tokens->next(); return new GenericNode( $open, $this->createTypeFromToken($type), $typeList, - $tokens->current() + $this->tokens->chomp() ); } - $tokens->next(); return $this->createTypeFromToken($type); } @@ -104,27 +124,31 @@ private function createTypeFromToken(Token $type): TypeNode return new ClassNode($type); } - private function parseVariable(Tokens $tokens): ?VariableNode + private function parseVariable(): ?VariableNode { - if (!$tokens->isType(Token::T_VARIABLE)) { + if ($this->tokens->current()->type() !== Token::T_VARIABLE) { return null; } - $name = $tokens->current(); + $name = $this->tokens->chomp(Token::T_VARIABLE); return new VariableNode($name); } - private function parseTypeList(Tokens $tokens): TypeList + private function parseTypeList(): TypeList { $types = []; while (true) { - $types[] = $this->parseType($tokens); - if ($tokens->current()->type() !== Token::T_COMMA) { - break; + if ($this->tokens->if(Token::T_LABEL)) { + $types[] = $this->parseType(); + } + if ($this->tokens->if(Token::T_COMMA)) { + $this->tokens->chomp(); + continue; } - $tokens->next(); + break; } + dump($types); return new TypeList($types); } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 0edeabe0..8ac4472c 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -11,6 +11,7 @@ use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Printer; use Phpactor\Docblock\Token; @@ -54,6 +55,11 @@ private function render(?Element $node): void return; } + if ($node instanceof VarNode) { + $this->renderVar($node); + return; + } + if ($node instanceof ListNode) { $this->renderListNode($node); return; @@ -96,6 +102,17 @@ private function renderParam(ParamNode $node): void $this->out[] = ')'; } + private function renderVar(VarNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + if ($node->variable()) { + $this->out[] = ','; + $this->render($node->variable()); + } + $this->out[] = ')'; + } + private function renderTypeNode(TypeNode $node): void { $this->out[] = $node->shortName() . '('; diff --git a/lib/Tokens.php b/lib/Tokens.php index 107c67ee..cc6e2049 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -39,58 +39,80 @@ public function getIterator(): ArrayIterator return new ArrayIterator($this->tokens); } - public function peek(): ?Token + public function hasAnother(): bool { - if (!isset($this->tokens[$this->position + 1])) { - return null; - } - - return $this->tokens[$this->position + 1]; + return isset($this->tokens[$this->position + 1]); } - public function next(): ?Token + /** + * Return the current token and move the position ahead. + */ + public function chomp(?string $type = null): ?Token { - if (!isset($this->tokens[$this->position + 1])) { + if (!isset($this->tokens[$this->position])) { return null; } - return $this->tokens[++$this->position]; + $token = $this->tokens[$this->position++]; + + if (null !== $type && $token->type() !== $type) { + throw new RuntimeException(sprintf( + 'Expected type "%s" at position "%s": "%s"', + $type, $this->position, + implode('', array_map(function (Token $token) { + return $token->value(); + }, $this->tokens)) + )); + } + + return $token; } public function current(): Token { if (!isset($this->tokens[$this->position])) { throw new RuntimeException(sprintf( - 'No token at current position "%s"', - $this->position + 'No token at position "%s"', $this->position )); } return $this->tokens[$this->position]; } - /** - * Skip until all tokens of the given type - */ - public function skip(string $type): self + public function ifNextIs(string $type): bool + { + if ($this->next()->type() === $type) { + $this->position++; + return true; + } + + return false; + } + + public function if(string $type): bool { - if ($this->current()->type() !== $type) { - return $this; + if ($this->current()->type() === $type) { + return true; } - while (null !== $current = $this->next()) { - if ($current->type() === $type) { - continue; - } + if ($this->current()->type() !== Token::T_WHITESPACE) { + return false; + } - return $this; + if ($this->next()->type() === $type) { + $this->position++; + return true; } - return $this; + return false; } - public function isType(string $type): bool + public function next(): ?Token { - return $this->current()->type() === $type; + if (!isset($this->tokens[$this->position + 1])) { + return null; + } + + return $this->tokens[$this->position + 1]; } } diff --git a/tests/Printer/PrinterTest.php b/tests/Printer/PrinterTest.php index 3d0ca500..6ae9bbbd 100644 --- a/tests/Printer/PrinterTest.php +++ b/tests/Printer/PrinterTest.php @@ -21,7 +21,8 @@ public function testPrint(string $path): void $parts = explode('---', $contents); - $node = (new Parser())->parse((new Lexer())->lex($parts[0])); + $tokens = (new Lexer())->lex($parts[0]); + $node = (new Parser())->parse($tokens); $rendered = (new TestPrinter())->print($node); /** @@ -33,7 +34,7 @@ public function testPrint(string $path): void return; } - self::assertEquals(ltrim($parts[1]), $rendered); + self::assertEquals(trim($parts[1]), $rendered); } /** diff --git a/tests/Printer/examples/param4.test b/tests/Printer/examples/param4.test new file mode 100644 index 00000000..b189e757 --- /dev/null +++ b/tests/Printer/examples/param4.test @@ -0,0 +1,7 @@ +/** + * @param bool + */ +--- +/** + * ParamNode(ScalarNode(bool),#missing#) + */ diff --git a/tests/Printer/examples/var1.test b/tests/Printer/examples/var1.test new file mode 100644 index 00000000..5926e58d --- /dev/null +++ b/tests/Printer/examples/var1.test @@ -0,0 +1,7 @@ +/** + * @var string[] + */ +--- +/** + * VarNode(ListNode(ScalarNode(string))) + */ From 28459e8faf7302677e81ffc2be489d8e44ede9f9 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 00:05:38 +0000 Subject: [PATCH 12/63] Remove dump --- lib/Parser.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Parser.php b/lib/Parser.php index 5c383807..ed4ee54c 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -76,12 +76,15 @@ private function parseParam(): ParamNode private function parseVar(): VarNode { $this->tokens->chomp(Token::T_TAG); - $type = null; + $type = $variable = null; if ($this->tokens->if(Token::T_LABEL)) { $type = $this->parseType(); } + if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { + $variable = $this->parseVariable(); + } - return new VarNode($type, null); + return new VarNode($type, $variable); } private function parseType(): ?TypeNode @@ -148,7 +151,6 @@ private function parseTypeList(): TypeList } break; } - dump($types); return new TypeList($types); } From 38e0a6e387aeae6977801ccc05513d89129281a3 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 00:18:19 +0000 Subject: [PATCH 13/63] Add bench --- composer.json | 1 + tests/Benchmark/ParserBench.php | 26 ++++++++++++++++++++++++++ tests/Printer/examples/var2.test | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 tests/Benchmark/ParserBench.php create mode 100644 tests/Printer/examples/var2.test diff --git a/composer.json b/composer.json index 7405546d..39f03878 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "php": "^7.3" }, "require-dev": { + "phpbench/phpbench": "^1.0", "ergebnis/composer-normalize": "^2.0", "friendsofphp/php-cs-fixer": "^2.17", "phpstan/phpstan": "~0.12.0", diff --git a/tests/Benchmark/ParserBench.php b/tests/Benchmark/ParserBench.php new file mode 100644 index 00000000..11ac49a0 --- /dev/null +++ b/tests/Benchmark/ParserBench.php @@ -0,0 +1,26 @@ +parse((new Lexer())->lex($doc)); + } +} diff --git a/tests/Printer/examples/var2.test b/tests/Printer/examples/var2.test new file mode 100644 index 00000000..4c21c5b4 --- /dev/null +++ b/tests/Printer/examples/var2.test @@ -0,0 +1,3 @@ +/** @var string $foobar */ +--- +/** VarNode(ScalarNode(string),VariableNode($foobar)) */ From 97f060d5cafb13edd1dc9032b6841645a681b5d7 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 00:31:32 +0000 Subject: [PATCH 14/63] Use dev-master --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 39f03878..420e6c04 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": "^7.3" }, "require-dev": { - "phpbench/phpbench": "^1.0", + "phpbench/phpbench": "dev-master", "ergebnis/composer-normalize": "^2.0", "friendsofphp/php-cs-fixer": "^2.17", "phpstan/phpstan": "~0.12.0", From f54f6467a4fca86c6edf62f1dfa664018a53f24d Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 10:55:51 +0000 Subject: [PATCH 15/63] Experiment with preg_match_all lexer --- lib/Lexer.php | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index 0c4493ff..b4787054 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -8,16 +8,23 @@ class Lexer * @var string[] */ private $patterns = [ - '^/\*+', // start tag - '\*/', // close tag - '\*', // leading tag - '\[\]', //tag - '@\w+', //tag - '\s+', // whitespace - ',', // comma - '\{', '\}', '\[', '\]', '<', '>', // brackets - '\$[a-zA-Z0-9_\x80-\xff]+', // variable - '[^a-zA-Z0-9_\x80-\xff]+', // label + '^/\*+' => Token::T_PHPDOC_OPEN, + '\*/' => Token::T_PHPDOC_CLOSE, + '\*' => Token::T_PHPDOC_LEADING, + '\[\]' => Token::T_LIST, + '@\w+' => Token::T_TAG, + '\s+' => Token::T_WHITESPACE, + ',' => Token::T_COMMA, + '\{' => Token::T_BRACKET_CURLY_OPEN, + '\}' => Token::T_BRACKET_CURLY_CLOSE, + '\[' => Token::T_BRACKET_SQUARE_OPEN, + '\]' => Token::T_BRACKET_SQUARE_CLOSE, + '<' => Token::T_BRACKET_ANGLE_OPEN, + '>' => Token::T_BRACKET_ANGLE_CLOSE, + // brackets'\$[a-zA-Z0-9_\x80-\xff]+', + '\$[a-zA-Z0-9_\x80-\xff]+' => Token::T_VARIABLE, + '[A-Za-zA-Z0-9_\x80-\xff]+' => Token::T_LABEL, + // label ]; /** @@ -31,27 +38,20 @@ public function lex(string $docblock): Tokens { $pattern = sprintf( '{(%s)|%s}', - implode(')|(', $this->patterns), + implode(')|(', array_map(function (string $pattern, string $name) { + return sprintf('?P<%s>%s', $name, $pattern); + }, array_keys($this->patterns), array_values($this->patterns))), implode('|', $this->ignorePatterns) ); - $chunks = (array)preg_split( - $pattern, - $docblock, - null, - PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE - ); + preg_match_all($pattern, $docblock, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL); $tokens = []; - $prevChunk = [null,null]; - foreach ($chunks as $chunk) { - [ $value, $offset ] = $chunk; - [ $prevValue, $_ ] = $prevChunk; - $tokens[] = new Token( - $offset, - $this->resolveType($value, $prevValue), - $value - ); - $prevChunk = $chunk; + foreach ($matches as $groups) { + foreach ($groups as $name => $group) { + if (is_string($name) && $group[1] >= 0) { + $tokens[] = new Token($group[1], $name, $group[0]); + } + } } return new Tokens($tokens); From 8e8ea0bc4e4cdd05a3bc085197a55f1ea7d6e130 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 10:57:09 +0000 Subject: [PATCH 16/63] Added phpstan comparison benchmark --- composer.json | 3 +- ...rBench.php => AbstractParserBenchCase.php} | 9 +++-- tests/Benchmark/PhpactorParserBench.php | 29 ++++++++++++++++ tests/Benchmark/PhpstanParserBench.php | 34 +++++++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) rename tests/Benchmark/{ParserBench.php => AbstractParserBenchCase.php} (61%) create mode 100644 tests/Benchmark/PhpactorParserBench.php create mode 100644 tests/Benchmark/PhpstanParserBench.php diff --git a/composer.json b/composer.json index 420e6c04..93f4220e 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "phpstan/phpstan": "~0.12.0", "phpunit/phpunit": "^9.0", "phpspec/prophecy-phpunit": "^2.0", - "symfony/var-dumper": "^5.2" + "symfony/var-dumper": "^5.2", + "phpstan/phpdoc-parser": "^0.4.10" }, "extra": { "branch-alias": { diff --git a/tests/Benchmark/ParserBench.php b/tests/Benchmark/AbstractParserBenchCase.php similarity index 61% rename from tests/Benchmark/ParserBench.php rename to tests/Benchmark/AbstractParserBenchCase.php index 11ac49a0..3a3c4a14 100644 --- a/tests/Benchmark/ParserBench.php +++ b/tests/Benchmark/AbstractParserBenchCase.php @@ -8,8 +8,9 @@ /** * @Iterations(33) * @Revs(50) + * @BeforeMethods({"setUp"}) */ -class ParserBench +abstract class AbstractParserBenchCase { public function benchParse(): void { @@ -20,7 +21,9 @@ public function benchParse(): void * @param string $baz */ EOT; - $parser = new Parser(); - $parser->parse((new Lexer())->lex($doc)); + $this->parse($doc); } + + abstract public function setUp(): void; + abstract public function parse(string $doc): void; } diff --git a/tests/Benchmark/PhpactorParserBench.php b/tests/Benchmark/PhpactorParserBench.php new file mode 100644 index 00000000..94a7b4f8 --- /dev/null +++ b/tests/Benchmark/PhpactorParserBench.php @@ -0,0 +1,29 @@ +parser = new Parser(); + $this->lexer = new Lexer(); + } + public function parse(string $doc): void + { + $this->parser->parse($this->lexer->lex($doc)); + } +} diff --git a/tests/Benchmark/PhpstanParserBench.php b/tests/Benchmark/PhpstanParserBench.php new file mode 100644 index 00000000..dcf0884a --- /dev/null +++ b/tests/Benchmark/PhpstanParserBench.php @@ -0,0 +1,34 @@ +parser = new PhpDocParser(new TypeParser(), new ConstExprParser()); + $this->lexer = new Lexer(); + } + + public function parse(string $doc): void + { + $tokens = new TokenIterator($this->lexer->tokenize($doc)); + $this->parser->parse($tokens); + } +} From 339ce227b8c8e2d3cf9c5ef539ab868cfafa28c4 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 10:57:24 +0000 Subject: [PATCH 17/63] Revert "Experiment with preg_match_all lexer" This reverts commit f54f6467a4fca86c6edf62f1dfa664018a53f24d. --- lib/Lexer.php | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index b4787054..0c4493ff 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -8,23 +8,16 @@ class Lexer * @var string[] */ private $patterns = [ - '^/\*+' => Token::T_PHPDOC_OPEN, - '\*/' => Token::T_PHPDOC_CLOSE, - '\*' => Token::T_PHPDOC_LEADING, - '\[\]' => Token::T_LIST, - '@\w+' => Token::T_TAG, - '\s+' => Token::T_WHITESPACE, - ',' => Token::T_COMMA, - '\{' => Token::T_BRACKET_CURLY_OPEN, - '\}' => Token::T_BRACKET_CURLY_CLOSE, - '\[' => Token::T_BRACKET_SQUARE_OPEN, - '\]' => Token::T_BRACKET_SQUARE_CLOSE, - '<' => Token::T_BRACKET_ANGLE_OPEN, - '>' => Token::T_BRACKET_ANGLE_CLOSE, - // brackets'\$[a-zA-Z0-9_\x80-\xff]+', - '\$[a-zA-Z0-9_\x80-\xff]+' => Token::T_VARIABLE, - '[A-Za-zA-Z0-9_\x80-\xff]+' => Token::T_LABEL, - // label + '^/\*+', // start tag + '\*/', // close tag + '\*', // leading tag + '\[\]', //tag + '@\w+', //tag + '\s+', // whitespace + ',', // comma + '\{', '\}', '\[', '\]', '<', '>', // brackets + '\$[a-zA-Z0-9_\x80-\xff]+', // variable + '[^a-zA-Z0-9_\x80-\xff]+', // label ]; /** @@ -38,20 +31,27 @@ public function lex(string $docblock): Tokens { $pattern = sprintf( '{(%s)|%s}', - implode(')|(', array_map(function (string $pattern, string $name) { - return sprintf('?P<%s>%s', $name, $pattern); - }, array_keys($this->patterns), array_values($this->patterns))), + implode(')|(', $this->patterns), implode('|', $this->ignorePatterns) ); + $chunks = (array)preg_split( + $pattern, + $docblock, + null, + PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE + ); - preg_match_all($pattern, $docblock, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL); $tokens = []; - foreach ($matches as $groups) { - foreach ($groups as $name => $group) { - if (is_string($name) && $group[1] >= 0) { - $tokens[] = new Token($group[1], $name, $group[0]); - } - } + $prevChunk = [null,null]; + foreach ($chunks as $chunk) { + [ $value, $offset ] = $chunk; + [ $prevValue, $_ ] = $prevChunk; + $tokens[] = new Token( + $offset, + $this->resolveType($value, $prevValue), + $value + ); + $prevChunk = $chunk; } return new Tokens($tokens); From 9cf4db7001d4358a07bd4d820412644e61535a28 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 11:02:34 +0000 Subject: [PATCH 18/63] Fixed parser test --- lib/Parser.php | 2 +- lib/Tokens.php | 5 +++++ tests/Printer/PrinterTest.php | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Parser.php b/lib/Parser.php index ed4ee54c..67f9f040 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -32,7 +32,7 @@ public function parse(Tokens $tokens): Node $this->tokens = $tokens; $children = []; - while ($tokens->hasAnother()) { + while ($tokens->hasCurrent()) { if ($tokens->current()->type() === Token::T_TAG) { $children[] = $this->parseTag(); continue; diff --git a/lib/Tokens.php b/lib/Tokens.php index cc6e2049..cbf44cf5 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -39,6 +39,11 @@ public function getIterator(): ArrayIterator return new ArrayIterator($this->tokens); } + public function hasCurrent(): bool + { + return isset($this->tokens[$this->position]); + } + public function hasAnother(): bool { return isset($this->tokens[$this->position + 1]); diff --git a/tests/Printer/PrinterTest.php b/tests/Printer/PrinterTest.php index 6ae9bbbd..501d5077 100644 --- a/tests/Printer/PrinterTest.php +++ b/tests/Printer/PrinterTest.php @@ -34,7 +34,7 @@ public function testPrint(string $path): void return; } - self::assertEquals(trim($parts[1]), $rendered); + self::assertEquals(trim($parts[1]), trim($rendered)); } /** From c7797e5f127e6334b5b93e1ed6a26b412d5f2a6a Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 11:13:22 +0000 Subject: [PATCH 19/63] Optimisations --- lib/Lexer.php | 75 +++++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index 0c4493ff..991d3b76 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -20,6 +20,17 @@ class Lexer '[^a-zA-Z0-9_\x80-\xff]+', // label ]; + private $tokenValueMap = [ + ']' => Token::T_BRACKET_SQUARE_CLOSE, + '[' => Token::T_BRACKET_SQUARE_OPEN, + '>' => Token::T_BRACKET_ANGLE_CLOSE, + '<' => Token::T_BRACKET_ANGLE_OPEN, + '{' => Token::T_BRACKET_CURLY_OPEN, + '}' => Token::T_BRACKET_CURLY_CLOSE, + ',' => Token::T_COMMA, + '[]' => Token::T_LIST, + ]; + /** * @var string[] */ @@ -59,27 +70,33 @@ public function lex(string $docblock): Tokens private function resolveType(string $value, ?string $prevValue): string { - if (false !== strpos($value, '/*')) { - return Token::T_PHPDOC_OPEN; - } + // performance: saves around 7% + // + // if (false !== strpos($value, '/*')) { + // return Token::T_PHPDOC_OPEN; + // } - if (false !== strpos($value, '*/')) { - return Token::T_PHPDOC_CLOSE; - } + // if (false !== strpos($value, '*/')) { + // return Token::T_PHPDOC_CLOSE; + // } - if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { - return Token::T_PHPDOC_LEADING; - } + // if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { + // return Token::T_PHPDOC_LEADING; + // } + + // if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { + // return Token::T_PHPDOC_LEADING; + // } - if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { - return Token::T_PHPDOC_LEADING; + if (array_key_exists($value, $this->tokenValueMap)) { + return $this->tokenValueMap[$value]; } - if (0 === strpos($value, '$')) { + if ($value[0] === '$') { return Token::T_VARIABLE; } - if (0 === strpos($value, '@')) { + if ($value[0] === '@') { return Token::T_TAG; } @@ -95,38 +112,6 @@ private function resolveType(string $value, ?string $prevValue): string return Token::T_LABEL; } - if ($value === ']') { - return Token::T_BRACKET_SQUARE_CLOSE; - } - - if ($value === '[') { - return Token::T_BRACKET_SQUARE_OPEN; - } - - if ($value === '<') { - return Token::T_BRACKET_ANGLE_OPEN; - } - - if ($value === '>') { - return Token::T_BRACKET_ANGLE_CLOSE; - } - - if ($value === '{') { - return Token::T_BRACKET_CURLY_OPEN; - } - - if ($value === '}') { - return Token::T_BRACKET_CURLY_CLOSE; - } - - if ($value === ',') { - return Token::T_COMMA; - } - - if ($value === '[]') { - return Token::T_LIST; - } - return Token::T_UNKNOWN; } From 61d6dbf192823f02044aed6166ed376affca01cf Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 11:31:43 +0000 Subject: [PATCH 20/63] Performance optimization --- lib/Lexer.php | 63 +++++++++++++++++++++------------------ tests/Unit/LexerTest.php | 2 +- tests/Unit/ParserTest.php | 2 +- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index 991d3b76..6ce16e9f 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -7,7 +7,7 @@ class Lexer /** * @var string[] */ - private $patterns = [ + private const PATTERNS = [ '^/\*+', // start tag '\*/', // close tag '\*', // leading tag @@ -20,7 +20,7 @@ class Lexer '[^a-zA-Z0-9_\x80-\xff]+', // label ]; - private $tokenValueMap = [ + private const TOKEN_VALUE_MAP = [ ']' => Token::T_BRACKET_SQUARE_CLOSE, '[' => Token::T_BRACKET_SQUARE_OPEN, '>' => Token::T_BRACKET_ANGLE_CLOSE, @@ -34,16 +34,26 @@ class Lexer /** * @var string[] */ - private $ignorePatterns = [ + private const IGNORE_PATTERNS = [ '\s+' ]; + /** + * @var bool + */ + private $includeSurrounding; + + public function __construct(bool $includeSurrounding = false) + { + $this->includeSurrounding = $includeSurrounding; + } + public function lex(string $docblock): Tokens { $pattern = sprintf( '{(%s)|%s}', - implode(')|(', $this->patterns), - implode('|', $this->ignorePatterns) + implode(')|(', self::PATTERNS), + implode('|', self::IGNORE_PATTERNS) ); $chunks = (array)preg_split( $pattern, @@ -53,13 +63,12 @@ public function lex(string $docblock): Tokens ); $tokens = []; - $prevChunk = [null,null]; + $prevChunk = null; foreach ($chunks as $chunk) { [ $value, $offset ] = $chunk; - [ $prevValue, $_ ] = $prevChunk; $tokens[] = new Token( $offset, - $this->resolveType($value, $prevValue), + $this->resolveType($value, $prevChunk), $value ); $prevChunk = $chunk; @@ -68,28 +77,24 @@ public function lex(string $docblock): Tokens return new Tokens($tokens); } - private function resolveType(string $value, ?string $prevValue): string + private function resolveType(string $value, ?array $prevChunk = null): string { - // performance: saves around 7% - // - // if (false !== strpos($value, '/*')) { - // return Token::T_PHPDOC_OPEN; - // } - - // if (false !== strpos($value, '*/')) { - // return Token::T_PHPDOC_CLOSE; - // } - - // if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { - // return Token::T_PHPDOC_LEADING; - // } - - // if ($prevValue && 0 === strpos($prevValue, "\n") && trim($value) === '*') { - // return Token::T_PHPDOC_LEADING; - // } - - if (array_key_exists($value, $this->tokenValueMap)) { - return $this->tokenValueMap[$value]; + if ($this->includeSurrounding) { + if (false !== strpos($value, '/*')) { + return Token::T_PHPDOC_OPEN; + } + + if (false !== strpos($value, '*/')) { + return Token::T_PHPDOC_CLOSE; + } + + if ($prevChunk && 0 === strpos($prevChunk[0], "\n") && trim($value) === '*') { + return Token::T_PHPDOC_LEADING; + } + } + + if (array_key_exists($value, self::TOKEN_VALUE_MAP)) { + return self::TOKEN_VALUE_MAP[$value]; } if ($value[0] === '$') { diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index f086a376..056e0af4 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -15,7 +15,7 @@ class LexerTest extends TestCase */ public function testLex(string $lex, array $expectedTokens): void { - $tokens = (new Lexer())->lex($lex)->toArray(); + $tokens = (new Lexer(true))->lex($lex)->toArray(); self::assertCount(count($expectedTokens), $tokens, 'Expected number of tokens'); diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index dd223d7b..73c501e9 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -20,7 +20,7 @@ class ParserTest extends TestCase */ public function testParse(string $text, Node $expected): void { - $node = (new Parser())->parse((new Lexer())->lex($text)); + $node = (new Parser())->parse((new Lexer(true))->lex($text)); self::assertEquals($expected, $node); } From ba68f54aecbcdbfdce38008fcf66dc27b97133d4 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 11:43:11 +0000 Subject: [PATCH 21/63] Use token properties --- lib/Parser.php | 16 ++++++++-------- lib/Printer/TestPrinter.php | 2 +- lib/Token.php | 21 +++------------------ lib/Tokens.php | 10 +++++----- tests/Unit/LexerTest.php | 2 +- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/lib/Parser.php b/lib/Parser.php index 67f9f040..b90b9e7b 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -33,7 +33,7 @@ public function parse(Tokens $tokens): Node $children = []; while ($tokens->hasCurrent()) { - if ($tokens->current()->type() === Token::T_TAG) { + if ($tokens->current()->type === Token::T_TAG) { $children[] = $this->parseTag(); continue; } @@ -47,11 +47,11 @@ private function parseTag(): TagNode { $token = $this->tokens->current(); - if ($token->value() === '@param') { + if ($token->value === '@param') { return $this->parseParam(); } - if ($token->value() === '@var') { + if ($token->value === '@var') { return $this->parseVar(); } @@ -92,18 +92,18 @@ private function parseType(): ?TypeNode $type = $this->tokens->chomp(Token::T_LABEL); $isList = false; - if ($this->tokens->current()->type() === Token::T_LIST) { + if ($this->tokens->current()->type === Token::T_LIST) { $list = $this->tokens->chomp(); return new ListNode($this->createTypeFromToken($type), $list); } - if ($this->tokens->current()->type() === Token::T_BRACKET_ANGLE_OPEN) { + if ($this->tokens->current()->type === Token::T_BRACKET_ANGLE_OPEN) { $open = $this->tokens->chomp(); if ($this->tokens->if(Token::T_LABEL)) { $typeList = $this->parseTypeList(); } - if ($this->tokens->current()->type() !== Token::T_BRACKET_ANGLE_CLOSE) { + if ($this->tokens->current()->type !== Token::T_BRACKET_ANGLE_CLOSE) { return null; } @@ -120,7 +120,7 @@ private function parseType(): ?TypeNode private function createTypeFromToken(Token $type): TypeNode { - if (in_array($type->value(), self::SCALAR_TYPES)) { + if (in_array($type->value, self::SCALAR_TYPES)) { return new ScalarNode($type); } @@ -129,7 +129,7 @@ private function createTypeFromToken(Token $type): TypeNode private function parseVariable(): ?VariableNode { - if ($this->tokens->current()->type() !== Token::T_VARIABLE) { + if ($this->tokens->current()->type !== Token::T_VARIABLE) { return null; } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 8ac4472c..dccadb6c 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -46,7 +46,7 @@ private function render(?Element $node): void } if ($node instanceof Token) { - $this->out[] = $node->value(); + $this->out[] = $node->value; return; } diff --git a/lib/Token.php b/lib/Token.php index 517ca24d..4d97cd66 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -29,17 +29,17 @@ final class Token implements Element /** * @var int */ - private $byteOffset; + public $byteOffset; /** * @var string */ - private $type; + public $type; /** * @var string */ - private $value; + public $value; public function __construct(int $byteOffset, string $type, string $value) { @@ -48,21 +48,6 @@ public function __construct(int $byteOffset, string $type, string $value) $this->value = $value; } - public function byteOffset(): int - { - return $this->byteOffset; - } - - public function value(): string - { - return $this->value; - } - - public function type(): string - { - return $this->type; - } - public function __toString(): string { return $this->value; diff --git a/lib/Tokens.php b/lib/Tokens.php index cbf44cf5..61d64e42 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -60,7 +60,7 @@ public function chomp(?string $type = null): ?Token $token = $this->tokens[$this->position++]; - if (null !== $type && $token->type() !== $type) { + if (null !== $type && $token->type !== $type) { throw new RuntimeException(sprintf( 'Expected type "%s" at position "%s": "%s"', $type, $this->position, @@ -86,7 +86,7 @@ public function current(): Token public function ifNextIs(string $type): bool { - if ($this->next()->type() === $type) { + if ($this->next()->type === $type) { $this->position++; return true; } @@ -96,15 +96,15 @@ public function ifNextIs(string $type): bool public function if(string $type): bool { - if ($this->current()->type() === $type) { + if ($this->current()->type === $type) { return true; } - if ($this->current()->type() !== Token::T_WHITESPACE) { + if ($this->current()->type !== Token::T_WHITESPACE) { return false; } - if ($this->next()->type() === $type) { + if ($this->next()->type === $type) { $this->position++; return true; } diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 056e0af4..1655bef4 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -21,7 +21,7 @@ public function testLex(string $lex, array $expectedTokens): void foreach ($tokens as $index => $token) { [$type, $value] = $expectedTokens[$index]; - $expectedToken = new Token($token->byteOffset(), $type, $value); + $expectedToken = new Token($token->byteOffset, $type, $value); self::assertEquals($expectedToken, $token); } } From a7e599b56cfff399040b03eb344fe759cbdf9731 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 11:53:46 +0000 Subject: [PATCH 22/63] Access current as property --- lib/Parser.php | 12 ++++++------ lib/Tokens.php | 30 ++++++++++++++---------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/Parser.php b/lib/Parser.php index b90b9e7b..cf9bb035 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -33,7 +33,7 @@ public function parse(Tokens $tokens): Node $children = []; while ($tokens->hasCurrent()) { - if ($tokens->current()->type === Token::T_TAG) { + if ($tokens->current->type === Token::T_TAG) { $children[] = $this->parseTag(); continue; } @@ -45,7 +45,7 @@ public function parse(Tokens $tokens): Node private function parseTag(): TagNode { - $token = $this->tokens->current(); + $token = $this->tokens->current; if ($token->value === '@param') { return $this->parseParam(); @@ -92,18 +92,18 @@ private function parseType(): ?TypeNode $type = $this->tokens->chomp(Token::T_LABEL); $isList = false; - if ($this->tokens->current()->type === Token::T_LIST) { + if ($this->tokens->current->type === Token::T_LIST) { $list = $this->tokens->chomp(); return new ListNode($this->createTypeFromToken($type), $list); } - if ($this->tokens->current()->type === Token::T_BRACKET_ANGLE_OPEN) { + if ($this->tokens->current->type === Token::T_BRACKET_ANGLE_OPEN) { $open = $this->tokens->chomp(); if ($this->tokens->if(Token::T_LABEL)) { $typeList = $this->parseTypeList(); } - if ($this->tokens->current()->type !== Token::T_BRACKET_ANGLE_CLOSE) { + if ($this->tokens->current->type !== Token::T_BRACKET_ANGLE_CLOSE) { return null; } @@ -129,7 +129,7 @@ private function createTypeFromToken(Token $type): TypeNode private function parseVariable(): ?VariableNode { - if ($this->tokens->current()->type !== Token::T_VARIABLE) { + if ($this->tokens->current->type !== Token::T_VARIABLE) { return null; } diff --git a/lib/Tokens.php b/lib/Tokens.php index 61d64e42..24e47ec0 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -13,6 +13,11 @@ final class Tokens implements IteratorAggregate */ private $tokens; + /** + * @var ?Token + */ + public $current; + private $position = 0; /** @@ -21,6 +26,9 @@ final class Tokens implements IteratorAggregate public function __construct(array $tokens) { $this->tokens = $tokens; + if (count($tokens)) { + $this->current = $tokens[$this->position]; + } } /** @@ -59,13 +67,14 @@ public function chomp(?string $type = null): ?Token } $token = $this->tokens[$this->position++]; + $this->current = @$this->tokens[$this->position]; if (null !== $type && $token->type !== $type) { throw new RuntimeException(sprintf( 'Expected type "%s" at position "%s": "%s"', $type, $this->position, implode('', array_map(function (Token $token) { - return $token->value(); + return $token->value; }, $this->tokens)) )); } @@ -73,21 +82,10 @@ public function chomp(?string $type = null): ?Token return $token; } - public function current(): Token - { - if (!isset($this->tokens[$this->position])) { - throw new RuntimeException(sprintf( - 'No token at position "%s"', $this->position - )); - } - - return $this->tokens[$this->position]; - } - public function ifNextIs(string $type): bool { if ($this->next()->type === $type) { - $this->position++; + $this->current = @$this->tokens[++$this->position]; return true; } @@ -96,16 +94,16 @@ public function ifNextIs(string $type): bool public function if(string $type): bool { - if ($this->current()->type === $type) { + if ($this->current->type === $type) { return true; } - if ($this->current()->type !== Token::T_WHITESPACE) { + if ($this->current->type !== Token::T_WHITESPACE) { return false; } if ($this->next()->type === $type) { - $this->position++; + $this->current = $this->tokens[++$this->position]; return true; } From 9da77bfe99d7a2114f1ebd1309dd09cf2152f3aa Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 12:40:26 +0000 Subject: [PATCH 23/63] Add type hint --- lib/Tokens.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Tokens.php b/lib/Tokens.php index 24e47ec0..2666a84a 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -18,6 +18,9 @@ final class Tokens implements IteratorAggregate */ public $current; + /** + * @var int + */ private $position = 0; /** From 3aae081668877f973bef73ffced206b3a4b93dcf Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 12:46:18 +0000 Subject: [PATCH 24/63] Create pattern in constructor --- lib/Lexer.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index 6ce16e9f..cadcdbd1 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -43,20 +43,28 @@ class Lexer */ private $includeSurrounding; + /** + * @var string + */ + private $pattern; + + /** + * @param bool $includeSurrounding Tokenize DOCBLOCK_ tags, decreases performance. + */ public function __construct(bool $includeSurrounding = false) { $this->includeSurrounding = $includeSurrounding; - } - - public function lex(string $docblock): Tokens - { - $pattern = sprintf( + $this->pattern = sprintf( '{(%s)|%s}', implode(')|(', self::PATTERNS), implode('|', self::IGNORE_PATTERNS) ); + } + + public function lex(string $docblock): Tokens + { $chunks = (array)preg_split( - $pattern, + $this->pattern, $docblock, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE From 27cf88b2bce6273e6611594bec550f3f558bbfe5 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 13:17:58 +0000 Subject: [PATCH 25/63] Support param text --- lib/Ast/ParamNode.php | 13 ++++++++++++- lib/Ast/TextNode.php | 28 ++++++++++++++++++++++++++++ lib/Parser.php | 29 +++++++++++++++++++++++++++-- lib/Printer/TestPrinter.php | 17 +++++++++++++++++ lib/Tokens.php | 4 ++++ tests/Printer/PrinterTest.php | 2 +- tests/Printer/examples/param5.test | 7 +++++++ tests/Printer/examples/param6.test | 11 +++++++++++ tests/Printer/examples/var3.test | 3 +++ 9 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 lib/Ast/TextNode.php create mode 100644 tests/Printer/examples/param5.test create mode 100644 tests/Printer/examples/param6.test create mode 100644 tests/Printer/examples/var3.test diff --git a/lib/Ast/ParamNode.php b/lib/Ast/ParamNode.php index e2bac69e..0ed5dade 100644 --- a/lib/Ast/ParamNode.php +++ b/lib/Ast/ParamNode.php @@ -14,10 +14,16 @@ class ParamNode extends TagNode */ private $variable; - public function __construct(?TypeNode $type, ?VariableNode $variable) + /** + * @var TextNode|null + */ + private $text; + + public function __construct(?TypeNode $type, ?VariableNode $variable, ?TextNode $text = null) { $this->type = $type; $this->variable = $variable; + $this->text = $text; } public function type(): ?TypeNode @@ -29,4 +35,9 @@ public function variable(): ?VariableNode { return $this->variable; } + + public function text(): ?TextNode + { + return $this->text; + } } diff --git a/lib/Ast/TextNode.php b/lib/Ast/TextNode.php new file mode 100644 index 00000000..3855ebe3 --- /dev/null +++ b/lib/Ast/TextNode.php @@ -0,0 +1,28 @@ +tokens = $tokens; + } + + public function toString(): string + { + return implode('', array_map(function (Token $token) { + return $token->value; + }, $this->tokens)); + } +} diff --git a/lib/Parser.php b/lib/Parser.php index cf9bb035..59f313d2 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -3,6 +3,7 @@ namespace Phpactor\Docblock; use Phpactor\Docblock\Ast\Docblock; +use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; @@ -60,7 +61,7 @@ private function parseTag(): TagNode private function parseParam(): ParamNode { - $type = $variable = null; + $type = $variable = $textNode = null; $this->tokens->chomp(Token::T_TAG); if ($this->tokens->ifNextIs(Token::T_LABEL)) { @@ -70,7 +71,31 @@ private function parseParam(): ParamNode $variable = $this->parseVariable(); } - return new ParamNode($type, $variable); + $text = []; + if ( + $this->tokens->current->type === Token::T_WHITESPACE && + $this->tokens->next()->type === Token::T_LABEL + ) { + $this->tokens->chomp(); + } + while ($this->tokens->current) { + if ($this->tokens->current->type === Token::T_PHPDOC_CLOSE) { + break; + } + if ($this->tokens->current->type === Token::T_PHPDOC_LEADING) { + break; + } + if (false !== strpos($this->tokens->current->value, "\n")) { + break; + } + $text[] = $this->tokens->chomp(); + } + + if ($text) { + $textNode = new TextNode($text); + } + + return new ParamNode($type, $variable, $textNode); } private function parseVar(): VarNode diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index dccadb6c..208ee968 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -4,6 +4,7 @@ use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; +use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -75,6 +76,11 @@ private function render(?Element $node): void return; } + if ($node instanceof TextNode) { + $this->renderTextNode($node); + return; + } + if ($node instanceof VariableNode) { $this->renderVariableNode($node); return; @@ -99,6 +105,10 @@ private function renderParam(ParamNode $node): void $this->render($node->type()); $this->out[] = ','; $this->render($node->variable()); + if ($node->text()) { + $this->out[] = ','; + $this->render($node->text()); + } $this->out[] = ')'; } @@ -152,4 +162,11 @@ private function renderTypeList(TypeList $typeList): void } } } + + private function renderTextNode(TextNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->out[] = $node->toString(); + $this->out[] = ')'; + } } diff --git a/lib/Tokens.php b/lib/Tokens.php index 2666a84a..c5bba974 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -95,6 +95,10 @@ public function ifNextIs(string $type): bool return false; } + /** + * If the current or next non-whitespace node matches, + * advance internal pointer and return true; + */ public function if(string $type): bool { if ($this->current->type === $type) { diff --git a/tests/Printer/PrinterTest.php b/tests/Printer/PrinterTest.php index 501d5077..6652fcd2 100644 --- a/tests/Printer/PrinterTest.php +++ b/tests/Printer/PrinterTest.php @@ -21,7 +21,7 @@ public function testPrint(string $path): void $parts = explode('---', $contents); - $tokens = (new Lexer())->lex($parts[0]); + $tokens = (new Lexer(true))->lex($parts[0]); $node = (new Parser())->parse($tokens); $rendered = (new TestPrinter())->print($node); diff --git a/tests/Printer/examples/param5.test b/tests/Printer/examples/param5.test new file mode 100644 index 00000000..477e13bd --- /dev/null +++ b/tests/Printer/examples/param5.test @@ -0,0 +1,7 @@ +/** + * @param bool $bool This is a boolean + */ +--- +/** + * ParamNode(ScalarNode(bool),VariableNode($bool),TextNode(This is a boolean)) + */ diff --git a/tests/Printer/examples/param6.test b/tests/Printer/examples/param6.test new file mode 100644 index 00000000..5e3e4f75 --- /dev/null +++ b/tests/Printer/examples/param6.test @@ -0,0 +1,11 @@ +/** + * @param bool $bool This is a boolean + * multiline comments not currently supported + * @param bool $bool This is a boolean + */ +--- +/** + * ParamNode(ScalarNode(bool),VariableNode($bool),TextNode(This is a boolean)) + * multiline comments not currently supported + * ParamNode(ScalarNode(bool),VariableNode($bool),TextNode(This is a boolean)) + */ diff --git a/tests/Printer/examples/var3.test b/tests/Printer/examples/var3.test new file mode 100644 index 00000000..4c21c5b4 --- /dev/null +++ b/tests/Printer/examples/var3.test @@ -0,0 +1,3 @@ +/** @var string $foobar */ +--- +/** VarNode(ScalarNode(string),VariableNode($foobar)) */ From 4670c87bd97c6733e6dc9975f410942295c1fe93 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 13:21:54 +0000 Subject: [PATCH 26/63] Remove skip docblock tokens --- lib/Lexer.php | 35 ++++++++++++----------------------- tests/Printer/PrinterTest.php | 2 +- tests/Unit/LexerTest.php | 2 +- tests/Unit/ParserTest.php | 2 +- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index cadcdbd1..1e7297ae 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -2,7 +2,7 @@ namespace Phpactor\Docblock; -class Lexer +final class Lexer { /** * @var string[] @@ -38,22 +38,13 @@ class Lexer '\s+' ]; - /** - * @var bool - */ - private $includeSurrounding; - /** * @var string */ private $pattern; - /** - * @param bool $includeSurrounding Tokenize DOCBLOCK_ tags, decreases performance. - */ - public function __construct(bool $includeSurrounding = false) + public function __construct() { - $this->includeSurrounding = $includeSurrounding; $this->pattern = sprintf( '{(%s)|%s}', implode(')|(', self::PATTERNS), @@ -87,18 +78,16 @@ public function lex(string $docblock): Tokens private function resolveType(string $value, ?array $prevChunk = null): string { - if ($this->includeSurrounding) { - if (false !== strpos($value, '/*')) { - return Token::T_PHPDOC_OPEN; - } - - if (false !== strpos($value, '*/')) { - return Token::T_PHPDOC_CLOSE; - } - - if ($prevChunk && 0 === strpos($prevChunk[0], "\n") && trim($value) === '*') { - return Token::T_PHPDOC_LEADING; - } + if (false !== strpos($value, '/*')) { + return Token::T_PHPDOC_OPEN; + } + + if (false !== strpos($value, '*/')) { + return Token::T_PHPDOC_CLOSE; + } + + if ($prevChunk && 0 === strpos($prevChunk[0], "\n") && trim($value) === '*') { + return Token::T_PHPDOC_LEADING; } if (array_key_exists($value, self::TOKEN_VALUE_MAP)) { diff --git a/tests/Printer/PrinterTest.php b/tests/Printer/PrinterTest.php index 6652fcd2..501d5077 100644 --- a/tests/Printer/PrinterTest.php +++ b/tests/Printer/PrinterTest.php @@ -21,7 +21,7 @@ public function testPrint(string $path): void $parts = explode('---', $contents); - $tokens = (new Lexer(true))->lex($parts[0]); + $tokens = (new Lexer())->lex($parts[0]); $node = (new Parser())->parse($tokens); $rendered = (new TestPrinter())->print($node); diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 1655bef4..d5ff9478 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -15,7 +15,7 @@ class LexerTest extends TestCase */ public function testLex(string $lex, array $expectedTokens): void { - $tokens = (new Lexer(true))->lex($lex)->toArray(); + $tokens = (new Lexer())->lex($lex)->toArray(); self::assertCount(count($expectedTokens), $tokens, 'Expected number of tokens'); diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index 73c501e9..dd223d7b 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -20,7 +20,7 @@ class ParserTest extends TestCase */ public function testParse(string $text, Node $expected): void { - $node = (new Parser())->parse((new Lexer(true))->lex($text)); + $node = (new Parser())->parse((new Lexer())->lex($text)); self::assertEquals($expected, $node); } From d01f5e990202bd3e6560cfdaf3f45320f1660800 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 13:25:58 +0000 Subject: [PATCH 27/63] Add ignored tests --- tests/Printer/PrinterTest.php | 5 +++++ tests/Printer/examples/deprecated1.test | 1 + tests/Printer/examples/method1.test | 1 + tests/Printer/examples/mixin1.test | 1 + tests/Printer/examples/nullable1.test | 1 + tests/Printer/examples/property1.test | 1 + tests/Printer/examples/return1.test | 1 + tests/Printer/examples/union1.test | 1 + 8 files changed, 12 insertions(+) create mode 100644 tests/Printer/examples/deprecated1.test create mode 100644 tests/Printer/examples/method1.test create mode 100644 tests/Printer/examples/mixin1.test create mode 100644 tests/Printer/examples/nullable1.test create mode 100644 tests/Printer/examples/property1.test create mode 100644 tests/Printer/examples/return1.test create mode 100644 tests/Printer/examples/union1.test diff --git a/tests/Printer/PrinterTest.php b/tests/Printer/PrinterTest.php index 501d5077..3e2073ac 100644 --- a/tests/Printer/PrinterTest.php +++ b/tests/Printer/PrinterTest.php @@ -21,6 +21,11 @@ public function testPrint(string $path): void $parts = explode('---', $contents); + if (empty($parts[0])) { + $this->markTestIncomplete(sprintf('No example given for "%s"', $path)); + return; + } + $tokens = (new Lexer())->lex($parts[0]); $node = (new Parser())->parse($tokens); $rendered = (new TestPrinter())->print($node); diff --git a/tests/Printer/examples/deprecated1.test b/tests/Printer/examples/deprecated1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/deprecated1.test @@ -0,0 +1 @@ +--- diff --git a/tests/Printer/examples/method1.test b/tests/Printer/examples/method1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/method1.test @@ -0,0 +1 @@ +--- diff --git a/tests/Printer/examples/mixin1.test b/tests/Printer/examples/mixin1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/mixin1.test @@ -0,0 +1 @@ +--- diff --git a/tests/Printer/examples/nullable1.test b/tests/Printer/examples/nullable1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/nullable1.test @@ -0,0 +1 @@ +--- diff --git a/tests/Printer/examples/property1.test b/tests/Printer/examples/property1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/property1.test @@ -0,0 +1 @@ +--- diff --git a/tests/Printer/examples/return1.test b/tests/Printer/examples/return1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/return1.test @@ -0,0 +1 @@ +--- diff --git a/tests/Printer/examples/union1.test b/tests/Printer/examples/union1.test new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/tests/Printer/examples/union1.test @@ -0,0 +1 @@ +--- From b9258c921e83589a6802b1f5c7b479f1e89385ca Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 13:30:33 +0000 Subject: [PATCH 28/63] support unknown tag --- lib/Parser.php | 2 +- lib/Printer/TestPrinter.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Parser.php b/lib/Parser.php index 59f313d2..06d48f6c 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -56,7 +56,7 @@ private function parseTag(): TagNode return $this->parseVar(); } - return new UnknownTag($token); + return new UnknownTag($this->tokens->chomp()); } private function parseParam(): ParamNode diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 208ee968..243e2d4f 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -12,6 +12,7 @@ use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Printer; @@ -86,6 +87,11 @@ private function render(?Element $node): void return; } + if ($node instanceof UnknownTag) { + $this->out[] = $node->shortName(); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) From f1b65b4a603922b955975476f98a82e61ee3c399 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 13:37:19 +0000 Subject: [PATCH 29/63] Support deprecated tag --- lib/Ast/DeprecatedNode.php | 21 ++++++++ lib/Parser.php | 66 +++++++++++++++---------- lib/Printer/TestPrinter.php | 15 ++++++ tests/Printer/examples/deprecated1.test | 6 +++ tests/Printer/examples/deprecated2.test | 7 +++ 5 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 lib/Ast/DeprecatedNode.php create mode 100644 tests/Printer/examples/deprecated2.test diff --git a/lib/Ast/DeprecatedNode.php b/lib/Ast/DeprecatedNode.php new file mode 100644 index 00000000..ea2604fe --- /dev/null +++ b/lib/Ast/DeprecatedNode.php @@ -0,0 +1,21 @@ +text = $text; + } + + public function text(): ?TextNode + { + return $this->text; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index 06d48f6c..ca50a697 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -2,6 +2,7 @@ namespace Phpactor\Docblock; +use Phpactor\Docblock\Ast\DeprecatedNode; use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; @@ -56,6 +57,10 @@ private function parseTag(): TagNode return $this->parseVar(); } + if ($token->value === '@deprecated') { + return $this->parseDeprecated(); + } + return new UnknownTag($this->tokens->chomp()); } @@ -71,31 +76,7 @@ private function parseParam(): ParamNode $variable = $this->parseVariable(); } - $text = []; - if ( - $this->tokens->current->type === Token::T_WHITESPACE && - $this->tokens->next()->type === Token::T_LABEL - ) { - $this->tokens->chomp(); - } - while ($this->tokens->current) { - if ($this->tokens->current->type === Token::T_PHPDOC_CLOSE) { - break; - } - if ($this->tokens->current->type === Token::T_PHPDOC_LEADING) { - break; - } - if (false !== strpos($this->tokens->current->value, "\n")) { - break; - } - $text[] = $this->tokens->chomp(); - } - - if ($text) { - $textNode = new TextNode($text); - } - - return new ParamNode($type, $variable, $textNode); + return new ParamNode($type, $variable, $this->parseText()); } private function parseVar(): VarNode @@ -179,4 +160,39 @@ private function parseTypeList(): TypeList return new TypeList($types); } + + private function parseDeprecated(): DeprecatedNode + { + $this->tokens->chomp(); + return new DeprecatedNode($this->parseText()); + } + + private function parseText(): ?TextNode + { + $text = []; + if ( + $this->tokens->current->type === Token::T_WHITESPACE && + $this->tokens->next()->type === Token::T_LABEL + ) { + $this->tokens->chomp(); + } + while ($this->tokens->current) { + if ($this->tokens->current->type === Token::T_PHPDOC_CLOSE) { + break; + } + if ($this->tokens->current->type === Token::T_PHPDOC_LEADING) { + break; + } + if (false !== strpos($this->tokens->current->value, "\n")) { + break; + } + $text[] = $this->tokens->chomp(); + } + + if ($text) { + return new TextNode($text); + } + + return null; + } } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 243e2d4f..ac87d933 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -2,6 +2,7 @@ namespace Phpactor\Docblock\Printer; +use Phpactor\Docblock\Ast\DeprecatedNode; use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; use Phpactor\Docblock\Ast\TextNode; @@ -92,6 +93,11 @@ private function render(?Element $node): void return; } + if ($node instanceof DeprecatedNode) { + $this->renderDeprecated($node); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) @@ -175,4 +181,13 @@ private function renderTextNode(TextNode $node): void $this->out[] = $node->toString(); $this->out[] = ')'; } + + private function renderDeprecated(DeprecatedNode $node): void + { + $this->out[] = $node->shortName() . '('; + if ($node->text()) { + $this->render($node->text()); + } + $this->out[] = ')'; + } } diff --git a/tests/Printer/examples/deprecated1.test b/tests/Printer/examples/deprecated1.test index ed97d539..dd9ff0d5 100644 --- a/tests/Printer/examples/deprecated1.test +++ b/tests/Printer/examples/deprecated1.test @@ -1 +1,7 @@ +/** + * @deprecated + */ --- +/** + * DeprecatedNode() + */ diff --git a/tests/Printer/examples/deprecated2.test b/tests/Printer/examples/deprecated2.test new file mode 100644 index 00000000..9695d03a --- /dev/null +++ b/tests/Printer/examples/deprecated2.test @@ -0,0 +1,7 @@ +/** + * @deprecated This is because + */ +--- +/** + * DeprecatedNode(TextNode(This is because)) + */ From c6bdf9eebb02e946562f3676e553fcdad1c19d23 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 13:45:38 +0000 Subject: [PATCH 30/63] Support method node --- lib/Ast/MethodNode.php | 34 +++++++++++++++++++++++++++++ lib/Parser.php | 19 ++++++++++++++++ lib/Printer/TestPrinter.php | 17 +++++++++++++++ tests/Printer/examples/method1.test | 6 +++++ 4 files changed, 76 insertions(+) create mode 100644 lib/Ast/MethodNode.php diff --git a/lib/Ast/MethodNode.php b/lib/Ast/MethodNode.php new file mode 100644 index 00000000..26002a02 --- /dev/null +++ b/lib/Ast/MethodNode.php @@ -0,0 +1,34 @@ +type = $type; + $this->name = $name; + } + + public function name(): ?Token + { + return $this->name; + } + + public function type(): ?TypeNode + { + return $this->type; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index ca50a697..bc285e85 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -4,6 +4,7 @@ use Phpactor\Docblock\Ast\DeprecatedNode; use Phpactor\Docblock\Ast\Docblock; +use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -61,6 +62,10 @@ private function parseTag(): TagNode return $this->parseDeprecated(); } + if ($token->value === '@method') { + return $this->parseMethod(); + } + return new UnknownTag($this->tokens->chomp()); } @@ -93,6 +98,20 @@ private function parseVar(): VarNode return new VarNode($type, $variable); } + private function parseMethod(): MethodNode + { + $this->tokens->chomp(Token::T_TAG); + $type = $name = null; + if ($this->tokens->if(Token::T_LABEL)) { + $type = $this->parseType(); + } + if ($this->tokens->ifNextIs(Token::T_LABEL)) { + $name = $this->tokens->chomp(); + } + + return new MethodNode($type, $name); + } + private function parseType(): ?TypeNode { $type = $this->tokens->chomp(Token::T_LABEL); diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index ac87d933..7ac21f9d 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -5,6 +5,7 @@ use Phpactor\Docblock\Ast\DeprecatedNode; use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; +use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; @@ -98,6 +99,11 @@ private function render(?Element $node): void return; } + if ($node instanceof MethodNode) { + $this->renderMethod($node); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) @@ -190,4 +196,15 @@ private function renderDeprecated(DeprecatedNode $node): void } $this->out[] = ')'; } + + private function renderMethod(MethodNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + if ($node->name()) { + $this->out[] = ','; + $this->render($node->name()); + } + $this->out[] = ')'; + } } diff --git a/tests/Printer/examples/method1.test b/tests/Printer/examples/method1.test index ed97d539..63ba6fae 100644 --- a/tests/Printer/examples/method1.test +++ b/tests/Printer/examples/method1.test @@ -1 +1,7 @@ +/** + * @method Foobar foobar() + */ --- +/** + * MethodNode(ClassNode(Foobar),foobar)() + */ From 4b028829993124ffe1ab5b2e17049e078d1ca3ba Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 14:04:50 +0000 Subject: [PATCH 31/63] Support nullable --- lib/Ast/MixinNode.php | 23 ++++++++++++++++ lib/Ast/Type/NullableNode.php | 35 ++++++++++++++++++++++++ lib/Lexer.php | 2 ++ lib/Parser.php | 38 ++++++++++++++++++++++++--- lib/Printer/TestPrinter.php | 26 ++++++++++++++++++ lib/Token.php | 4 +-- tests/Printer/examples/mixin1.test | 6 +++++ tests/Printer/examples/nullable1.test | 6 +++++ 8 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 lib/Ast/MixinNode.php create mode 100644 lib/Ast/Type/NullableNode.php diff --git a/lib/Ast/MixinNode.php b/lib/Ast/MixinNode.php new file mode 100644 index 00000000..6ebb834b --- /dev/null +++ b/lib/Ast/MixinNode.php @@ -0,0 +1,23 @@ +class = $class; + } + + public function class(): ?ClassNode + { + return $this->class; + } +} diff --git a/lib/Ast/Type/NullableNode.php b/lib/Ast/Type/NullableNode.php new file mode 100644 index 00000000..ec6ce250 --- /dev/null +++ b/lib/Ast/Type/NullableNode.php @@ -0,0 +1,35 @@ +nullable = $nullable; + $this->type = $type; + } + + public function nullable(): Token + { + return $this->nullable; + } + + public function type(): TypeNode + { + return $this->type; + } +} diff --git a/lib/Lexer.php b/lib/Lexer.php index 1e7297ae..f9afd61c 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -12,6 +12,7 @@ final class Lexer '\*/', // close tag '\*', // leading tag '\[\]', //tag + '\?', //tag '@\w+', //tag '\s+', // whitespace ',', // comma @@ -29,6 +30,7 @@ final class Lexer '}' => Token::T_BRACKET_CURLY_CLOSE, ',' => Token::T_COMMA, '[]' => Token::T_LIST, + '?' => Token::T_NULLABLE, ]; /** diff --git a/lib/Parser.php b/lib/Parser.php index bc285e85..c01d4d86 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -2,9 +2,11 @@ namespace Phpactor\Docblock; +use PhpParser\Node\NullableType; use Phpactor\Docblock\Ast\DeprecatedNode; use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\MethodNode; +use Phpactor\Docblock\Ast\MixinNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -14,6 +16,7 @@ use Phpactor\Docblock\Ast\TypeNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\Type\ScalarNode; use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\VarNode; @@ -66,6 +69,10 @@ private function parseTag(): TagNode return $this->parseMethod(); } + if ($token->value === '@mixin') { + return $this->parseMixin(); + } + return new UnknownTag($this->tokens->chomp()); } @@ -74,7 +81,7 @@ private function parseParam(): ParamNode $type = $variable = $textNode = null; $this->tokens->chomp(Token::T_TAG); - if ($this->tokens->ifNextIs(Token::T_LABEL)) { + if ($this->ifType()) { $type = $this->parseType(); } if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { @@ -88,7 +95,7 @@ private function parseVar(): VarNode { $this->tokens->chomp(Token::T_TAG); $type = $variable = null; - if ($this->tokens->if(Token::T_LABEL)) { + if ($this->ifType()) { $type = $this->parseType(); } if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { @@ -102,7 +109,7 @@ private function parseMethod(): MethodNode { $this->tokens->chomp(Token::T_TAG); $type = $name = null; - if ($this->tokens->if(Token::T_LABEL)) { + if ($this->ifType()) { $type = $this->parseType(); } if ($this->tokens->ifNextIs(Token::T_LABEL)) { @@ -114,6 +121,11 @@ private function parseMethod(): MethodNode private function parseType(): ?TypeNode { + if ($this->tokens->current->type === Token::T_NULLABLE) { + $nullable = $this->tokens->chomp(); + return new NullableNode($nullable, $this->parseType()); + } + $type = $this->tokens->chomp(Token::T_LABEL); $isList = false; @@ -186,6 +198,21 @@ private function parseDeprecated(): DeprecatedNode return new DeprecatedNode($this->parseText()); } + private function parseMixin(): MixinNode + { + $this->tokens->chomp(); + $type = null; + + if ($this->tokens->if(Token::T_LABEL)) { + $type = $this->parseType(); + if (!$type instanceof ClassNode) { + $type = null; + } + } + + return new MixinNode($type); + } + private function parseText(): ?TextNode { $text = []; @@ -214,4 +241,9 @@ private function parseText(): ?TextNode return null; } + + private function ifType(): bool + { + return $this->tokens->if(Token::T_LABEL) || $this->tokens->if(Token::T_NULLABLE); + } } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 7ac21f9d..776e00c9 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -6,6 +6,7 @@ use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; use Phpactor\Docblock\Ast\MethodNode; +use Phpactor\Docblock\Ast\MixinNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; @@ -14,6 +15,7 @@ use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; @@ -74,6 +76,11 @@ private function render(?Element $node): void return; } + if ($node instanceof NullableNode) { + $this->renderNullable($node); + return; + } + if ($node instanceof TypeNode) { $this->renderTypeNode($node); return; @@ -104,6 +111,11 @@ private function render(?Element $node): void return; } + if ($node instanceof MixinNode) { + $this->renderMixin($node); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) @@ -207,4 +219,18 @@ private function renderMethod(MethodNode $node): void } $this->out[] = ')'; } + + private function renderMixin(MixinNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->class()); + $this->out[] = ')'; + } + + private function renderNullable(NullableNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + $this->out[] = ')'; + } } diff --git a/lib/Token.php b/lib/Token.php index 4d97cd66..b64ad171 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -6,14 +6,12 @@ final class Token implements Element { - public const T_PHPDOC_BORDER= 'PHPDOC_BORDER'; public const T_PHPDOC_OPEN = 'PHPDOC_OPEN'; public const T_PHPDOC_LEADING = 'PHPDOC_LEADING'; public const T_PHPDOC_CLOSE = 'PHPDOC_CLOSE'; - public const T_BORDER = 'BORDER'; - public const T_TEXT = 'TEXT'; public const T_VARIABLE = 'VARIABLE'; public const T_UNKNOWN = 'UNKNOWN'; + public const T_NULLABLE = 'NULLABLE'; public const T_TAG = 'TAG'; public const T_COMMA = 'COMMA'; public const T_LIST = 'LIST'; diff --git a/tests/Printer/examples/mixin1.test b/tests/Printer/examples/mixin1.test index ed97d539..acdadcfa 100644 --- a/tests/Printer/examples/mixin1.test +++ b/tests/Printer/examples/mixin1.test @@ -1 +1,7 @@ +/** + * @mixin Foobar + */ --- +/** + * MixinNode(ClassNode(Foobar)) + */ diff --git a/tests/Printer/examples/nullable1.test b/tests/Printer/examples/nullable1.test index ed97d539..9014ebf9 100644 --- a/tests/Printer/examples/nullable1.test +++ b/tests/Printer/examples/nullable1.test @@ -1 +1,7 @@ +/** + * @var ?Foo + */ --- +/** + * VarNode(NullableNode(ClassNode(Foo))) + */ From 38ee7272b201ca460f2c34d397c49e326d256a08 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 14:09:09 +0000 Subject: [PATCH 32/63] Support property --- lib/Ast/PropertyNode.php | 34 +++++++++++++++++++++++++++ lib/Parser.php | 19 +++++++++++++++ lib/Printer/TestPrinter.php | 17 ++++++++++++++ tests/Printer/examples/property1.test | 6 +++++ 4 files changed, 76 insertions(+) create mode 100644 lib/Ast/PropertyNode.php diff --git a/lib/Ast/PropertyNode.php b/lib/Ast/PropertyNode.php new file mode 100644 index 00000000..93480583 --- /dev/null +++ b/lib/Ast/PropertyNode.php @@ -0,0 +1,34 @@ +type = $type; + $this->name = $name; + } + + public function name(): ?Token + { + return $this->name; + } + + public function type(): ?TypeNode + { + return $this->type; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index c01d4d86..d298fa76 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -7,6 +7,7 @@ use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\MixinNode; +use Phpactor\Docblock\Ast\PropertyNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -69,6 +70,10 @@ private function parseTag(): TagNode return $this->parseMethod(); } + if ($token->value === '@property') { + return $this->parseProperty(); + } + if ($token->value === '@mixin') { return $this->parseMixin(); } @@ -119,6 +124,20 @@ private function parseMethod(): MethodNode return new MethodNode($type, $name); } + private function parseProperty(): PropertyNode + { + $this->tokens->chomp(Token::T_TAG); + $type = $name = null; + if ($this->ifType()) { + $type = $this->parseType(); + } + if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { + $name = $this->tokens->chomp(); + } + + return new PropertyNode($type, $name); + } + private function parseType(): ?TypeNode { if ($this->tokens->current->type === Token::T_NULLABLE) { diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 776e00c9..25c55ce7 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -7,6 +7,7 @@ use Phpactor\Docblock\Ast\Element; use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\MixinNode; +use Phpactor\Docblock\Ast\PropertyNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; @@ -111,6 +112,11 @@ private function render(?Element $node): void return; } + if ($node instanceof PropertyNode) { + $this->renderProperty($node); + return; + } + if ($node instanceof MixinNode) { $this->renderMixin($node); return; @@ -233,4 +239,15 @@ private function renderNullable(NullableNode $node): void $this->render($node->type()); $this->out[] = ')'; } + + private function renderProperty(PropertyNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + if ($node->name()) { + $this->out[] = ','; + $this->render($node->name()); + } + $this->out[] = ')'; + } } diff --git a/tests/Printer/examples/property1.test b/tests/Printer/examples/property1.test index ed97d539..d05b05b0 100644 --- a/tests/Printer/examples/property1.test +++ b/tests/Printer/examples/property1.test @@ -1 +1,7 @@ +/** + * @property string $foo + */ --- +/** + * PropertyNode(ScalarNode(string),$foo) + */ From 034f18b539c08a193def9981aca4c37c7c1a7f92 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 14:13:32 +0000 Subject: [PATCH 33/63] Support return node --- lib/Ast/ReturnNode.php | 21 +++++++++++++++++++++ lib/Parser.php | 17 +++++++++++++++++ lib/Printer/TestPrinter.php | 13 +++++++++++++ tests/Printer/examples/return1.test | 6 ++++++ 4 files changed, 57 insertions(+) create mode 100644 lib/Ast/ReturnNode.php diff --git a/lib/Ast/ReturnNode.php b/lib/Ast/ReturnNode.php new file mode 100644 index 00000000..4c84cf2f --- /dev/null +++ b/lib/Ast/ReturnNode.php @@ -0,0 +1,21 @@ +type = $type; + } + + public function type(): ?TypeNode + { + return $this->type; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index d298fa76..f19ae9fa 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -8,6 +8,7 @@ use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\MixinNode; use Phpactor\Docblock\Ast\PropertyNode; +use Phpactor\Docblock\Ast\ReturnNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -78,6 +79,10 @@ private function parseTag(): TagNode return $this->parseMixin(); } + if ($token->value === '@return') { + return $this->parseReturn(); + } + return new UnknownTag($this->tokens->chomp()); } @@ -232,6 +237,18 @@ private function parseMixin(): MixinNode return new MixinNode($type); } + private function parseReturn(): ReturnNode + { + $this->tokens->chomp(); + $type = null; + + if ($this->tokens->if(Token::T_LABEL)) { + $type = $this->parseType(); + } + + return new ReturnNode($type); + } + private function parseText(): ?TextNode { $text = []; diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 25c55ce7..8bb2a954 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -8,6 +8,7 @@ use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\MixinNode; use Phpactor\Docblock\Ast\PropertyNode; +use Phpactor\Docblock\Ast\ReturnNode; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; @@ -122,6 +123,11 @@ private function render(?Element $node): void return; } + if ($node instanceof ReturnNode) { + $this->renderReturn($node); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) @@ -250,4 +256,11 @@ private function renderProperty(PropertyNode $node): void } $this->out[] = ')'; } + + private function renderReturn(ReturnNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->render($node->type()); + $this->out[] = ')'; + } } diff --git a/tests/Printer/examples/return1.test b/tests/Printer/examples/return1.test index ed97d539..9e8ab1db 100644 --- a/tests/Printer/examples/return1.test +++ b/tests/Printer/examples/return1.test @@ -1 +1,7 @@ +/** + * @return Foobar + */ --- +/** + * ReturnNode(ClassNode(Foobar)) + */ From 657f3449d4b72adf67d8860afa32f6bfeabfaad9 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 14:38:01 +0000 Subject: [PATCH 34/63] Support fully qualfiied names --- lib/Ast/Type/UnionNode.php | 24 ++++++++++++++ lib/Lexer.php | 6 ++-- lib/Parser.php | 44 ++++++++++++++++++++------ lib/Printer/TestPrinter.php | 17 ++++++++-- lib/Token.php | 1 + tests/Printer/examples/classname1.test | 8 +++++ tests/Printer/examples/classname2.test | 8 +++++ tests/Printer/examples/union1.test | 2 ++ 8 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 lib/Ast/Type/UnionNode.php create mode 100644 tests/Printer/examples/classname1.test create mode 100644 tests/Printer/examples/classname2.test diff --git a/lib/Ast/Type/UnionNode.php b/lib/Ast/Type/UnionNode.php new file mode 100644 index 00000000..4d9946bf --- /dev/null +++ b/lib/Ast/Type/UnionNode.php @@ -0,0 +1,24 @@ +types = $types; + } + + public function types(): TypeList + { + return $this->types; + } +} diff --git a/lib/Lexer.php b/lib/Lexer.php index f9afd61c..c5e93bbe 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -16,9 +16,10 @@ final class Lexer '@\w+', //tag '\s+', // whitespace ',', // comma + '\|', // bar (union) '\{', '\}', '\[', '\]', '<', '>', // brackets '\$[a-zA-Z0-9_\x80-\xff]+', // variable - '[^a-zA-Z0-9_\x80-\xff]+', // label + '[^a-zA-Z0-9_\x80-\xff\\\]+', // label ]; private const TOKEN_VALUE_MAP = [ @@ -31,6 +32,7 @@ final class Lexer ',' => Token::T_COMMA, '[]' => Token::T_LIST, '?' => Token::T_NULLABLE, + '|' => Token::T_BAR, ]; /** @@ -112,7 +114,7 @@ private function resolveType(string $value, ?array $prevChunk = null): string return Token::T_WHITESPACE; } - if (ctype_alpha($value) || $value === '_') { + if (ctype_alpha($value) || false !== strpos($value, '\\')) { return Token::T_LABEL; } diff --git a/lib/Parser.php b/lib/Parser.php index f19ae9fa..1145cc22 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -20,6 +20,7 @@ use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\Type\ScalarNode; +use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; @@ -92,7 +93,7 @@ private function parseParam(): ParamNode $this->tokens->chomp(Token::T_TAG); if ($this->ifType()) { - $type = $this->parseType(); + $type = $this->parseTypes(); } if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { $variable = $this->parseVariable(); @@ -106,7 +107,7 @@ private function parseVar(): VarNode $this->tokens->chomp(Token::T_TAG); $type = $variable = null; if ($this->ifType()) { - $type = $this->parseType(); + $type = $this->parseTypes(); } if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { $variable = $this->parseVariable(); @@ -120,7 +121,7 @@ private function parseMethod(): MethodNode $this->tokens->chomp(Token::T_TAG); $type = $name = null; if ($this->ifType()) { - $type = $this->parseType(); + $type = $this->parseTypes(); } if ($this->tokens->ifNextIs(Token::T_LABEL)) { $name = $this->tokens->chomp(); @@ -134,7 +135,7 @@ private function parseProperty(): PropertyNode $this->tokens->chomp(Token::T_TAG); $type = $name = null; if ($this->ifType()) { - $type = $this->parseType(); + $type = $this->parseTypes(); } if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { $name = $this->tokens->chomp(); @@ -143,11 +144,36 @@ private function parseProperty(): PropertyNode return new PropertyNode($type, $name); } + private function parseTypes(): ?TypeNode + { + $type = $this->parseType(); + if (null === $type) { + return $type; + } + $types = [$type]; + + while (true) { + if ($this->tokens->if(Token::T_BAR)) { + $this->tokens->chomp(); + $types[] = $this->parseType(); + if (null !== $type) { + continue; + } + } + break; + } + + if (count($types) === 1) { + return $types[0]; + } + + return new UnionNode(new TypeList($types)); + } private function parseType(): ?TypeNode { if ($this->tokens->current->type === Token::T_NULLABLE) { $nullable = $this->tokens->chomp(); - return new NullableNode($nullable, $this->parseType()); + return new NullableNode($nullable, $this->parseTypes()); } $type = $this->tokens->chomp(Token::T_LABEL); @@ -199,12 +225,12 @@ private function parseVariable(): ?VariableNode return new VariableNode($name); } - private function parseTypeList(): TypeList + private function parseTypeList(string $delimiter = ','): TypeList { $types = []; while (true) { if ($this->tokens->if(Token::T_LABEL)) { - $types[] = $this->parseType(); + $types[] = $this->parseTypes(); } if ($this->tokens->if(Token::T_COMMA)) { $this->tokens->chomp(); @@ -228,7 +254,7 @@ private function parseMixin(): MixinNode $type = null; if ($this->tokens->if(Token::T_LABEL)) { - $type = $this->parseType(); + $type = $this->parseTypes(); if (!$type instanceof ClassNode) { $type = null; } @@ -243,7 +269,7 @@ private function parseReturn(): ReturnNode $type = null; if ($this->tokens->if(Token::T_LABEL)) { - $type = $this->parseType(); + $type = $this->parseTypes(); } return new ReturnNode($type); diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 8bb2a954..07b645e8 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -18,6 +18,7 @@ use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\Type\NullableNode; +use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; @@ -83,6 +84,11 @@ private function render(?Element $node): void return; } + if ($node instanceof UnionNode) { + $this->renderUnion($node); + return; + } + if ($node instanceof TypeNode) { $this->renderTypeNode($node); return; @@ -195,12 +201,12 @@ private function renderGenericNode(GenericNode $node): void $this->out[] = ')'; } - private function renderTypeList(TypeList $typeList): void + private function renderTypeList(TypeList $typeList, string $delimiter = ','): void { foreach ($typeList as $i => $param) { $this->render($param); if ($i + 1 !== $typeList->count()) { - $this->out[] = ','; + $this->out[] = $delimiter; } } } @@ -263,4 +269,11 @@ private function renderReturn(ReturnNode $node): void $this->render($node->type()); $this->out[] = ')'; } + + private function renderUnion(UnionNode $node): void + { + $this->out[] = $node->shortName() . '('; + $this->renderTypeList($node->types(), '|'); + $this->out[] = ')'; + } } diff --git a/lib/Token.php b/lib/Token.php index b64ad171..5ebb40c8 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -12,6 +12,7 @@ final class Token implements Element public const T_VARIABLE = 'VARIABLE'; public const T_UNKNOWN = 'UNKNOWN'; public const T_NULLABLE = 'NULLABLE'; + public const T_BAR = 'BAR'; public const T_TAG = 'TAG'; public const T_COMMA = 'COMMA'; public const T_LIST = 'LIST'; diff --git a/tests/Printer/examples/classname1.test b/tests/Printer/examples/classname1.test new file mode 100644 index 00000000..492b2b5b --- /dev/null +++ b/tests/Printer/examples/classname1.test @@ -0,0 +1,8 @@ +/** + * @var Foobar\Barfoo + */ +--- +/** + * VarNode(ClassNode(Foobar\Barfoo)) + */ + diff --git a/tests/Printer/examples/classname2.test b/tests/Printer/examples/classname2.test new file mode 100644 index 00000000..286959e4 --- /dev/null +++ b/tests/Printer/examples/classname2.test @@ -0,0 +1,8 @@ +/** + * @var \foo_bar\bar_foo + */ +--- +/** + * VarNode(ClassNode(\foo_bar\bar_foo)) + */ + diff --git a/tests/Printer/examples/union1.test b/tests/Printer/examples/union1.test index ed97d539..379eba23 100644 --- a/tests/Printer/examples/union1.test +++ b/tests/Printer/examples/union1.test @@ -1 +1,3 @@ +/** @var string|bool|?Bar */ --- +/** VarNode(UnionNode(ScalarNode(string)|ScalarNode(bool)|NullableNode(ClassNode(Bar)))) */ From a264866625b26c686fde8be6eb39b3d69743f5e0 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 16:56:03 +0000 Subject: [PATCH 35/63] Remove duplicate check --- lib/Lexer.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/Lexer.php b/lib/Lexer.php index c5e93bbe..74e1769d 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -110,10 +110,6 @@ private function resolveType(string $value, ?array $prevChunk = null): string return Token::T_WHITESPACE; } - if (trim($value) === '') { - return Token::T_WHITESPACE; - } - if (ctype_alpha($value) || false !== strpos($value, '\\')) { return Token::T_LABEL; } From 15382fbd31389d9f521f9e3c92c798576cc1a1c1 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 16:57:45 +0000 Subject: [PATCH 36/63] Use switch (doesn't make any perf diff) --- lib/Parser.php | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/Parser.php b/lib/Parser.php index 1145cc22..bd731922 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -56,32 +56,27 @@ private function parseTag(): TagNode { $token = $this->tokens->current; - if ($token->value === '@param') { - return $this->parseParam(); - } + switch ($token->value) { + case '@param': + return $this->parseParam(); - if ($token->value === '@var') { - return $this->parseVar(); - } + case '@var': + return $this->parseVar(); - if ($token->value === '@deprecated') { - return $this->parseDeprecated(); - } + case '@deprecated': + return $this->parseDeprecated(); - if ($token->value === '@method') { - return $this->parseMethod(); - } + case '@method': + return $this->parseMethod(); - if ($token->value === '@property') { - return $this->parseProperty(); - } + case '@property': + return $this->parseProperty(); - if ($token->value === '@mixin') { - return $this->parseMixin(); - } + case '@mixin': + return $this->parseMixin(); - if ($token->value === '@return') { - return $this->parseReturn(); + case '@return': + return $this->parseReturn(); } return new UnknownTag($this->tokens->chomp()); From 3c0abd67c2121cdbdedeb7dbf28b87a1b9feae9e Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 17:32:20 +0000 Subject: [PATCH 37/63] Support NULL and return text --- lib/Ast/ReturnNode.php | 13 +- lib/Ast/Type/NullNode.php | 24 + lib/Parser.php | 6 +- lib/Printer/TestPrinter.php | 10 + tests/Benchmark/AbstractParserBenchCase.php | 20 + tests/Benchmark/examples/php_core.example | 1043 +++++++++++++++++++ tests/Printer/examples/php_core1.test | 51 + 7 files changed, 1165 insertions(+), 2 deletions(-) create mode 100644 lib/Ast/Type/NullNode.php create mode 100644 tests/Benchmark/examples/php_core.example create mode 100644 tests/Printer/examples/php_core1.test diff --git a/lib/Ast/ReturnNode.php b/lib/Ast/ReturnNode.php index 4c84cf2f..f6dea30a 100644 --- a/lib/Ast/ReturnNode.php +++ b/lib/Ast/ReturnNode.php @@ -9,13 +9,24 @@ class ReturnNode extends TagNode */ private $type; - public function __construct(?TypeNode $type) + /** + * @var TextNode|null + */ + private $text; + + public function __construct(?TypeNode $type, ?TextNode $text = null) { $this->type = $type; + $this->text = $text; } public function type(): ?TypeNode { return $this->type; } + + public function text(): ?TextNode + { + return $this->text; + } } diff --git a/lib/Ast/Type/NullNode.php b/lib/Ast/Type/NullNode.php new file mode 100644 index 00000000..0802c711 --- /dev/null +++ b/lib/Ast/Type/NullNode.php @@ -0,0 +1,24 @@ +null = $null; + } + + public function null(): Token + { + return $this->null; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index bd731922..07c2f029 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -18,6 +18,7 @@ use Phpactor\Docblock\Ast\TypeNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\NullNode; use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\Type\ScalarNode; use Phpactor\Docblock\Ast\Type\UnionNode; @@ -202,6 +203,9 @@ private function parseType(): ?TypeNode private function createTypeFromToken(Token $type): TypeNode { + if (strtolower($type->value) === 'null') { + return new NullNode($type); + } if (in_array($type->value, self::SCALAR_TYPES)) { return new ScalarNode($type); } @@ -267,7 +271,7 @@ private function parseReturn(): ReturnNode $type = $this->parseTypes(); } - return new ReturnNode($type); + return new ReturnNode($type, $this->parseText()); } private function parseText(): ?TextNode diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 07b645e8..59ab9e07 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -17,6 +17,7 @@ use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\NullNode; use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; @@ -89,6 +90,11 @@ private function render(?Element $node): void return; } + if ($node instanceof NullNode) { + $this->out[] = $node->shortName() . '()'; + return; + } + if ($node instanceof TypeNode) { $this->renderTypeNode($node); return; @@ -267,6 +273,10 @@ private function renderReturn(ReturnNode $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->type()); + if ($node->text()) { + $this->out[] = ','; + $this->render($node->text()); + } $this->out[] = ')'; } diff --git a/tests/Benchmark/AbstractParserBenchCase.php b/tests/Benchmark/AbstractParserBenchCase.php index 3a3c4a14..93db977b 100644 --- a/tests/Benchmark/AbstractParserBenchCase.php +++ b/tests/Benchmark/AbstractParserBenchCase.php @@ -2,6 +2,7 @@ namespace Phpactor\Docblock\Tests\Benchmark; +use Generator; use Phpactor\Docblock\Lexer; use Phpactor\Docblock\Parser; @@ -9,6 +10,7 @@ * @Iterations(33) * @Revs(50) * @BeforeMethods({"setUp"}) + * @OutputTimeUnit("milliseconds") */ abstract class AbstractParserBenchCase { @@ -24,6 +26,24 @@ public function benchParse(): void $this->parse($doc); } + /** + * @ParamProviders({"provideCoreDocs"}) + */ + public function benchPhpCore(array $params): void + { + $this->parse(trim($params['doc'])); + } + + public function provideCoreDocs(): Generator + { + $contents = file_get_contents(__DIR__ . '/examples/php_core.example'); + foreach (explode('#!---!#', $contents) as $doc) { + yield [ + 'doc' => $doc + ]; + } + } + abstract public function setUp(): void; abstract public function parse(string $doc): void; } diff --git a/tests/Benchmark/examples/php_core.example b/tests/Benchmark/examples/php_core.example new file mode 100644 index 00000000..32b500d3 --- /dev/null +++ b/tests/Benchmark/examples/php_core.example @@ -0,0 +1,1043 @@ + + * The argument offset. Function arguments are counted starting from + * zero. + *

+ * @return mixed|false the specified argument, or false on error. + */ +#!---!# + +/** + * Returns an array comprising a function's argument list + * @link https://php.net/manual/en/function.func-get-args.php + * @return array an array in which each element is a copy of the corresponding + * member of the current user-defined function's argument list. + */ +#!---!# + +/** + * Get string length + * @link https://php.net/manual/en/function.strlen.php + * @param string $string

+ * The string being measured for length. + *

+ * @return int The length of the string on success, + * and 0 if the string is empty. + */ +#!---!# + +/** + * Binary safe string comparison + * @link https://php.net/manual/en/function.strcmp.php + * @param string $str1

+ * The first string. + *

+ * @param string $str2

+ * The second string. + *

+ * @return int < 0 if str1 is less than + * str2; > 0 if str1 + * is greater than str2, and 0 if they are + * equal. + */ +#!---!# + +/** + * Binary safe string comparison of the first n characters + * @link https://php.net/manual/en/function.strncmp.php + * @param string $str1

+ * The first string. + *

+ * @param string $str2

+ * The second string. + *

+ * @param int $len

+ * Number of characters to use in the comparison. + *

+ * @return int < 0 if str1 is less than + * str2; > 0 if str1 + * is greater than str2, and 0 if they are + * equal. + */ +#!---!# + +/** + * Binary safe case-insensitive string comparison + * @link https://php.net/manual/en/function.strcasecmp.php + * @param string $str1

+ * The first string + *

+ * @param string $str2

+ * The second string + *

+ * @return int < 0 if str1 is less than + * str2; > 0 if str1 + * is greater than str2, and 0 if they are + * equal. + */ +#!---!# + +/** + * Binary safe case-insensitive string comparison of the first n characters + * @link https://php.net/manual/en/function.strncasecmp.php + * @param string $str1

+ * The first string. + *

+ * @param string $str2

+ * The second string. + *

+ * @param int $len

+ * The length of strings to be used in the comparison. + *

+ * @return int < 0 if str1 is less than + * str2; > 0 if str1 is + * greater than str2, and 0 if they are equal. + */ +#!---!# + +/** + * The function returns {@see true} if the passed $haystack starts from the + * $needle string or {@see false} otherwise. + * + * @param string $haystack + * @param string $needle + * @return bool + * @since 8.0 + */ +#!---!# + +/** + * The function returns {@see true} if the passed $haystack ends with the + * $needle string or {@see false} otherwise. + * + * @param string $haystack + * @param string $needle + * @return bool + * @since 8.0 + */ +#!---!# + +/** + * Checks if $needle is found in $haystack and returns a boolean value + * (true/false) whether or not the $needle was found. + * + * @param string $haystack + * @param string $needle + * @return bool + * @since 8.0 + */ +#!---!# + +/** + * Return the current key and value pair from an array and advance the array cursor + * @link https://php.net/manual/en/function.each.php + * @param array|ArrayObject &$array

+ * The input array. + *

+ * @return array the current key and value pair from the array + * array. This pair is returned in a four-element + * array, with the keys 0, 1, + * key, and value. Elements + * 0 and key contain the key name of + * the array element, and 1 and value + * contain the data. + *

+ *

+ * If the internal pointer for the array points past the end of the + * array contents, each returns + * false. + * @deprecated 7.2 Use a foreach loop instead. + * @removed 8.0 + */ +#!---!# + +/** + * Sets which PHP errors are reported + * @link https://php.net/manual/en/function.error-reporting.php + * @param int $level [optional]

+ * The new error_reporting + * level. It takes on either a bitmask, or named constants. Using named + * constants is strongly encouraged to ensure compatibility for future + * versions. As error levels are added, the range of integers increases, + * so older integer-based error levels will not always behave as expected. + *

+ *

+ * The available error level constants and the actual + * meanings of these error levels are described in the + * predefined constants. + * + * error_reporting level constants and bit values + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
valueconstant
1 + * E_ERROR + *
2 + * E_WARNING + *
4 + * E_PARSE + *
8 + * E_NOTICE + *
16 + * E_CORE_ERROR + *
32 + * E_CORE_WARNING + *
64 + * E_COMPILE_ERROR + *
128 + * E_COMPILE_WARNING + *
256 + * E_USER_ERROR + *
512 + * E_USER_WARNING + *
1024 + * E_USER_NOTICE + *
32767 + * E_ALL + *
2048 + * E_STRICT + *
4096 + * E_RECOVERABLE_ERROR + *
8192 + * E_DEPRECATED + *
16384 + * E_USER_DEPRECATED + *
+ *

+ * @return int the old error_reporting + * level or the current level if no level parameter is + * given. + */ +#!---!# + +/** + * Defines a named constant + * @link https://php.net/manual/en/function.define.php + * @param string $name

+ * The name of the constant. + *

+ * @param mixed $value

+ * The value of the constant. + * In PHP 5, value must be a scalar value (integer, float, string, boolean, or null). + * In PHP 7, array values are also accepted. + * It is possible to define resource constants, + * however it is not recommended and may cause unpredictable behavior. + *

+ * @param bool $case_insensitive [optional]

+ * If set to true, the constant will be defined case-insensitive. + * The default behavior is case-sensitive; i.e. + * CONSTANT and Constant represent + * different values. + * Defining case-insensitive constants is deprecated as of PHP 7.3.0. + *

+ *

+ * Case-insensitive constants are stored as lower-case. + *

+ * @return bool true on success or false on failure. + */ +#!---!# + +/** + * Checks whether a given named constant exists + * @link https://php.net/manual/en/function.defined.php + * @param string $name

+ * The constant name. + *

+ * @return bool true if the named constant given by name + * has been defined, false otherwise. + */ +#!---!# + +/** + * Returns the name of the class of an object + * @link https://php.net/manual/en/function.get-class.php + * @param object $object [optional]

+ * The tested object. This parameter may be omitted when inside a class. + *

+ * @return string|false

The name of the class of which object is an + * instance. Returns false if object is not an + * object. + *

+ *

+ * If object is omitted when inside a class, the + * name of that class is returned. + */ +#!---!# + +/** + * the "Late Static Binding" class name + * @link https://php.net/manual/en/function.get-called-class.php + * @return string|false The class name. Returns false if called from outside a class. + */ +#!---!# + +/** + * Retrieves the parent class name for object or class + * @link https://php.net/manual/en/function.get-parent-class.php + * @param mixed $object [optional]

+ * The tested object or class name + *

+ * @return string|false

The name of the parent class of the class of which + * object is an instance or the name. + *

+ *

+ * If the object does not have a parent false will be returned. + *

+ *

+ * If called without parameter outside object, this function returns false. + */ +#!---!# + +/** + * Checks if the class method exists + * @link https://php.net/manual/en/function.method-exists.php + * @param mixed $object

+ * An object instance or a class name + *

+ * @param string $method_name

+ * The method name + *

+ * @return bool true if the method given by method_name + * has been defined for the given object, false + * otherwise. + */ +#!---!# + +/** + * Checks if the object or class has a property + * @link https://php.net/manual/en/function.property-exists.php + * @param mixed $class

+ * The class name or an object of the class to test for + *

+ * @param string $property

+ * The name of the property + *

+ * @return bool true if the property exists, false if it doesn't exist or + * null in case of an error. + */ +#!---!# + +/** + * Checks if the trait exists + * @param string $traitname Name of the trait to check + * @param bool $autoload [optional] Whether to autoload if not already loaded. + * @return bool Returns TRUE if trait exists, FALSE if not, NULL in case of an error. + * @link https://secure.php.net/manual/en/function.trait-exists.php + * @since 5.4 + */ +#!---!# + +/** + * Checks if the class has been defined + * @link https://php.net/manual/en/function.class-exists.php + * @param string $class_name

+ * The class name. The name is matched in a case-insensitive manner. + *

+ * @param bool $autoload [optional]

+ * Whether or not to call autoload by default. + *

+ * @return bool true if class_name is a defined class, + * false otherwise. + */ +#!---!# + +/** + * Checks if the interface has been defined + * @link https://php.net/manual/en/function.interface-exists.php + * @param string $interface_name

+ * The interface name + *

+ * @param bool $autoload [optional]

+ * Whether to call autoload or not by default. + *

+ * @return bool true if the interface given by + * interface_name has been defined, false otherwise. + * @since 5.0.2 + */ +#!---!# + +/** + * Return true if the given function has been defined + * @link https://php.net/manual/en/function.function-exists.php + * @param string $function_name

+ * The function name, as a string. + *

+ * @return bool true if function_name exists and is a + * function, false otherwise. + *

+ *

+ * This function will return false for constructs, such as + * include_once and echo. + */ +#!---!# + +/** + * Creates an alias for a class + * @link https://php.net/manual/en/function.class-alias.php + * @param string $original The original class. + * @param string $alias The alias name for the class. + * @param bool $autoload [optional] Whether to autoload if the original class is not found. + * @return bool true on success or false on failure. + */ +#!---!# + +/** + * Returns an array with the names of included or required files + * @link https://php.net/manual/en/function.get-included-files.php + * @return string[] an array of the names of all files. + *

+ *

+ * The script originally called is considered an "included file," so it will + * be listed together with the files referenced by + * include and family. + *

+ *

+ * Files that are included or required multiple times only show up once in + * the returned array. + */ +#!---!# + +/** + * Alias of get_included_files + * @link https://php.net/manual/en/function.get-required-files.php + * @return string[] + */ +#!---!# + +/** + * Checks if the object has this class as one of its parents + * @link https://php.net/manual/en/function.is-subclass-of.php + * @param mixed $object

+ * A class name or an object instance + *

+ * @param string $class_name

+ * The class name + *

+ * @param bool $allow_string [optional]

+ * If this parameter set to false, string class name as object is not allowed. + * This also prevents from calling autoloader if the class doesn't exist. + *

+ * @return bool This function returns true if the object object, + * belongs to a class which is a subclass of + * class_name, false otherwise. + */ +#!---!# + +/** + * Checks if the object is of this class or has this class as one of its parents + * @link https://php.net/manual/en/function.is-a.php + * @param object|string $object

+ * The tested object + *

+ * @param string $class_name

+ * The class name + *

+ * @param bool $allow_string [optional]

+ * If this parameter set to FALSE, string class name as object + * is not allowed. This also prevents from calling autoloader if the class doesn't exist. + *

+ * @return bool TRUE if the object is of this class or has this class as one of + * its parents, FALSE otherwise. + */ +#!---!# + +/** + * Get the default properties of the class + * @link https://php.net/manual/en/function.get-class-vars.php + * @param string $class_name

+ * The class name + *

+ * @return array an associative array of declared properties visible from the + * current scope, with their default value. + * The resulting array elements are in the form of + * varname => value. + */ +#!---!# + +/** + * Gets the properties of the given object + * @link https://php.net/manual/en/function.get-object-vars.php + * @param object $object

+ * An object instance. + *

+ * @return array an associative array of defined object accessible non-static properties + * for the specified object in scope. If a property have + * not been assigned a value, it will be returned with a null value. + */ +#!---!# + +/** + * Gets the class methods' names + * @link https://php.net/manual/en/function.get-class-methods.php + * @param mixed $class_name

+ * The class name or an object instance + *

+ * @return array an array of method names defined for the class specified by + * class_name. In case of an error, it returns null. + */ +#!---!# + +/** + * Generates a user-level error/warning/notice message + * @link https://php.net/manual/en/function.trigger-error.php + * @param string $error_msg

+ * The designated error message for this error. It's limited to 1024 + * characters in length. Any additional characters beyond 1024 will be + * truncated. + *

+ * @param int $error_type [optional]

+ * The designated error type for this error. It only works with the E_USER + * family of constants, and will default to E_USER_NOTICE. + *

+ * @return bool This function returns false if wrong error_type is + * specified, true otherwise. + */ +#!---!# + +/** + * Alias of trigger_error + * @link https://php.net/manual/en/function.user-error.php + * @param string $message + * @param int $error_type [optional] + * @return bool This function returns false if wrong error_type is + * specified, true otherwise. + */ +#!---!# + +/** + * Sets a user-defined error handler function + * @link https://php.net/manual/en/function.set-error-handler.php + * @param callable|null $error_handler

+ * The user function needs to accept two parameters: the error code, and a + * string describing the error. Then there are three optional parameters + * that may be supplied: the filename in which the error occurred, the + * line number in which the error occurred, and the context in which the + * error occurred (an array that points to the active symbol table at the + * point the error occurred). The function can be shown as: + *

+ *

+ * handler + * interrno + * stringerrstr + * stringerrfile + * interrline + * arrayerrcontext + * errno + * The first parameter, errno, contains the + * level of the error raised, as an integer. + * @param int $error_types [optional]

+ * Can be used to mask the triggering of the + * error_handler function just like the error_reporting ini setting + * controls which errors are shown. Without this mask set the + * error_handler will be called for every error + * regardless to the setting of the error_reporting setting. + *

+ * @return callable|null a string containing the previously defined error handler (if any). If + * the built-in error handler is used null is returned. null is also returned + * in case of an error such as an invalid callback. If the previous error handler + * was a class method, this function will return an indexed array with the class + * and the method name. + */ +#!---!# + +/** + * Restores the previous error handler function + * @link https://php.net/manual/en/function.restore-error-handler.php + * @return bool This function always returns true. + */ +#!---!# + +/** + * Sets a user-defined exception handler function + * @link https://php.net/manual/en/function.set-exception-handler.php + * @param callable|null $exception_handler

+ * Name of the function to be called when an uncaught exception occurs. + * This function must be defined before calling + * set_exception_handler. This handler function + * needs to accept one parameter, which will be the exception object that + * was thrown. + * NULL may be passed instead, to reset this handler to its default state. + *

+ * @return callable|null the name of the previously defined exception handler, or null on error. If + * no previous handler was defined, null is also returned. + */ +#!---!# + +/** + * Restores the previously defined exception handler function + * @link https://php.net/manual/en/function.restore-exception-handler.php + * @return bool This function always returns true. + */ +#!---!# + +/** + * Returns an array with the name of the defined classes + * @link https://php.net/manual/en/function.get-declared-classes.php + * @return array an array of the names of the declared classes in the current + * script. + *

+ *

+ * Note that depending on what extensions you have compiled or + * loaded into PHP, additional classes could be present. This means that + * you will not be able to define your own classes using these + * names. There is a list of predefined classes in the Predefined Classes section of + * the appendices. + */ +#!---!# + +/** + * Returns an array of all declared interfaces + * @link https://php.net/manual/en/function.get-declared-interfaces.php + * @return array an array of the names of the declared interfaces in the current + * script. + */ +#!---!# + +/** + * Returns an array of all declared traits + * @return array with names of all declared traits in values. Returns NULL in case of a failure. + * @link https://secure.php.net/manual/en/function.get-declared-traits.php + * @see class_uses() + * @since 5.4 + */ +#!---!# + +/** + * Returns an array of all defined functions + * @link https://php.net/manual/en/function.get-defined-functions.php + * @param bool $exclude_disabled [optional] Whether disabled functions should be excluded from the return value. + * @return array an multidimensional array containing a list of all defined + * functions, both built-in (internal) and user-defined. The internal + * functions will be accessible via $arr["internal"], and + * the user defined ones using $arr["user"] (see example + * below). + */ +#!---!# + +/** + * Returns an array of all defined variables + * @link https://php.net/manual/en/function.get-defined-vars.php + * @return array A multidimensional array with all the variables. + */ +#!---!# + +/** + * Create an anonymous (lambda-style) function + * @link https://php.net/manual/en/function.create-function.php + * @param string $args

+ * The function arguments. + *

+ * @param string $code

+ * The function code. + *

+ * @return string|false a unique function name as a string, or false on error. + * @deprecated 7.2 Use anonymous functions instead. + * @removed 8.0 + */ +#!---!# + +/** + * Returns the resource type + * @link https://php.net/manual/en/function.get-resource-type.php + * @param resource $handle

+ * The evaluated resource handle. + *

+ * @return string If the given handle is a resource, this function + * will return a string representing its type. If the type is not identified + * by this function, the return value will be the string + * Unknown. + *

+ *

+ * This function will return false and generate an error if + * handle is not a resource. + */ +#!---!# + +/** + * Returns an array with the names of all modules compiled and loaded + * @link https://php.net/manual/en/function.get-loaded-extensions.php + * @param bool $zend_extensions [optional]

+ * Only return Zend extensions, if not then regular extensions, like + * mysqli are listed. Defaults to false (return regular extensions). + *

+ * @return array an indexed array of all the modules names. + */ +#!---!# + +/** + * Find out whether an extension is loaded + * @link https://php.net/manual/en/function.extension-loaded.php + * @param string $name

+ * The extension name. + *

+ *

+ * You can see the names of various extensions by using + * phpinfo or if you're using the + * CGI or CLI version of + * PHP you can use the -m switch to + * list all available extensions: + *

+ * $ php -m
+ * [PHP Modules]
+ * xml
+ * tokenizer
+ * standard
+ * sockets
+ * session
+ * posix
+ * pcre
+ * overload
+ * mysql
+ * mbstring
+ * ctype
+ * [Zend Modules]
+ * 
+ *

+ * @return bool true if the extension identified by name + * is loaded, false otherwise. + */ +#!---!# + +/** + * Returns an array with the names of the functions of a module + * @link https://php.net/manual/en/function.get-extension-funcs.php + * @param string $module_name

+ * The module name. + *

+ *

+ * This parameter must be in lowercase. + *

+ * @return string[]|false an array with all the functions, or false if + * module_name is not a valid extension. + */ +#!---!# + +/** + * Returns an associative array with the names of all the constants and their values + * @link https://php.net/manual/en/function.get-defined-constants.php + * @param bool $categorize [optional]

+ * Causing this function to return a multi-dimensional + * array with categories in the keys of the first dimension and constants + * and their values in the second dimension. + * + * define("MY_CONSTANT", 1); + * print_r(get_defined_constants(true)); + * + * The above example will output something similar to: + *

+ * Array
+ * (
+ * [Core] => Array
+ * (
+ * [E_ERROR] => 1
+ * [E_WARNING] => 2
+ * [E_PARSE] => 4
+ * [E_NOTICE] => 8
+ * [E_CORE_ERROR] => 16
+ * [E_CORE_WARNING] => 32
+ * [E_COMPILE_ERROR] => 64
+ * [E_COMPILE_WARNING] => 128
+ * [E_USER_ERROR] => 256
+ * [E_USER_WARNING] => 512
+ * [E_USER_NOTICE] => 1024
+ * [E_STRICT] => 2048
+ * [E_RECOVERABLE_ERROR] => 4096
+ * [E_DEPRECATED] => 8192
+ * [E_USER_DEPRECATED] => 16384
+ * [E_ALL] => 32767
+ * [TRUE] => 1
+ * )
+ * [pcre] => Array
+ * (
+ * [PREG_PATTERN_ORDER] => 1
+ * [PREG_SET_ORDER] => 2
+ * [PREG_OFFSET_CAPTURE] => 256
+ * [PREG_SPLIT_NO_EMPTY] => 1
+ * [PREG_SPLIT_DELIM_CAPTURE] => 2
+ * [PREG_SPLIT_OFFSET_CAPTURE] => 4
+ * [PREG_GREP_INVERT] => 1
+ * )
+ * [user] => Array
+ * (
+ * [MY_CONSTANT] => 1
+ * )
+ * )
+ * 
+ *

+ * @return array + */ +#!---!# + +/** + * Generates a backtrace + * @link https://php.net/manual/en/function.debug-backtrace.php + * @param int $options [optional]

+ * As of 5.3.6, this parameter is a bitmask for the following options: + * + * debug_backtrace options + * + * + * + * + * + * + * + * + *
DEBUG_BACKTRACE_PROVIDE_OBJECT + * Whether or not to populate the "object" index. + *
DEBUG_BACKTRACE_IGNORE_ARGS + * Whether or not to omit the "args" index, and thus all the function/method arguments, + * to save memory. + *
+ * Before 5.3.6, the only values recognized are true or false, which are the same as + * setting or not setting the DEBUG_BACKTRACE_PROVIDE_OBJECT option respectively. + *

+ * @param int $limit [optional]

+ * As of 5.4.0, this parameter can be used to limit the number of stack frames returned. + * By default (limit=0) it returns all stack frames. + *

+ * @return array an array of associative arrays. The possible returned elements + * are as follows: + *

+ *

+ * + * Possible returned elements from debug_backtrace + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
&Name;&Type;Description
functionstring + * The current function name. See also + * __FUNCTION__. + *
lineinteger + * The current line number. See also + * __LINE__. + *
filestring + * The current file name. See also + * __FILE__. + *
classstring + * The current class name. See also + * __CLASS__ + *
objectobject + * The current object. + *
typestring + * The current call type. If a method call, "->" is returned. If a static + * method call, "::" is returned. If a function call, nothing is returned. + *
argsarray + * If inside a function, this lists the functions arguments. If + * inside an included file, this lists the included file name(s). + *
+ */ +#!---!# + +/** + * Prints a backtrace + * @link https://php.net/manual/en/function.debug-print-backtrace.php + * @param int $options [optional]

+ * As of 5.3.6, this parameter is a bitmask for the following options: + * + * debug_print_backtrace options + * + * + * + * + *
DEBUG_BACKTRACE_IGNORE_ARGS + * Whether or not to omit the "args" index, and thus all the function/method arguments, + * to save memory. + *
+ *

+ * @param int $limit [optional]

+ * As of 5.4.0, this parameter can be used to limit the number of stack frames printed. + * By default (limit=0) it prints all stack frames. + *

+ * @return void + */ +#!---!# + +/** + * Forces collection of any existing garbage cycles + * @link https://php.net/manual/en/function.gc-collect-cycles.php + * @return int number of collected cycles. + */ +#!---!# + +/** + * Returns status of the circular reference collector + * @link https://php.net/manual/en/function.gc-enabled.php + * @return bool true if the garbage collector is enabled, false otherwise. + */ +#!---!# + +/** + * Activates the circular reference collector + * @link https://php.net/manual/en/function.gc-enable.php + * @return void + */ +#!---!# + +/** + * Deactivates the circular reference collector + * @link https://php.net/manual/en/function.gc-disable.php + * @return void + */ +#!---!# + +/** + * Gets information about the garbage collector + * @link https://php.net/manual/en/function.gc-status.php + * @return array associative array with the following elements: + *
    + *
  • "runs"
  • + *
  • "collected"
  • + *
  • "threshold"
  • + *
  • "roots"
  • + *
+ * @since 7.3 + */ +#!---!# + +/** + * Reclaims memory used by the Zend Engine memory manager + * @link https://php.net/manual/en/function.gc-mem-caches.php + * @return int Returns the number of bytes freed. + * @since 7.0 + */ +#!---!# + +/** + * Returns active resources + * @link https://php.net/manual/en/function.get-resources.php + * @param string $type [optional]

+ * + * If defined, this will cause get_resources() to only return resources of the given type. A list of resource types is available. + * + * If the string Unknown is provided as the type, then only resources that are of an unknown type will be returned. + * + * If omitted, all resources will be returned. + *

+ * @return array Returns an array of currently active resources, indexed by resource number. + * @since 7.0 + */ +#!---!# diff --git a/tests/Printer/examples/php_core1.test b/tests/Printer/examples/php_core1.test new file mode 100644 index 00000000..ee1be1ec --- /dev/null +++ b/tests/Printer/examples/php_core1.test @@ -0,0 +1,51 @@ +/** + * Sets a user-defined error handler function + * @link https://php.net/manual/en/function.set-error-handler.php + * @param callable|null $error_handler

+ * The user function needs to accept two parameters... + *

+ *

+ * handler + * arrayerrcontext + * errno + * The first parameter, errno, contains the + * level of the error raised, as an integer. + * @param int $error_types [optional]

+ * Can be used to mask the triggering of the + * error_handler function just like the error_reporting ini setting + * controls which errors are shown. Without this mask set the + * error_handler will be called for every error + * regardless to the setting of the error_reporting setting. + *

+ * @return callable|null a string containing the previously defined error handler (if any). If + * the built-in error handler is used null is returned. null is also returned + * in case of an error such as an invalid callback. If the previous error handler + * was a class method, this function will return an indexed array with the class + * and the method name. + */ + --- +/** + * Sets a user-defined error handler function + * UnknownTag https://php.net/manual/en/function.set-error-handler.php + * ParamNode(UnionNode(ClassNode(callable)|NullNode()),VariableNode($error_handler),TextNode(

)) + * The user function needs to accept two parameters... + *

+ *

+ * handler + * arrayerrcontext + * errno + * The first parameter, errno, contains the + * level of the error raised, as an integer. + * @param int $error_types [optional]

+ * Can be used to mask the triggering of the + * error_handler function just like the error_reporting ini setting + * controls which errors are shown. Without this mask set the + * error_handler will be called for every error + * regardless to the setting of the error_reporting setting. + *

+ * ReturnNode(UnionNode(ClassNode(callable)|NullNode()),TextNode(a string containing the previously defined error handler (if any). If)) + * the built-in error handler is used null is returned. null is also returned + * in case of an error such as an invalid callback. If the previous error handler + * was a class method, this function will return an indexed array with the class + * and the method name. + */ From 8cf1de297f67908ee1aef655ff646d6537f031d3 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 22:20:24 +0000 Subject: [PATCH 38/63] Support method parameters --- lib/Ast/MethodNode.php | 65 ++++++++++++++++++++++++++- lib/Ast/ParameterList.php | 42 +++++++++++++++++ lib/Ast/ParameterNode.php | 31 +++++++++++++ lib/Lexer.php | 10 +++-- lib/Parser.php | 57 +++++++++++++++++++++-- lib/Printer/TestPrinter.php | 43 ++++++++++++++++++ lib/Token.php | 2 + tests/Printer/examples/method1.test | 2 +- tests/Printer/examples/method2.test | 7 +++ tests/Printer/examples/method3.test | 7 +++ tests/Printer/examples/php_core1.test | 6 +-- 11 files changed, 259 insertions(+), 13 deletions(-) create mode 100644 lib/Ast/ParameterList.php create mode 100644 lib/Ast/ParameterNode.php create mode 100644 tests/Printer/examples/method2.test create mode 100644 tests/Printer/examples/method3.test diff --git a/lib/Ast/MethodNode.php b/lib/Ast/MethodNode.php index 26002a02..4a35dbfe 100644 --- a/lib/Ast/MethodNode.php +++ b/lib/Ast/MethodNode.php @@ -16,10 +16,48 @@ class MethodNode extends TagNode */ private $name; - public function __construct(?TypeNode $type, ?Token $name) + /** + * @var Token|null + */ + private $static; + + /** + * @var ParameterList|null + */ + private $parameters; + + /** + * @var TextNode|null + */ + private $text; + + /** + * @var Token|null + */ + private $parenOpen; + + /** + * @var Token|null + */ + private $parenClose; + + public function __construct( + ?TypeNode $type, + ?Token $name, + ?Token $static, + ?Token $parenOpen, + ?ParameterList $parameters, + ?Token $parenClose, + ?TextNode $text + ) { $this->type = $type; $this->name = $name; + $this->static = $static; + $this->parameters = $parameters; + $this->text = $text; + $this->parenOpen = $parenOpen; + $this->parenClose = $parenClose; } public function name(): ?Token @@ -31,4 +69,29 @@ public function type(): ?TypeNode { return $this->type; } + + public function static(): ?Token + { + return $this->static; + } + + public function parameters(): ?ParameterList + { + return $this->parameters; + } + + public function text(): ?TextNode + { + return $this->text; + } + + public function parenOpen(): ?Token + { + return $this->parenOpen; + } + + public function parenClose(): ?Token + { + return $this->parenClose; + } } diff --git a/lib/Ast/ParameterList.php b/lib/Ast/ParameterList.php new file mode 100644 index 00000000..1c89f370 --- /dev/null +++ b/lib/Ast/ParameterList.php @@ -0,0 +1,42 @@ + + */ +class ParameterList implements IteratorAggregate, Countable +{ + /** + * @var ParameterNode[] + */ + private $parameterList; + + /** + * @param ParameterNode[] $parameterList + */ + public function __construct(array $parameterList) + { + $this->parameterList = $parameterList; + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->parameterList); + } + + /** + * {@inheritDoc} + */ + public function count() + { + return count($this->parameterList); + } +} diff --git a/lib/Ast/ParameterNode.php b/lib/Ast/ParameterNode.php new file mode 100644 index 00000000..1489ab8e --- /dev/null +++ b/lib/Ast/ParameterNode.php @@ -0,0 +1,31 @@ +type = $type; + $this->name = $name; + } + + public function name(): ?VariableNode + { + return $this->name; + } + + public function type(): ?TypeNode + { + return $this->type; + } +} diff --git a/lib/Lexer.php b/lib/Lexer.php index 74e1769d..2d3a2d55 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -8,7 +8,7 @@ final class Lexer * @var string[] */ private const PATTERNS = [ - '^/\*+', // start tag + '/\*+', // start tag '\*/', // close tag '\*', // leading tag '\[\]', //tag @@ -17,9 +17,9 @@ final class Lexer '\s+', // whitespace ',', // comma '\|', // bar (union) - '\{', '\}', '\[', '\]', '<', '>', // brackets + '(', ')', '\{', '\}', '\[', '\]', '<', '>', // brackets '\$[a-zA-Z0-9_\x80-\xff]+', // variable - '[^a-zA-Z0-9_\x80-\xff\\\]+', // label + '[a-zA-Z0-9_\\\]+', // label ]; private const TOKEN_VALUE_MAP = [ @@ -29,6 +29,8 @@ final class Lexer '<' => Token::T_BRACKET_ANGLE_OPEN, '{' => Token::T_BRACKET_CURLY_OPEN, '}' => Token::T_BRACKET_CURLY_CLOSE, + '(' => Token::T_PAREN_OPEN, + ')' => Token::T_PAREN_CLOSE, ',' => Token::T_COMMA, '[]' => Token::T_LIST, '?' => Token::T_NULLABLE, @@ -39,7 +41,7 @@ final class Lexer * @var string[] */ private const IGNORE_PATTERNS = [ - '\s+' + '\s+', ]; /** diff --git a/lib/Parser.php b/lib/Parser.php index 07c2f029..4b4378f6 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -7,6 +7,8 @@ use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\MixinNode; +use Phpactor\Docblock\Ast\ParameterList; +use Phpactor\Docblock\Ast\ParameterNode; use Phpactor\Docblock\Ast\PropertyNode; use Phpactor\Docblock\Ast\ReturnNode; use Phpactor\Docblock\Ast\TextNode; @@ -34,7 +36,7 @@ final class Parser private $tokens; private const SCALAR_TYPES = [ - 'int', 'float', 'bool', 'string' + 'int', 'float', 'bool', 'string', 'mixed', 'callable' ]; public function parse(Tokens $tokens): Node @@ -115,15 +117,30 @@ private function parseVar(): VarNode private function parseMethod(): MethodNode { $this->tokens->chomp(Token::T_TAG); - $type = $name = null; + $type = $name = $parameterList = $open = $close = null; + $static = null; + + if ($this->tokens->ifNextIs(Token::T_LABEL)) { + if ($this->tokens->current->value === 'static') { + $static = $this->tokens->chomp(); + } + } + if ($this->ifType()) { $type = $this->parseTypes(); } - if ($this->tokens->ifNextIs(Token::T_LABEL)) { + + if ($this->tokens->if(Token::T_LABEL)) { $name = $this->tokens->chomp(); } - return new MethodNode($type, $name); + if ($this->tokens->if(Token::T_PAREN_OPEN)) { + $open = $this->tokens->chomp(Token::T_PAREN_OPEN); + $parameterList = $this->parseParameterList(); + $close = $this->tokens->chomp(Token::T_PAREN_CLOSE); + } + + return new MethodNode($type, $name, $static, $open, $parameterList, $close, $this->parseText()); } private function parseProperty(): PropertyNode @@ -241,6 +258,37 @@ private function parseTypeList(string $delimiter = ','): TypeList return new TypeList($types); } + private function parseParameterList(): ?ParameterList + { + if ($this->tokens->if(Token::T_PAREN_CLOSE)) { + return null; + } + + $parameters = []; + while (true) { + $parameters[] = $this->parseParameter(); + if ($this->tokens->if(Token::T_COMMA)) { + $this->tokens->chomp(); + continue; + } + break; + } + + return new ParameterList($parameters); + } + + private function parseParameter(): ParameterNode + { + $type = $name = null; + if ($this->tokens->if(Token::T_LABEL)) { + $type = $this->parseTypes(); + } + if ($this->tokens->if(Token::T_VARIABLE)) { + $name = $this->parseVariable(); + } + return new ParameterNode($type, $name); + } + private function parseDeprecated(): DeprecatedNode { $this->tokens->chomp(); @@ -307,4 +355,5 @@ private function ifType(): bool { return $this->tokens->if(Token::T_LABEL) || $this->tokens->if(Token::T_NULLABLE); } + } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 59ab9e07..b61fb088 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -7,6 +7,8 @@ use Phpactor\Docblock\Ast\Element; use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\MixinNode; +use Phpactor\Docblock\Ast\ParameterList; +use Phpactor\Docblock\Ast\ParameterNode; use Phpactor\Docblock\Ast\PropertyNode; use Phpactor\Docblock\Ast\ReturnNode; use Phpactor\Docblock\Ast\TextNode; @@ -140,6 +142,11 @@ private function render(?Element $node): void return; } + if ($node instanceof ParameterNode) { + $this->renderParameter($node); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) @@ -241,6 +248,17 @@ private function renderMethod(MethodNode $node): void $this->out[] = ','; $this->render($node->name()); } + if ($node->static()) { + $this->out[] = ',static'; + } + if ($node->parameters()) { + $this->out[] = ','; + $this->renderParameterList($node->parameters()); + } + if ($node->text()) { + $this->out[] = ','; + $this->render($node->text()); + } $this->out[] = ')'; } @@ -286,4 +304,29 @@ private function renderUnion(UnionNode $node): void $this->renderTypeList($node->types(), '|'); $this->out[] = ')'; } + + private function renderParameterList(ParameterList $list): void + { + $this->out[] = 'ParameterList('; + foreach ($list as $i => $parameter) { + $this->render($parameter); + if ($i + 1 !== $list->count()) { + $this->out[] = ','; + } + } + $this->out[] = ')'; + } + + private function renderParameter(ParameterNode $node): void + { + $this->out[] = $node->shortName() . '('; + if ($node->name()) { + $this->render($node->name()); + } + if ($node->type()) { + $this->out[] = ','; + $this->render($node->type()); + } + $this->out[] = ')'; + } } diff --git a/lib/Token.php b/lib/Token.php index 5ebb40c8..6fc4e26c 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -24,6 +24,8 @@ final class Token implements Element public const T_BRACKET_ANGLE_CLOSE = 'BRACKET_ANGLE_CLOSE'; public const T_BRACKET_CURLY_OPEN = 'BRACKET_CURLY_OPEN'; public const T_BRACKET_CURLY_CLOSE = 'BRACKET_CURLY_CLOSE'; + public const T_PAREN_OPEN = 'PAREN_OPEN'; + public const T_PAREN_CLOSE = 'PAREN_CLOSE'; /** * @var int diff --git a/tests/Printer/examples/method1.test b/tests/Printer/examples/method1.test index 63ba6fae..826a9b5a 100644 --- a/tests/Printer/examples/method1.test +++ b/tests/Printer/examples/method1.test @@ -3,5 +3,5 @@ */ --- /** - * MethodNode(ClassNode(Foobar),foobar)() + * MethodNode(ClassNode(Foobar),foobar) */ diff --git a/tests/Printer/examples/method2.test b/tests/Printer/examples/method2.test new file mode 100644 index 00000000..ab66ad1f --- /dev/null +++ b/tests/Printer/examples/method2.test @@ -0,0 +1,7 @@ +/** + * @method static Foobar foobar() + */ +--- +/** + * MethodNode(ClassNode(Foobar),foobar,static) + */ diff --git a/tests/Printer/examples/method3.test b/tests/Printer/examples/method3.test new file mode 100644 index 00000000..8600ba44 --- /dev/null +++ b/tests/Printer/examples/method3.test @@ -0,0 +1,7 @@ +/** + * @method static bool allAlnum(mixed $value) Assert that value is alphanumeric for all values. + */ +--- +/** + * MethodNode(ScalarNode(bool),allAlnum,static,ParameterList(ParameterNode(VariableNode($value),ScalarNode(mixed))),TextNode(Assert that value is alphanumeric for all values.)) + */ diff --git a/tests/Printer/examples/php_core1.test b/tests/Printer/examples/php_core1.test index ee1be1ec..e555336e 100644 --- a/tests/Printer/examples/php_core1.test +++ b/tests/Printer/examples/php_core1.test @@ -27,7 +27,7 @@ /** * Sets a user-defined error handler function * UnknownTag https://php.net/manual/en/function.set-error-handler.php - * ParamNode(UnionNode(ClassNode(callable)|NullNode()),VariableNode($error_handler),TextNode(

)) + * ParamNode(UnionNode(ScalarNode(callable)|NullNode()),VariableNode($error_handler),TextNode(

)) * The user function needs to accept two parameters... *

*

@@ -36,14 +36,14 @@ * errno * The first parameter, errno, contains the * level of the error raised, as an integer. - * @param int $error_types [optional]

+ * ParamNode(ScalarNode(int),VariableNode($error_types),TextNode( [optional]

)) * Can be used to mask the triggering of the * error_handler function just like the error_reporting ini setting * controls which errors are shown. Without this mask set the * error_handler will be called for every error * regardless to the setting of the error_reporting setting. *

- * ReturnNode(UnionNode(ClassNode(callable)|NullNode()),TextNode(a string containing the previously defined error handler (if any). If)) + * ReturnNode(UnionNode(ScalarNode(callable)|NullNode()),TextNode(a string containing the previously defined error handler (if any). If)) * the built-in error handler is used null is returned. null is also returned * in case of an error such as an invalid callback. If the previous error handler * was a class method, this function will return an indexed array with the class From 0e38ec911efc171a009317fafa1f0b66ffb6c2fb Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 23 Jan 2021 23:01:24 +0000 Subject: [PATCH 39/63] Support for default method values --- composer.json | 3 +- lib/Ast/Node.php | 2 +- lib/Ast/ParameterNode.php | 13 +- lib/Ast/Value/NullValue.php | 32 ++++ lib/Ast/Value/UnkownValue.php | 0 lib/Ast/ValueNode.php | 11 ++ lib/Lexer.php | 4 +- lib/Parser.php | 23 ++- lib/Printer/TestPrinter.php | 15 ++ lib/Token.php | 1 + lib/Tokens.php | 12 ++ tests/Benchmark/AbstractParserBenchCase.php | 10 +- tests/Benchmark/examples/assert.example | 186 ++++++++++++++++++++ tests/Printer/examples/method4.test | 7 + tests/Printer/examples/method5.test | 7 + tests/Printer/examples/method6.test | 7 + 16 files changed, 325 insertions(+), 8 deletions(-) create mode 100644 lib/Ast/Value/NullValue.php create mode 100644 lib/Ast/Value/UnkownValue.php create mode 100644 lib/Ast/ValueNode.php create mode 100644 tests/Benchmark/examples/assert.example create mode 100644 tests/Printer/examples/method4.test create mode 100644 tests/Printer/examples/method5.test create mode 100644 tests/Printer/examples/method6.test diff --git a/composer.json b/composer.json index 93f4220e..bd7deaf2 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "phpunit/phpunit": "^9.0", "phpspec/prophecy-phpunit": "^2.0", "symfony/var-dumper": "^5.2", - "phpstan/phpdoc-parser": "^0.4.10" + "phpstan/phpdoc-parser": "^0.4.10", + "jetbrains/phpstorm-stubs": "^2020.2" }, "extra": { "branch-alias": { diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 8ecbfb98..740956d2 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -2,7 +2,7 @@ namespace Phpactor\Docblock\Ast; -class Node implements Element +abstract class Node implements Element { public function shortName(): string { diff --git a/lib/Ast/ParameterNode.php b/lib/Ast/ParameterNode.php index 1489ab8e..ee2c49da 100644 --- a/lib/Ast/ParameterNode.php +++ b/lib/Ast/ParameterNode.php @@ -13,10 +13,16 @@ class ParameterNode extends Node */ private $name; - public function __construct(?TypeNode $type, ?VariableNode $name) + /** + * @var ValueNode|null + */ + private $default; + + public function __construct(?TypeNode $type, ?VariableNode $name, ?ValueNode $default) { $this->type = $type; $this->name = $name; + $this->default = $default; } public function name(): ?VariableNode @@ -28,4 +34,9 @@ public function type(): ?TypeNode { return $this->type; } + + public function default(): ?ValueNode + { + return $this->default; + } } diff --git a/lib/Ast/Value/NullValue.php b/lib/Ast/Value/NullValue.php new file mode 100644 index 00000000..50e444fc --- /dev/null +++ b/lib/Ast/Value/NullValue.php @@ -0,0 +1,32 @@ +null = $null; + } + + public function null(): Token + { + return $this->null; + } + + /** + * {@inheritDoc} + */ + public function value() + { + return null; + } +} diff --git a/lib/Ast/Value/UnkownValue.php b/lib/Ast/Value/UnkownValue.php new file mode 100644 index 00000000..e69de29b diff --git a/lib/Ast/ValueNode.php b/lib/Ast/ValueNode.php new file mode 100644 index 00000000..7fe42ebd --- /dev/null +++ b/lib/Ast/ValueNode.php @@ -0,0 +1,11 @@ +', // brackets '\$[a-zA-Z0-9_\x80-\xff]+', // variable '[a-zA-Z0-9_\\\]+', // label @@ -35,6 +36,7 @@ final class Lexer '[]' => Token::T_LIST, '?' => Token::T_NULLABLE, '|' => Token::T_BAR, + '=' => Token::T_EQUALS, ]; /** @@ -112,7 +114,7 @@ private function resolveType(string $value, ?array $prevChunk = null): string return Token::T_WHITESPACE; } - if (ctype_alpha($value) || false !== strpos($value, '\\')) { + if (ctype_alpha($value[0]) || $value[0] === '\\') { return Token::T_LABEL; } diff --git a/lib/Parser.php b/lib/Parser.php index 4b4378f6..d0b83e3f 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -25,6 +25,8 @@ use Phpactor\Docblock\Ast\Type\ScalarNode; use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; +use Phpactor\Docblock\Ast\ValueNode; +use Phpactor\Docblock\Ast\Value\NullValue; use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; @@ -137,7 +139,7 @@ private function parseMethod(): MethodNode if ($this->tokens->if(Token::T_PAREN_OPEN)) { $open = $this->tokens->chomp(Token::T_PAREN_OPEN); $parameterList = $this->parseParameterList(); - $close = $this->tokens->chomp(Token::T_PAREN_CLOSE); + $close = $this->tokens->chompIf(Token::T_PAREN_CLOSE); } return new MethodNode($type, $name, $static, $open, $parameterList, $close, $this->parseText()); @@ -279,14 +281,18 @@ private function parseParameterList(): ?ParameterList private function parseParameter(): ParameterNode { - $type = $name = null; + $type = $name = $default = null; if ($this->tokens->if(Token::T_LABEL)) { $type = $this->parseTypes(); } if ($this->tokens->if(Token::T_VARIABLE)) { $name = $this->parseVariable(); } - return new ParameterNode($type, $name); + if ($this->tokens->if(Token::T_EQUALS)) { + $equals = $this->tokens->chomp(); + $default = $this->parseValue(); + } + return new ParameterNode($type, $name, $default); } private function parseDeprecated(): DeprecatedNode @@ -356,4 +362,15 @@ private function ifType(): bool return $this->tokens->if(Token::T_LABEL) || $this->tokens->if(Token::T_NULLABLE); } + private function parseValue(): ?ValueNode + { + if ($this->tokens->if(Token::T_LABEL)) { + if (strtolower($this->tokens->current->value) === 'null') { + return new NullValue($this->tokens->chomp()); + } + } + + return null; + } + } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index b61fb088..7b95da6a 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -23,6 +23,7 @@ use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; +use Phpactor\Docblock\Ast\ValueNode; use Phpactor\Docblock\Ast\VarNode; use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Printer; @@ -147,6 +148,11 @@ private function render(?Element $node): void return; } + if ($node instanceof ValueNode) { + $this->renderValue($node); + return; + } + throw new RuntimeException(sprintf( 'Do not know how to render "%s"', get_class($node) @@ -327,6 +333,15 @@ private function renderParameter(ParameterNode $node): void $this->out[] = ','; $this->render($node->type()); } + if ($node->default()) { + $this->out[] = ','; + $this->render($node->default()); + } $this->out[] = ')'; } + + private function renderValue(ValueNode $node) + { + $this->out[] = json_encode($node->value()); + } } diff --git a/lib/Token.php b/lib/Token.php index 6fc4e26c..9c288387 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -14,6 +14,7 @@ final class Token implements Element public const T_NULLABLE = 'NULLABLE'; public const T_BAR = 'BAR'; public const T_TAG = 'TAG'; + public const T_EQUALS = 'EQUALS'; public const T_COMMA = 'COMMA'; public const T_LIST = 'LIST'; public const T_LABEL = 'LABEL'; diff --git a/lib/Tokens.php b/lib/Tokens.php index c5bba974..94814575 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -85,6 +85,18 @@ public function chomp(?string $type = null): ?Token return $token; } + /** + * Chomp only if the current node is the given type + */ + public function chompIf(string $type): ?Token + { + if ($this->current->type === $type) { + return $this->chomp($type); + } + + return null; + } + public function ifNextIs(string $type): bool { if ($this->next()->type === $type) { diff --git a/tests/Benchmark/AbstractParserBenchCase.php b/tests/Benchmark/AbstractParserBenchCase.php index 93db977b..9f201f21 100644 --- a/tests/Benchmark/AbstractParserBenchCase.php +++ b/tests/Benchmark/AbstractParserBenchCase.php @@ -34,16 +34,24 @@ public function benchPhpCore(array $params): void $this->parse(trim($params['doc'])); } + /** + * @return Generator + */ public function provideCoreDocs(): Generator { $contents = file_get_contents(__DIR__ . '/examples/php_core.example'); foreach (explode('#!---!#', $contents) as $doc) { - yield [ + yield str_replace("\n", '', substr($doc, 0, 10)) => [ 'doc' => $doc ]; } } + public function benchAssert(): void + { + $this->parse(file_get_contents(__DIR__ . '/examples/assert.example')); + } + abstract public function setUp(): void; abstract public function parse(string $doc): void; } diff --git a/tests/Benchmark/examples/assert.example b/tests/Benchmark/examples/assert.example new file mode 100644 index 00000000..ddbbb345 --- /dev/null +++ b/tests/Benchmark/examples/assert.example @@ -0,0 +1,186 @@ +/** + * Assert library. + * + * @author Benjamin Eberlei + * + * @method static bool allAlnum(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values. + * @method static bool allBase64(string $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. + * @method static bool allBetween(mixed $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit for all values. + * @method static bool allBetweenExclusive(mixed $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit for all values. + * @method static bool allBetweenLength(mixed $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths for all values. + * @method static bool allBoolean(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean for all values. + * @method static bool allChoice(mixed $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices for all values. + * @method static bool allChoicesNotEmpty(array $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content for all values. + * @method static bool allClassExists(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists for all values. + * @method static bool allContains(mixed $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars for all values. + * @method static bool allCount(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count for all values. + * @method static bool allDate(string $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format for all values. + * @method static bool allDefined(mixed $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. + * @method static bool allDigit(mixed $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit for all values. + * @method static bool allDirectory(string $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists for all values. + * @method static bool allE164(string $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number for all values. + * @method static bool allEmail(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) for all values. + * @method static bool allEndsWith(mixed $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars for all values. + * @method static bool allEq(mixed $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) for all values. + * @method static bool allEqArraySubset(mixed $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset for all values. + * @method static bool allExtensionLoaded(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded for all values. + * @method static bool allExtensionVersion(string $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed for all values. + * @method static bool allFalse(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False for all values. + * @method static bool allFile(string $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists for all values. + * @method static bool allFloat(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float for all values. + * @method static bool allGreaterOrEqualThan(mixed $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit for all values. + * @method static bool allGreaterThan(mixed $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit for all values. + * @method static bool allImplementsInterface(mixed $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface for all values. + * @method static bool allInArray(mixed $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() for all values. + * @method static bool allInteger(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer for all values. + * @method static bool allIntegerish(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish for all values. + * @method static bool allInterfaceExists(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists for all values. + * @method static bool allIp(string $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address for all values. + * @method static bool allIpv4(string $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address for all values. + * @method static bool allIpv6(string $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address for all values. + * @method static bool allIsArray(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array for all values. + * @method static bool allIsArrayAccessible(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object for all values. + * @method static bool allIsCallable(mixed $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable for all values. + * @method static bool allIsCountable(array|Countable|ResourceBundle|SimpleXMLElement $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable for all values. + * @method static bool allIsInstanceOf(mixed $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name for all values. + * @method static bool allIsJsonString(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string for all values. + * @method static bool allIsObject(mixed $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object for all values. + * @method static bool allIsResource(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource for all values. + * @method static bool allIsTraversable(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object for all values. + * @method static bool allKeyExists(mixed $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array for all values. + * @method static bool allKeyIsset(mixed $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() for all values. + * @method static bool allKeyNotExists(mixed $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array for all values. + * @method static bool allLength(mixed $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length for all values. + * @method static bool allLessOrEqualThan(mixed $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit for all values. + * @method static bool allLessThan(mixed $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit for all values. + * @method static bool allMax(mixed $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit for all values. + * @method static bool allMaxCount(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements for all values. + * @method static bool allMaxLength(mixed $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars for all values. + * @method static bool allMethodExists(string $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object for all values. + * @method static bool allMin(mixed $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit for all values. + * @method static bool allMinCount(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements for all values. + * @method static bool allMinLength(mixed $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long for all values. + * @method static bool allNoContent(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty for all values. + * @method static bool allNotBlank(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank for all values. + * @method static bool allNotContains(mixed $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars for all values. + * @method static bool allNotEmpty(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty for all values. + * @method static bool allNotEmptyKey(mixed $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty for all values. + * @method static bool allNotEq(mixed $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) for all values. + * @method static bool allNotInArray(mixed $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices for all values. + * @method static bool allNotIsInstanceOf(mixed $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name for all values. + * @method static bool allNotNull(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null for all values. + * @method static bool allNotRegex(mixed $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex for all values. + * @method static bool allNotSame(mixed $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) for all values. + * @method static bool allNull(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is null for all values. + * @method static bool allNumeric(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric for all values. + * @method static bool allObjectOrClass(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists for all values. + * @method static bool allPhpVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version for all values. + * @method static bool allPropertiesExist(mixed $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist for all values. + * @method static bool allPropertyExists(mixed $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists for all values. + * @method static bool allRange(mixed $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers for all values. + * @method static bool allReadable(string $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable for all values. + * @method static bool allRegex(mixed $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex for all values. + * @method static bool allSame(mixed $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) for all values. + * @method static bool allSatisfy(mixed $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback for all values. + * @method static bool allScalar(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar for all values. + * @method static bool allStartsWith(mixed $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars for all values. + * @method static bool allString(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string for all values. + * @method static bool allSubclassOf(mixed $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name for all values. + * @method static bool allTrue(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True for all values. + * @method static bool allUniqueValues(array $values, string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality) for all values. + * @method static bool allUrl(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL for all values. + * @method static bool allUuid(string $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID for all values. + * @method static bool allVersion(string $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions for all values. + * @method static bool allWriteable(string $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable for all values. + * @method static bool nullOrAlnum(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric or that the value is null. + * @method static bool nullOrBase64(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null. + * @method static bool nullOrBetween(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit or that the value is null. + * @method static bool nullOrBetweenExclusive(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit or that the value is null. + * @method static bool nullOrBetweenLength(mixed|null $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths or that the value is null. + * @method static bool nullOrBoolean(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean or that the value is null. + * @method static bool nullOrChoice(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices or that the value is null. + * @method static bool nullOrChoicesNotEmpty(array|null $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content or that the value is null. + * @method static bool nullOrClassExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists or that the value is null. + * @method static bool nullOrContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars or that the value is null. + * @method static bool nullOrCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count or that the value is null. + * @method static bool nullOrDate(string|null $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format or that the value is null. + * @method static bool nullOrDefined(mixed|null $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null. + * @method static bool nullOrDigit(mixed|null $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit or that the value is null. + * @method static bool nullOrDirectory(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists or that the value is null. + * @method static bool nullOrE164(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number or that the value is null. + * @method static bool nullOrEmail(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) or that the value is null. + * @method static bool nullOrEndsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars or that the value is null. + * @method static bool nullOrEq(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) or that the value is null. + * @method static bool nullOrEqArraySubset(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset or that the value is null. + * @method static bool nullOrExtensionLoaded(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded or that the value is null. + * @method static bool nullOrExtensionVersion(string|null $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed or that the value is null. + * @method static bool nullOrFalse(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False or that the value is null. + * @method static bool nullOrFile(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists or that the value is null. + * @method static bool nullOrFloat(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float or that the value is null. + * @method static bool nullOrGreaterOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit or that the value is null. + * @method static bool nullOrGreaterThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit or that the value is null. + * @method static bool nullOrImplementsInterface(mixed|null $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface or that the value is null. + * @method static bool nullOrInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() or that the value is null. + * @method static bool nullOrInteger(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer or that the value is null. + * @method static bool nullOrIntegerish(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish or that the value is null. + * @method static bool nullOrInterfaceExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists or that the value is null. + * @method static bool nullOrIp(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address or that the value is null. + * @method static bool nullOrIpv4(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address or that the value is null. + * @method static bool nullOrIpv6(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address or that the value is null. + * @method static bool nullOrIsArray(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or that the value is null. + * @method static bool nullOrIsArrayAccessible(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object or that the value is null. + * @method static bool nullOrIsCallable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable or that the value is null. + * @method static bool nullOrIsCountable(array|Countable|ResourceBundle|SimpleXMLElement|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable or that the value is null. + * @method static bool nullOrIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name or that the value is null. + * @method static bool nullOrIsJsonString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string or that the value is null. + * @method static bool nullOrIsObject(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object or that the value is null. + * @method static bool nullOrIsResource(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource or that the value is null. + * @method static bool nullOrIsTraversable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object or that the value is null. + * @method static bool nullOrKeyExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array or that the value is null. + * @method static bool nullOrKeyIsset(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() or that the value is null. + * @method static bool nullOrKeyNotExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array or that the value is null. + * @method static bool nullOrLength(mixed|null $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length or that the value is null. + * @method static bool nullOrLessOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit or that the value is null. + * @method static bool nullOrLessThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit or that the value is null. + * @method static bool nullOrMax(mixed|null $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit or that the value is null. + * @method static bool nullOrMaxCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements or that the value is null. + * @method static bool nullOrMaxLength(mixed|null $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars or that the value is null. + * @method static bool nullOrMethodExists(string|null $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object or that the value is null. + * @method static bool nullOrMin(mixed|null $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit or that the value is null. + * @method static bool nullOrMinCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements or that the value is null. + * @method static bool nullOrMinLength(mixed|null $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long or that the value is null. + * @method static bool nullOrNoContent(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty or that the value is null. + * @method static bool nullOrNotBlank(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank or that the value is null. + * @method static bool nullOrNotContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars or that the value is null. + * @method static bool nullOrNotEmpty(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty or that the value is null. + * @method static bool nullOrNotEmptyKey(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty or that the value is null. + * @method static bool nullOrNotEq(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) or that the value is null. + * @method static bool nullOrNotInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices or that the value is null. + * @method static bool nullOrNotIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name or that the value is null. + * @method static bool nullOrNotNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null or that the value is null. + * @method static bool nullOrNotRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex or that the value is null. + * @method static bool nullOrNotSame(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) or that the value is null. + * @method static bool nullOrNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is null or that the value is null. + * @method static bool nullOrNumeric(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric or that the value is null. + * @method static bool nullOrObjectOrClass(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists or that the value is null. + * @method static bool nullOrPhpVersion(string|null $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version or that the value is null. + * @method static bool nullOrPropertiesExist(mixed|null $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist or that the value is null. + * @method static bool nullOrPropertyExists(mixed|null $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists or that the value is null. + * @method static bool nullOrRange(mixed|null $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers or that the value is null. + * @method static bool nullOrReadable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable or that the value is null. + * @method static bool nullOrRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex or that the value is null. + * @method static bool nullOrSame(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) or that the value is null. + * @method static bool nullOrSatisfy(mixed|null $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback or that the value is null. + * @method static bool nullOrScalar(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar or that the value is null. + * @method static bool nullOrStartsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars or that the value is null. + * @method static bool nullOrString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string or that the value is null. + * @method static bool nullOrSubclassOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name or that the value is null. + * @method static bool nullOrTrue(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True or that the value is null. + * @method static bool nullOrUniqueValues(array|null $values, string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality) or that the value is null. + * @method static bool nullOrUrl(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL or that the value is null. + * @method static bool nullOrUuid(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID or that the value is null. + * @method static bool nullOrVersion(string|null $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions or that the value is null. + * @method static bool nullOrWriteable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable or that the value is null. + */ + --- + diff --git a/tests/Printer/examples/method4.test b/tests/Printer/examples/method4.test new file mode 100644 index 00000000..c6a80786 --- /dev/null +++ b/tests/Printer/examples/method4.test @@ -0,0 +1,7 @@ +/** + * @method bool is(string $value = null) + */ +--- +/** + * MethodNode(ScalarNode(bool),is,ParameterList(ParameterNode(VariableNode($value),ScalarNode(string),null))) + */ diff --git a/tests/Printer/examples/method5.test b/tests/Printer/examples/method5.test new file mode 100644 index 00000000..ee5656aa --- /dev/null +++ b/tests/Printer/examples/method5.test @@ -0,0 +1,7 @@ +/** + * @method static bool allAlnum(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values. + */ +--- +/** + * MethodNode(ScalarNode(bool),allAlnum,static,ParameterList(ParameterNode(VariableNode($value),ScalarNode(mixed)),ParameterNode(VariableNode($message),UnionNode(ScalarNode(string)|ScalarNode(callable)),null),ParameterNode(VariableNode($propertyPath),ScalarNode(string),null)),TextNode(Assert that value is alphanumeric for all values.)) + */ diff --git a/tests/Printer/examples/method6.test b/tests/Printer/examples/method6.test new file mode 100644 index 00000000..60452406 --- /dev/null +++ b/tests/Printer/examples/method6.test @@ -0,0 +1,7 @@ +/** + * @method static bool allIpv4(string $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address for all values. + */ +--- +/** + * MethodNode(ScalarNode(bool),allIpv4,static,ParameterList(ParameterNode(VariableNode($value),ScalarNode(string)),ParameterNode(VariableNode($flag),ScalarNode(int),null),ParameterNode(VariableNode($message),UnionNode(ScalarNode(string)|ScalarNode(callable)),null),ParameterNode(VariableNode($propertyPath),ScalarNode(string),null)),TextNode(Assert that value is an IPv4 address for all values.)) + */ From 28df9f567340c46c90892690e8a68f8589ff8abf Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 24 Jan 2021 18:55:39 +0000 Subject: [PATCH 40/63] Updated phpcs --- .php_cs.dist | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.php_cs.dist b/.php_cs.dist index a3d5fa36..2820b890 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -4,7 +4,7 @@ $finder = PhpCsFixer\Finder::create() ->in('lib') ->in('tests') ->exclude([ - 'tests/Workspace' + 'tests/Workspace', ]) ; @@ -13,6 +13,11 @@ return PhpCsFixer\Config::create() '@PSR2' => true, 'no_unused_imports' => true, 'array_syntax' => ['syntax' => 'short'], + 'void_return' => true, + 'ordered_class_elements' => true, + 'single_quote' => true, + 'heredoc_indentation' => true, + 'global_namespace_import' => true, ]) ->setFinder($finder) ; From eccad5be16f33dbda9452d6f222a681b98395fb3 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 24 Jan 2021 18:56:03 +0000 Subject: [PATCH 41/63] Apply CS fixes --- lib/Ast/MethodNode.php | 3 +- lib/Ast/TypeNode.php | 2 - lib/Ast/VariableNode.php | 1 - lib/Lexer.php | 1 - lib/Parser.php | 11 ++-- lib/Printer/TestPrinter.php | 5 +- lib/Tokens.php | 11 ++-- tests/Benchmark/AbstractParserBenchCase.php | 17 +++-- tests/Benchmark/PhpactorParserBench.php | 1 - tests/Benchmark/PhpstanParserBench.php | 1 - tests/Unit/LexerTest.php | 70 ++++++++++----------- tests/Unit/ParserTest.php | 3 - 12 files changed, 55 insertions(+), 71 deletions(-) diff --git a/lib/Ast/MethodNode.php b/lib/Ast/MethodNode.php index 4a35dbfe..16b7cee3 100644 --- a/lib/Ast/MethodNode.php +++ b/lib/Ast/MethodNode.php @@ -49,8 +49,7 @@ public function __construct( ?ParameterList $parameters, ?Token $parenClose, ?TextNode $text - ) - { + ) { $this->type = $type; $this->name = $name; $this->static = $static; diff --git a/lib/Ast/TypeNode.php b/lib/Ast/TypeNode.php index 2ca7a693..4cefe3ea 100644 --- a/lib/Ast/TypeNode.php +++ b/lib/Ast/TypeNode.php @@ -2,8 +2,6 @@ namespace Phpactor\Docblock\Ast; -use Phpactor\Docblock\Token; - abstract class TypeNode extends Node { } diff --git a/lib/Ast/VariableNode.php b/lib/Ast/VariableNode.php index 6bcca641..9e55e617 100644 --- a/lib/Ast/VariableNode.php +++ b/lib/Ast/VariableNode.php @@ -4,7 +4,6 @@ use Phpactor\Docblock\Token; - class VariableNode extends Node { /** diff --git a/lib/Lexer.php b/lib/Lexer.php index 308cfadc..13bfb232 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -120,5 +120,4 @@ private function resolveType(string $value, ?array $prevChunk = null): string return Token::T_UNKNOWN; } - } diff --git a/lib/Parser.php b/lib/Parser.php index d0b83e3f..4ed76538 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -2,7 +2,6 @@ namespace Phpactor\Docblock; -use PhpParser\Node\NullableType; use Phpactor\Docblock\Ast\DeprecatedNode; use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\MethodNode; @@ -32,15 +31,14 @@ final class Parser { + private const SCALAR_TYPES = [ + 'int', 'float', 'bool', 'string', 'mixed', 'callable' + ]; /** * @var Tokens */ private $tokens; - private const SCALAR_TYPES = [ - 'int', 'float', 'bool', 'string', 'mixed', 'callable' - ]; - public function parse(Tokens $tokens): Node { $this->tokens = $tokens; @@ -332,7 +330,7 @@ private function parseText(): ?TextNode { $text = []; if ( - $this->tokens->current->type === Token::T_WHITESPACE && + $this->tokens->current->type === Token::T_WHITESPACE && $this->tokens->next()->type === Token::T_LABEL ) { $this->tokens->chomp(); @@ -372,5 +370,4 @@ private function parseValue(): ?ValueNode return null; } - } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 7b95da6a..c3b6ad68 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -14,7 +14,6 @@ use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; -use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Ast\ParamNode; use Phpactor\Docblock\Ast\Type\GenericNode; @@ -43,7 +42,7 @@ public function print(Node $node): string $this->render($node); - return implode("", $this->out); + return implode('', $this->out); } private function render(?Element $node): void @@ -340,7 +339,7 @@ private function renderParameter(ParameterNode $node): void $this->out[] = ')'; } - private function renderValue(ValueNode $node) + private function renderValue(ValueNode $node): void { $this->out[] = json_encode($node->value()); } diff --git a/lib/Tokens.php b/lib/Tokens.php index 94814575..1876e366 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -8,15 +8,15 @@ final class Tokens implements IteratorAggregate { - /** - * @var Token[] - */ - private $tokens; /** * @var ?Token */ public $current; + /** + * @var Token[] + */ + private $tokens; /** * @var int @@ -75,7 +75,8 @@ public function chomp(?string $type = null): ?Token if (null !== $type && $token->type !== $type) { throw new RuntimeException(sprintf( 'Expected type "%s" at position "%s": "%s"', - $type, $this->position, + $type, + $this->position, implode('', array_map(function (Token $token) { return $token->value; }, $this->tokens)) diff --git a/tests/Benchmark/AbstractParserBenchCase.php b/tests/Benchmark/AbstractParserBenchCase.php index 9f201f21..9cdb1639 100644 --- a/tests/Benchmark/AbstractParserBenchCase.php +++ b/tests/Benchmark/AbstractParserBenchCase.php @@ -3,8 +3,6 @@ namespace Phpactor\Docblock\Tests\Benchmark; use Generator; -use Phpactor\Docblock\Lexer; -use Phpactor\Docblock\Parser; /** * @Iterations(33) @@ -14,15 +12,16 @@ */ abstract class AbstractParserBenchCase { + abstract public function setUp(): void; public function benchParse(): void { $doc = <<<'EOT' -/** - * @param Foobar $foobar - * @var Foobar $bafoo - * @param string $baz - */ -EOT; + /** + * @param Foobar $foobar + * @var Foobar $bafoo + * @param string $baz + */ + EOT; $this->parse($doc); } @@ -51,7 +50,5 @@ public function benchAssert(): void { $this->parse(file_get_contents(__DIR__ . '/examples/assert.example')); } - - abstract public function setUp(): void; abstract public function parse(string $doc): void; } diff --git a/tests/Benchmark/PhpactorParserBench.php b/tests/Benchmark/PhpactorParserBench.php index 94a7b4f8..f7d4cd5a 100644 --- a/tests/Benchmark/PhpactorParserBench.php +++ b/tests/Benchmark/PhpactorParserBench.php @@ -4,7 +4,6 @@ use Phpactor\Docblock\Lexer; use Phpactor\Docblock\Parser; -use Phpactor\Docblock\Tests\Benchmark\AbstractParserBenchCase; class PhpactorParserBench extends AbstractParserBenchCase { diff --git a/tests/Benchmark/PhpstanParserBench.php b/tests/Benchmark/PhpstanParserBench.php index dcf0884a..665f4718 100644 --- a/tests/Benchmark/PhpstanParserBench.php +++ b/tests/Benchmark/PhpstanParserBench.php @@ -7,7 +7,6 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; -use Phpactor\Docblock\Tests\Benchmark\AbstractParserBenchCase; class PhpstanParserBench extends AbstractParserBenchCase { diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index d5ff9478..150c7333 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -34,72 +34,72 @@ public function provideLex(): Generator yield [ '', [] ]; yield [ <<<'EOT' -/** - * Hello this is - * Multi - */ -EOT + /** + * Hello this is + * Multi + */ + EOT ,[ - [Token::T_PHPDOC_OPEN, "/**"], + [Token::T_PHPDOC_OPEN, '/**'], [Token::T_WHITESPACE, "\n "], - [Token::T_PHPDOC_LEADING, "*"], - [Token::T_WHITESPACE, " "], - [Token::T_LABEL, "Hello"], - [Token::T_WHITESPACE, " "], - [Token::T_LABEL, "this"], - [Token::T_WHITESPACE, " "], - [Token::T_LABEL, "is"], + [Token::T_PHPDOC_LEADING, '*'], + [Token::T_WHITESPACE, ' '], + [Token::T_LABEL, 'Hello'], + [Token::T_WHITESPACE, ' '], + [Token::T_LABEL, 'this'], + [Token::T_WHITESPACE, ' '], + [Token::T_LABEL, 'is'], [Token::T_WHITESPACE, "\n "], - [Token::T_PHPDOC_LEADING, "*"], - [Token::T_WHITESPACE, " "], - [Token::T_LABEL, "Multi"], + [Token::T_PHPDOC_LEADING, '*'], + [Token::T_WHITESPACE, ' '], + [Token::T_LABEL, 'Multi'], [Token::T_WHITESPACE, "\n "], - [Token::T_PHPDOC_CLOSE, "*/"], + [Token::T_PHPDOC_CLOSE, '*/'], ] ]; yield [ 'Foobar', [ - [Token::T_LABEL, "Foobar"], + [Token::T_LABEL, 'Foobar'], ] ]; yield [ 'Foobar[]', [ - [Token::T_LABEL, "Foobar"], - [Token::T_LIST, "[]"], + [Token::T_LABEL, 'Foobar'], + [Token::T_LIST, '[]'], ] ]; yield [ 'Foobar', [ - [Token::T_LABEL, "Foobar"], - [Token::T_BRACKET_ANGLE_OPEN, "<"], - [Token::T_LABEL, "Barfoo"], - [Token::T_BRACKET_ANGLE_CLOSE, ">"], + [Token::T_LABEL, 'Foobar'], + [Token::T_BRACKET_ANGLE_OPEN, '<'], + [Token::T_LABEL, 'Barfoo'], + [Token::T_BRACKET_ANGLE_CLOSE, '>'], ] ]; yield [ 'Foobar', [ - [Token::T_LABEL, "Foobar"], - [Token::T_BRACKET_ANGLE_OPEN, "<"], - [Token::T_LABEL, "Barfoo"], - [Token::T_BRACKET_ANGLE_CLOSE, ">"], + [Token::T_LABEL, 'Foobar'], + [Token::T_BRACKET_ANGLE_OPEN, '<'], + [Token::T_LABEL, 'Barfoo'], + [Token::T_BRACKET_ANGLE_CLOSE, '>'], ] ]; yield [ 'Foobar{Barfoo, Foobar}', [ - [Token::T_LABEL, "Foobar"], - [Token::T_BRACKET_CURLY_OPEN, "{"], - [Token::T_LABEL, "Barfoo"], - [Token::T_COMMA, ","], - [Token::T_WHITESPACE, " "], - [Token::T_LABEL, "Foobar"], - [Token::T_BRACKET_CURLY_CLOSE, "}"], + [Token::T_LABEL, 'Foobar'], + [Token::T_BRACKET_CURLY_OPEN, '{'], + [Token::T_LABEL, 'Barfoo'], + [Token::T_COMMA, ','], + [Token::T_WHITESPACE, ' '], + [Token::T_LABEL, 'Foobar'], + [Token::T_BRACKET_CURLY_CLOSE, '}'], ] ]; } diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index dd223d7b..26949747 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -5,10 +5,7 @@ use Generator; use PHPUnit\Framework\TestCase; use Phpactor\Docblock\Ast\Docblock; -use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Node; -use Phpactor\Docblock\Ast\ParamNode; -use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Lexer; use Phpactor\Docblock\Parser; use Phpactor\Docblock\Token; From c3ffc0576f178bf8653ad2ea76f08be73a373b94 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 24 Jan 2021 21:00:59 +0000 Subject: [PATCH 42/63] Add lexer bench --- tests/Benchmark/LexerBench.php | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/Benchmark/LexerBench.php diff --git a/tests/Benchmark/LexerBench.php b/tests/Benchmark/LexerBench.php new file mode 100644 index 00000000..a2726f75 --- /dev/null +++ b/tests/Benchmark/LexerBench.php @@ -0,0 +1,55 @@ +lex($docblock['docblock']); + } + + public function provideDocblock(): Generator + { + yield [ + 'docblock' => <<<'EOT' + /** + * This is some complicated method + * @since 5.2 + * + * @param Foobar $barfoo Does a barfoo and then returns + * @param Barfoo $foobar Performs a foobar and then runs away. + * + * @return Baz + */ + EOT + ]; + yield [ + 'docblock' => <<<'EOT' + /** + * Assert library. + * + * @author Benjamin Eberlei + * + * @method static bool allAlnum(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values. + * @method static bool allBase64(string $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. + * @method static bool allBetween(mixed $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit for all values. + * @method static bool allBetweenExclusive(mixed $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit for all values. + * @method static bool allBetweenLength(mixed $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths for all values. + * @method static bool allBoolean(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean for all values. + * @method static bool allChoice(mixed $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices for all values. + * @method static bool allChoicesNotEmpty(array $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content for all values. + * @method static bool allClassExists(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists for all values. + * @method static bool allContains(mixed $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars for all values. + * @method static bool allCount(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count for all values. + * @method static bool allDate(string $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format for all values. + EOT + ]; + } +} From 0bfc82138fae6745477d8d0b3fa2d02b0b461aec Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 25 Jan 2021 18:45:31 +0000 Subject: [PATCH 43/63] Adding traversal API --- lib/Ast/DeprecatedNode.php | 4 ++ lib/Ast/Docblock.php | 15 ++++--- lib/Ast/ElementList.php | 66 +++++++++++++++++++++++++++++++ lib/Ast/MethodNode.php | 25 ++++++++---- lib/Ast/Node.php | 54 +++++++++++++++++++++++++ lib/Parser.php | 4 ++ tests/Unit/Ast/MethodNodeTest.php | 21 ++++++++++ tests/Unit/Ast/NodeTestCase.php | 29 ++++++++++++++ 8 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 lib/Ast/ElementList.php create mode 100644 tests/Unit/Ast/MethodNodeTest.php create mode 100644 tests/Unit/Ast/NodeTestCase.php diff --git a/lib/Ast/DeprecatedNode.php b/lib/Ast/DeprecatedNode.php index ea2604fe..f9918262 100644 --- a/lib/Ast/DeprecatedNode.php +++ b/lib/Ast/DeprecatedNode.php @@ -4,6 +4,10 @@ class DeprecatedNode extends TagNode { + public const CHILD_NAMES = [ + 'text', + ]; + /** * @var TextNode */ diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index 1aec2f21..bf63d68c 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -4,23 +4,28 @@ class Docblock extends Node { + protected const CHILD_NAMES = [ + 'children' + ]; + /** - * @var Element[] + * @var ElementList */ - private $children = []; + public $children = []; /** * @param Element[] $children */ public function __construct(array $children) { - $this->children = $children; + $this->children = new ElementList($children); } /** - * @return Element[] + * @return ElementList */ - public function children(): array + public function children(): ElementList + { return $this->children; } diff --git a/lib/Ast/ElementList.php b/lib/Ast/ElementList.php new file mode 100644 index 00000000..7e67f267 --- /dev/null +++ b/lib/Ast/ElementList.php @@ -0,0 +1,66 @@ + + */ +class ElementList extends Node implements IteratorAggregate +{ + protected const CHILD_NAMES = [ + 'elements', + ]; + + /** + * @var T[] + */ + public $elements; + + /** + * @param T[] $elements + */ + public function __construct(array $elements) + { + $this->elements = $elements; + } + + /** + * @return ArrayIterator + */ + public function getIterator(): Iterator + { + return new ArrayIterator($this->elements); + } + + /** + * @template T + * @param class-string $classFqn + * @return ElementList + */ + public function byClass(string $classFqn): ElementList + { + return new self(array_filter($this->elements, function (Element $element) use ($classFqn): bool { + return get_class($element) === $classFqn; + })); + } + + public function byName(string $name): ElementList + { + return new self(array_filter($this->elements, function (Element $element) use ($classFqn): bool { + return get_class($element) === $classFqn; + })); + } + + /** + * @return Element[] + */ + public function toArray(): array + { + return $this->elements; + } +} diff --git a/lib/Ast/MethodNode.php b/lib/Ast/MethodNode.php index 16b7cee3..84cf60a9 100644 --- a/lib/Ast/MethodNode.php +++ b/lib/Ast/MethodNode.php @@ -6,40 +6,51 @@ class MethodNode extends TagNode { + public const CHILD_NAMES = [ + 'type', + 'static', + 'type', + 'name', + 'parenOpen', + 'parameters', + 'parenClose', + 'text' + ]; + /** * @var TypeNode|null */ - private $type; + public $type; /** * @var Token|null */ - private $name; + public $name; /** * @var Token|null */ - private $static; + public $static; /** * @var ParameterList|null */ - private $parameters; + public $parameters; /** * @var TextNode|null */ - private $text; + public $text; /** * @var Token|null */ - private $parenOpen; + public $parenOpen; /** * @var Token|null */ - private $parenClose; + public $parenClose; public function __construct( ?TypeNode $type, diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 740956d2..4d37cf32 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -2,10 +2,64 @@ namespace Phpactor\Docblock\Ast; +use Generator; +use Phpactor\Docblock\Token; + abstract class Node implements Element { + protected const CHILD_NAMES = [ + ]; + public function shortName(): string { return substr(get_class($this), strrpos(get_class($this), '\\') + 1); } + + /** + * @return Generator + */ + public function getDescendantNodes(): Generator + { + yield $this; + yield from $this->walkNodes($this->getChildNodes()); + } + + /** + * @param iterable> $nodes + * + * @return Generator + */ + private function walkNodes(iterable $nodes): Generator + { + $result = []; + foreach ($nodes as $child) { + if (is_array($child)) { + yield from $this->walkNodes($child); + continue; + } + + if ($child instanceof Node) { + yield from $child->getDescendantNodes(); + continue; + } + + if ($child instanceof Token) { + yield $child; + continue; + } + } + } + + /** + * @return Generator + */ + private function getChildNodes(): Generator + { + foreach (static::CHILD_NAMES as $name) { + $child = $this->$name; + if (null !== $child) { + yield $child; + } + } + } } diff --git a/lib/Parser.php b/lib/Parser.php index 4ed76538..eba6bb2d 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -328,6 +328,10 @@ private function parseReturn(): ReturnNode private function parseText(): ?TextNode { + if (!$this->tokens->current) { + return null; + } + $text = []; if ( $this->tokens->current->type === Token::T_WHITESPACE && diff --git a/tests/Unit/Ast/MethodNodeTest.php b/tests/Unit/Ast/MethodNodeTest.php new file mode 100644 index 00000000..98ece977 --- /dev/null +++ b/tests/Unit/Ast/MethodNodeTest.php @@ -0,0 +1,21 @@ + + */ + public function provideNode(): Generator + { + yield [ + '@method static Baz\Bar bar(string $boo, string $baz)', + function (MethodNode $methodNode) { + } + ]; + } +} diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php new file mode 100644 index 00000000..31ec905d --- /dev/null +++ b/tests/Unit/Ast/NodeTestCase.php @@ -0,0 +1,29 @@ +parse((new Lexer())->lex($doc)); + $nodes = iterator_to_array($node->getDescendantNodes(), false); + self::assertIsIterable($nodes); + } + + /** + * @return Generator + */ + abstract public function provideNode(): Generator; +} From 3f6fdee3492031ababe0a34b86152978fa89cdef Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 25 Jan 2021 19:53:13 +0000 Subject: [PATCH 44/63] Added test for method node --- lib/Ast/Element.php | 14 +++++++++ lib/Ast/MethodNode.php | 8 +++++ lib/Ast/Node.php | 49 +++++++++++++++++++++++++++++++ lib/Ast/ParameterList.php | 24 ++++++++++----- lib/Ast/ParameterNode.php | 13 ++++++-- lib/Ast/Type/ClassNode.php | 7 ++++- lib/Ast/Type/GenericNode.php | 15 +++++++--- lib/Ast/Type/ListNode.php | 9 ++++-- lib/Ast/Type/NullNode.php | 6 +++- lib/Ast/Type/NullableNode.php | 9 ++++-- lib/Ast/Type/ScalarNode.php | 6 +++- lib/Ast/Type/UnionNode.php | 6 +++- lib/Ast/TypeNode.php | 2 ++ lib/Ast/VariableNode.php | 6 +++- lib/Parser.php | 13 ++++++-- lib/Token.php | 18 +++++++++++- tests/Unit/Ast/MethodNodeTest.php | 10 +++++++ tests/Unit/Ast/NodeTestCase.php | 1 + 18 files changed, 189 insertions(+), 27 deletions(-) diff --git a/lib/Ast/Element.php b/lib/Ast/Element.php index 94ed2a77..38369be3 100644 --- a/lib/Ast/Element.php +++ b/lib/Ast/Element.php @@ -4,4 +4,18 @@ interface Element { + /** + * Return the string aggregation of all tokens in this element + */ + public function toString(): string; + + /** + * Return the start byte offset, starting at 0 + */ + public function start(): int; + + /** + * Return the end byte offset, starting at 0 + */ + public function end(): int; } diff --git a/lib/Ast/MethodNode.php b/lib/Ast/MethodNode.php index 84cf60a9..fde3a1d5 100644 --- a/lib/Ast/MethodNode.php +++ b/lib/Ast/MethodNode.php @@ -7,6 +7,7 @@ class MethodNode extends TagNode { public const CHILD_NAMES = [ + 'tag', 'type', 'static', 'type', @@ -52,7 +53,13 @@ class MethodNode extends TagNode */ public $parenClose; + /** + * @var Token|null + */ + public $tag; + public function __construct( + ?Token $tag, ?TypeNode $type, ?Token $name, ?Token $static, @@ -68,6 +75,7 @@ public function __construct( $this->text = $text; $this->parenOpen = $parenOpen; $this->parenClose = $parenClose; + $this->tag = $tag; } public function name(): ?Token diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 4d37cf32..4ea360f1 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -10,6 +10,32 @@ abstract class Node implements Element protected const CHILD_NAMES = [ ]; + public function toString(): string + { + $parts = []; + foreach (static::CHILD_NAMES as $childName) { + $child = $this->$childName; + + if (is_array($child)) { + $parts = array_merge(array_map(function (Element $element) { + return $element->toString(); + }, $child)); + continue; + } + + if ($child instanceof Token) { + $parts[] = $child->value; + continue; + } + if ($child instanceof Node) { + $parts[] = $child->toString(); + continue; + } + } + + return implode(' ', $parts); + } + public function shortName(): string { return substr(get_class($this), strrpos(get_class($this), '\\') + 1); @@ -62,4 +88,27 @@ private function getChildNodes(): Generator } } } + + public function start(): int + { + $first = $this->getChildNodes()->current(); + if (null === $first) { + return 0; + } + + return $first->start(); + } + + public function end(): int + { + foreach (array_reverse(static::CHILD_NAMES) as $childName) { + $element = $this->$childName; + if (null === $element) { + continue; + } + + return $element->end(); + } + return 0; + } } diff --git a/lib/Ast/ParameterList.php b/lib/Ast/ParameterList.php index 1c89f370..f684cdd9 100644 --- a/lib/Ast/ParameterList.php +++ b/lib/Ast/ParameterList.php @@ -9,19 +9,22 @@ /** * @implements IteratorAggregate */ -class ParameterList implements IteratorAggregate, Countable +class ParameterList extends Node implements IteratorAggregate, Countable { + protected const CHILD_NAMES = [ + 'list' + ]; /** * @var ParameterNode[] */ - private $parameterList; + public $list; /** - * @param ParameterNode[] $parameterList + * @param ParameterNode[] $list */ - public function __construct(array $parameterList) + public function __construct(array $list) { - $this->parameterList = $parameterList; + $this->list = $list; } /** @@ -29,7 +32,7 @@ public function __construct(array $parameterList) */ public function getIterator(): ArrayIterator { - return new ArrayIterator($this->parameterList); + return new ArrayIterator($this->list); } /** @@ -37,6 +40,13 @@ public function getIterator(): ArrayIterator */ public function count() { - return count($this->parameterList); + return count($this->list); + } + + public function toString(): string + { + return implode(', ', array_map(function (Element $element) { + return $element->toString(); + }, $this->list)); } } diff --git a/lib/Ast/ParameterNode.php b/lib/Ast/ParameterNode.php index ee2c49da..8b4ed04b 100644 --- a/lib/Ast/ParameterNode.php +++ b/lib/Ast/ParameterNode.php @@ -4,19 +4,26 @@ class ParameterNode extends Node { + protected const CHILD_NAMES = [ + 'type', + 'name', + 'default', + ]; + /** * @var TypeNode|null */ - private $type; + public $type; + /** * @var VariableNode|null */ - private $name; + public $name; /** * @var ValueNode|null */ - private $default; + public $default; public function __construct(?TypeNode $type, ?VariableNode $name, ?ValueNode $default) { diff --git a/lib/Ast/Type/ClassNode.php b/lib/Ast/Type/ClassNode.php index da21bdba..6d9b1382 100644 --- a/lib/Ast/Type/ClassNode.php +++ b/lib/Ast/Type/ClassNode.php @@ -7,10 +7,15 @@ class ClassNode extends TypeNode { + protected const CHILD_NAMES = [ + 'name' + ]; + + /** * @var Token */ - private $name; + public $name; public function __construct(Token $name) { diff --git a/lib/Ast/Type/GenericNode.php b/lib/Ast/Type/GenericNode.php index 66fd2bf2..b84e3158 100644 --- a/lib/Ast/Type/GenericNode.php +++ b/lib/Ast/Type/GenericNode.php @@ -8,25 +8,32 @@ class GenericNode extends TypeNode { + protected const CHILD_NAMES = [ + 'open', + 'type', + 'open', + 'parameters', + 'close' + ]; /** * @var Token */ - private $open; + public $open; /** * @var Token */ - private $close; + public $close; /** * @var TypeList */ - private $parameters; + public $parameters; /** * @var TypeNode */ - private $type; + public $type; public function __construct(Token $open, TypeNode $type, TypeList $parameters, Token $close) { diff --git a/lib/Ast/Type/ListNode.php b/lib/Ast/Type/ListNode.php index aef076f2..f7a35f92 100644 --- a/lib/Ast/Type/ListNode.php +++ b/lib/Ast/Type/ListNode.php @@ -7,15 +7,20 @@ class ListNode extends TypeNode { + protected const CHILD_NAMES = [ + 'type', + 'listChars', + ]; + /** * @var TypeNode */ - private $type; + public $type; /** * @var Token */ - private $listChars; + public $listChars; public function __construct(TypeNode $type, Token $listChars) { diff --git a/lib/Ast/Type/NullNode.php b/lib/Ast/Type/NullNode.php index 0802c711..370cca45 100644 --- a/lib/Ast/Type/NullNode.php +++ b/lib/Ast/Type/NullNode.php @@ -7,10 +7,14 @@ class NullNode extends TypeNode { + protected const CHILD_NAMES = [ + 'null', + ]; + /** * @var Token */ - private $null; + public $null; public function __construct(Token $null) { diff --git a/lib/Ast/Type/NullableNode.php b/lib/Ast/Type/NullableNode.php index ec6ce250..795f867f 100644 --- a/lib/Ast/Type/NullableNode.php +++ b/lib/Ast/Type/NullableNode.php @@ -7,15 +7,20 @@ class NullableNode extends TypeNode { + protected const CHILD_NAMES = [ + 'nullable', + 'type', + ]; + /** * @var Token */ - private $nullable; + public $nullable; /** * @var TypeNode */ - private $type; + public $type; public function __construct(Token $nullable, TypeNode $type) { diff --git a/lib/Ast/Type/ScalarNode.php b/lib/Ast/Type/ScalarNode.php index f7e41397..d83fc6db 100644 --- a/lib/Ast/Type/ScalarNode.php +++ b/lib/Ast/Type/ScalarNode.php @@ -7,10 +7,14 @@ class ScalarNode extends TypeNode { + protected const CHILD_NAMES = [ + 'name', + ]; + /** * @var Token */ - private $name; + public $name; public function __construct(Token $name) { diff --git a/lib/Ast/Type/UnionNode.php b/lib/Ast/Type/UnionNode.php index 4d9946bf..5cba708b 100644 --- a/lib/Ast/Type/UnionNode.php +++ b/lib/Ast/Type/UnionNode.php @@ -7,10 +7,14 @@ class UnionNode extends TypeNode { + protected const CHILD_NAMES = [ + 'types', + ]; + /** * @var TypeList */ - private $types; + public $types; public function __construct(TypeList $types) { diff --git a/lib/Ast/TypeNode.php b/lib/Ast/TypeNode.php index 4cefe3ea..2ca7a693 100644 --- a/lib/Ast/TypeNode.php +++ b/lib/Ast/TypeNode.php @@ -2,6 +2,8 @@ namespace Phpactor\Docblock\Ast; +use Phpactor\Docblock\Token; + abstract class TypeNode extends Node { } diff --git a/lib/Ast/VariableNode.php b/lib/Ast/VariableNode.php index 9e55e617..5049ac5f 100644 --- a/lib/Ast/VariableNode.php +++ b/lib/Ast/VariableNode.php @@ -6,10 +6,14 @@ class VariableNode extends Node { + protected const CHILD_NAMES = [ + 'name' + ]; + /** * @var Token */ - private $name; + public $name; public function __construct(Token $name) { diff --git a/lib/Parser.php b/lib/Parser.php index eba6bb2d..f0a7482f 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -41,8 +41,8 @@ final class Parser public function parse(Tokens $tokens): Node { - $this->tokens = $tokens; $children = []; + $this->tokens = $tokens; while ($tokens->hasCurrent()) { if ($tokens->current->type === Token::T_TAG) { @@ -52,6 +52,13 @@ public function parse(Tokens $tokens): Node $children[] = $tokens->chomp(); } + if (count($children) === 1) { + $node = reset($children); + if ($node instanceof Node) { + return $node; + } + } + return new Docblock($children); } @@ -116,7 +123,7 @@ private function parseVar(): VarNode private function parseMethod(): MethodNode { - $this->tokens->chomp(Token::T_TAG); + $tag = $this->tokens->chomp(Token::T_TAG); $type = $name = $parameterList = $open = $close = null; $static = null; @@ -140,7 +147,7 @@ private function parseMethod(): MethodNode $close = $this->tokens->chompIf(Token::T_PAREN_CLOSE); } - return new MethodNode($type, $name, $static, $open, $parameterList, $close, $this->parseText()); + return new MethodNode($tag, $type, $name, $static, $open, $parameterList, $close, $this->parseText()); } private function parseProperty(): PropertyNode diff --git a/lib/Token.php b/lib/Token.php index 9c288387..23851fde 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -50,8 +50,24 @@ public function __construct(int $byteOffset, string $type, string $value) $this->value = $value; } - public function __toString(): string + public function toString(): string { return $this->value; } + + /** + * {@inheritDoc} + */ + public function start(): int + { + return $this->byteOffset; + } + + /** + * {@inheritDoc} + */ + public function end(): int + { + return $this->byteOffset + strlen($this->value); + } } diff --git a/tests/Unit/Ast/MethodNodeTest.php b/tests/Unit/Ast/MethodNodeTest.php index 98ece977..123bfc0d 100644 --- a/tests/Unit/Ast/MethodNodeTest.php +++ b/tests/Unit/Ast/MethodNodeTest.php @@ -4,6 +4,7 @@ use Generator; use PHPUnit\Framework\TestCase; +use Phpactor\Docblock\Ast\MethodNode; class MethodNodeTest extends NodeTestCase { @@ -15,6 +16,15 @@ public function provideNode(): Generator yield [ '@method static Baz\Bar bar(string $boo, string $baz)', function (MethodNode $methodNode) { + self::assertEquals('static', $methodNode->static->value); + self::assertEquals('Baz\Bar', $methodNode->type->toString()); + self::assertEquals('bar', $methodNode->name->toString()); + self::assertEquals('(', $methodNode->parenOpen->toString()); + self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); + self::assertEquals(')', $methodNode->parenClose->toString()); + self::assertEquals(0, $methodNode->start()); + self::assertEquals(52, $methodNode->end()); + self::assertEquals('@method Baz\Bar static Baz\Bar bar ( string $boo, string $baz )', $methodNode->toString()); } ]; } diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php index 31ec905d..2ca502ea 100644 --- a/tests/Unit/Ast/NodeTestCase.php +++ b/tests/Unit/Ast/NodeTestCase.php @@ -20,6 +20,7 @@ public function testNode(string $doc, ?Closure $assertion = null): void $node = (new Parser())->parse((new Lexer())->lex($doc)); $nodes = iterator_to_array($node->getDescendantNodes(), false); self::assertIsIterable($nodes); + $assertion($node); } /** From 9d1e7117b060ec706a76a2d5a36abe4744ced08d Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 25 Jan 2021 20:45:35 +0000 Subject: [PATCH 45/63] Aiming towards 100% isomorphism --- lib/Ast/MethodNode.php | 1 - lib/Ast/Node.php | 47 +++++++++++++++++++------------ lib/Ast/ParameterList.php | 8 +----- lib/Parser.php | 13 ++++++++- lib/Token.php | 1 + lib/Tokens.php | 12 ++++++-- tests/Unit/Ast/MethodNodeTest.php | 4 +-- tests/Unit/Ast/NodeTestCase.php | 36 ++++++++++++++++++++++- 8 files changed, 88 insertions(+), 34 deletions(-) diff --git a/lib/Ast/MethodNode.php b/lib/Ast/MethodNode.php index fde3a1d5..0569adc4 100644 --- a/lib/Ast/MethodNode.php +++ b/lib/Ast/MethodNode.php @@ -8,7 +8,6 @@ class MethodNode extends TagNode { public const CHILD_NAMES = [ 'tag', - 'type', 'static', 'type', 'name', diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 4ea360f1..fc65d554 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -12,28 +12,39 @@ abstract class Node implements Element public function toString(): string { - $parts = []; - foreach (static::CHILD_NAMES as $childName) { - $child = $this->$childName; + return implode(' ', array_map(function (Token $token) { + return $token->value; + }, iterator_to_array($this->tokens(), false))); + } - if (is_array($child)) { - $parts = array_merge(array_map(function (Element $element) { - return $element->toString(); - }, $child)); + /** + * @return Generator + */ + public function tokens(): Generator + { + yield from $this->findTokens($this->getChildElements()); + } + + /** + * @return Generator + * @param iterable> $nodes + */ + private function findTokens(iterable $nodes): Generator + { + foreach ($nodes as $node) { + if ($node instanceof Token) { + yield $node; continue; } - if ($child instanceof Token) { - $parts[] = $child->value; - continue; + if ($node instanceof Node) { + yield from $node->tokens(); } - if ($child instanceof Node) { - $parts[] = $child->toString(); - continue; + + if (is_array($node)) { + yield from $this->findTokens($node); } } - - return implode(' ', $parts); } public function shortName(): string @@ -47,7 +58,7 @@ public function shortName(): string public function getDescendantNodes(): Generator { yield $this; - yield from $this->walkNodes($this->getChildNodes()); + yield from $this->walkNodes($this->getChildElements()); } /** @@ -79,7 +90,7 @@ private function walkNodes(iterable $nodes): Generator /** * @return Generator */ - private function getChildNodes(): Generator + public function getChildElements(): Generator { foreach (static::CHILD_NAMES as $name) { $child = $this->$name; @@ -91,7 +102,7 @@ private function getChildNodes(): Generator public function start(): int { - $first = $this->getChildNodes()->current(); + $first = $this->getChildElements()->current(); if (null === $first) { return 0; } diff --git a/lib/Ast/ParameterList.php b/lib/Ast/ParameterList.php index f684cdd9..f01f2f6e 100644 --- a/lib/Ast/ParameterList.php +++ b/lib/Ast/ParameterList.php @@ -14,6 +14,7 @@ class ParameterList extends Node implements IteratorAggregate, Countable protected const CHILD_NAMES = [ 'list' ]; + /** * @var ParameterNode[] */ @@ -42,11 +43,4 @@ public function count() { return count($this->list); } - - public function toString(): string - { - return implode(', ', array_map(function (Element $element) { - return $element->toString(); - }, $this->list)); - } } diff --git a/lib/Parser.php b/lib/Parser.php index f0a7482f..9585e184 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -100,6 +100,7 @@ private function parseParam(): ParamNode if ($this->ifType()) { $type = $this->parseTypes(); } + if ($this->tokens->ifNextIs(Token::T_VARIABLE)) { $variable = $this->parseVariable(); } @@ -189,14 +190,24 @@ private function parseTypes(): ?TypeNode return new UnionNode(new TypeList($types)); } + private function parseType(): ?TypeNode { + if (null === $this->tokens->current) { + return null; + } + if ($this->tokens->current->type === Token::T_NULLABLE) { $nullable = $this->tokens->chomp(); return new NullableNode($nullable, $this->parseTypes()); } $type = $this->tokens->chomp(Token::T_LABEL); + + if (null === $this->tokens->current) { + return null; + } + $isList = false; if ($this->tokens->current->type === Token::T_LIST) { @@ -275,7 +286,7 @@ private function parseParameterList(): ?ParameterList while (true) { $parameters[] = $this->parseParameter(); if ($this->tokens->if(Token::T_COMMA)) { - $this->tokens->chomp(); + $parameters[] = $this->tokens->chomp(); continue; } break; diff --git a/lib/Token.php b/lib/Token.php index 23851fde..368deb02 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -27,6 +27,7 @@ final class Token implements Element public const T_BRACKET_CURLY_CLOSE = 'BRACKET_CURLY_CLOSE'; public const T_PAREN_OPEN = 'PAREN_OPEN'; public const T_PAREN_CLOSE = 'PAREN_CLOSE'; + public const T_INVALID = 'INVALID'; /** * @var int diff --git a/lib/Tokens.php b/lib/Tokens.php index 1876e366..7b3b8427 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -8,11 +8,11 @@ final class Tokens implements IteratorAggregate { - /** * @var ?Token */ public $current; + /** * @var Token[] */ @@ -100,7 +100,8 @@ public function chompIf(string $type): ?Token public function ifNextIs(string $type): bool { - if ($this->next()->type === $type) { + $next = $this->next(); + if ($next && $next->type === $type) { $this->current = @$this->tokens[++$this->position]; return true; } @@ -114,6 +115,10 @@ public function ifNextIs(string $type): bool */ public function if(string $type): bool { + if (null === $this->current) { + return false; + } + if ($this->current->type === $type) { return true; } @@ -122,7 +127,8 @@ public function if(string $type): bool return false; } - if ($this->next()->type === $type) { + $next = $this->next(); + if ($next && $this->next()->type === $type) { $this->current = $this->tokens[++$this->position]; return true; } diff --git a/tests/Unit/Ast/MethodNodeTest.php b/tests/Unit/Ast/MethodNodeTest.php index 123bfc0d..7b59651a 100644 --- a/tests/Unit/Ast/MethodNodeTest.php +++ b/tests/Unit/Ast/MethodNodeTest.php @@ -22,9 +22,7 @@ function (MethodNode $methodNode) { self::assertEquals('(', $methodNode->parenOpen->toString()); self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); self::assertEquals(')', $methodNode->parenClose->toString()); - self::assertEquals(0, $methodNode->start()); - self::assertEquals(52, $methodNode->end()); - self::assertEquals('@method Baz\Bar static Baz\Bar bar ( string $boo, string $baz )', $methodNode->toString()); + self::assertEquals('@method static Baz\Bar bar ( string $boo, string $baz )', $methodNode->toString()); } ]; } diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php index 2ca502ea..001ed0b6 100644 --- a/tests/Unit/Ast/NodeTestCase.php +++ b/tests/Unit/Ast/NodeTestCase.php @@ -5,6 +5,7 @@ use Closure; use Generator; use PHPUnit\Framework\TestCase; +use Phpactor\Docblock\Ast\Element; use Phpactor\Docblock\Ast\ElementList; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Lexer; @@ -17,14 +18,47 @@ abstract class NodeTestCase extends TestCase */ public function testNode(string $doc, ?Closure $assertion = null): void { - $node = (new Parser())->parse((new Lexer())->lex($doc)); + $node = $this->parse($doc); $nodes = iterator_to_array($node->getDescendantNodes(), false); self::assertIsIterable($nodes); + self::assertEquals(0, $node->start()); + self::assertEquals(strlen($doc), $node->end()); + $assertion($node); } + /** + * @dataProvider provideNode + */ + public function testPartialParse(string $doc): void + { + $node = $this->parse($doc); + $partial = []; + foreach ($node->getChildElements() as $child) { + $partial[] = $child->toString(); + $node = $this->parse(implode(' ', $partial)); + self::assertInstanceOf(Element::class, $node); + } + } + + /** + * @dataProvider provideNode + */ + public function testIsomorphism(string $doc): void + { + $one = $this->parse($doc); + $two = $this->parse($one->toString()); + self::assertEquals($one, $two); + } + /** * @return Generator */ abstract public function provideNode(): Generator; + + private function parse(string $doc): Node + { + $node = (new Parser())->parse((new Lexer())->lex($doc)); + return $node; + } } From 104e91f95c0437b42ec87a3f30ca8a03ddb6dff3 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 25 Jan 2021 21:16:17 +0000 Subject: [PATCH 46/63] Method node is isomorphic --- lib/Ast/Node.php | 52 +++++++++++++++++++++++++------ lib/Printer/TestPrinter.php | 3 -- lib/Token.php | 5 +++ lib/Tokens.php | 4 +++ tests/Unit/Ast/MethodNodeTest.php | 4 +-- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index fc65d554..d488ff0c 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -12,9 +12,13 @@ abstract class Node implements Element public function toString(): string { - return implode(' ', array_map(function (Token $token) { - return $token->value; - }, iterator_to_array($this->tokens(), false))); + $out = str_repeat(' ', $this->length());; + $start = $this->start(); + foreach ($this->tokens() as $token) { + $out = substr_replace($out, $token->value, $token->start() - $start, $token->length()); + } + + return $out; } /** @@ -102,24 +106,54 @@ public function getChildElements(): Generator public function start(): int { - $first = $this->getChildElements()->current(); - if (null === $first) { - return 0; + return $this->startOf($this->getChildElements()); + } + + /** + * @param iterable> $elements + */ + public function startOf(iterable $elements): int + { + foreach ($elements as $element) { + if ($element instanceof Element) { + return $element->start(); + } + if (is_array($element)) { + return $this->startOf($element); + } } - return $first->start(); + return 0; } public function end(): int { - foreach (array_reverse(static::CHILD_NAMES) as $childName) { - $element = $this->$childName; + return $this->endOf(array_reverse(iterator_to_array($this->getChildElements(), false))); + } + + /** + * @param iterable> $elements + */ + private function endOf(iterable $elements): int + { + foreach ($elements as $element) { + if (null === $element) { continue; } + if (is_array($element)) { + return $this->endOf(array_reverse($element)); + } + return $element->end(); } + return 0; } + + private function length(): int + { + return $this->end() - $this->start(); + } } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index c3b6ad68..f68553db 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -315,9 +315,6 @@ private function renderParameterList(ParameterList $list): void $this->out[] = 'ParameterList('; foreach ($list as $i => $parameter) { $this->render($parameter); - if ($i + 1 !== $list->count()) { - $this->out[] = ','; - } } $this->out[] = ')'; } diff --git a/lib/Token.php b/lib/Token.php index 368deb02..d0ecb32b 100644 --- a/lib/Token.php +++ b/lib/Token.php @@ -71,4 +71,9 @@ public function end(): int { return $this->byteOffset + strlen($this->value); } + + public function length(): int + { + return $this->end() - $this->start(); + } } diff --git a/lib/Tokens.php b/lib/Tokens.php index 7b3b8427..b6159194 100644 --- a/lib/Tokens.php +++ b/lib/Tokens.php @@ -91,6 +91,10 @@ public function chomp(?string $type = null): ?Token */ public function chompIf(string $type): ?Token { + if ($this->current === null) { + return null; + } + if ($this->current->type === $type) { return $this->chomp($type); } diff --git a/tests/Unit/Ast/MethodNodeTest.php b/tests/Unit/Ast/MethodNodeTest.php index 7b59651a..68d9371f 100644 --- a/tests/Unit/Ast/MethodNodeTest.php +++ b/tests/Unit/Ast/MethodNodeTest.php @@ -16,13 +16,13 @@ public function provideNode(): Generator yield [ '@method static Baz\Bar bar(string $boo, string $baz)', function (MethodNode $methodNode) { + self::assertEquals('@method static Baz\Bar bar(string $boo, string $baz)', $methodNode->toString()); + self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); self::assertEquals('static', $methodNode->static->value); self::assertEquals('Baz\Bar', $methodNode->type->toString()); self::assertEquals('bar', $methodNode->name->toString()); self::assertEquals('(', $methodNode->parenOpen->toString()); - self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); self::assertEquals(')', $methodNode->parenClose->toString()); - self::assertEquals('@method static Baz\Bar bar ( string $boo, string $baz )', $methodNode->toString()); } ]; } From 0baa83b4c0e3c9c36a9d455da3ea55e9f521ac04 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 25 Jan 2021 21:25:35 +0000 Subject: [PATCH 47/63] Param node --- lib/Ast/ParamNode.php | 23 +++++++++++++++++++---- lib/Parser.php | 4 ++-- tests/Unit/Ast/NodeTest.php | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 tests/Unit/Ast/NodeTest.php diff --git a/lib/Ast/ParamNode.php b/lib/Ast/ParamNode.php index 0ed5dade..62261274 100644 --- a/lib/Ast/ParamNode.php +++ b/lib/Ast/ParamNode.php @@ -2,28 +2,43 @@ namespace Phpactor\Docblock\Ast; +use Phpactor\Docblock\Token; + class ParamNode extends TagNode { + protected const CHILD_NAMES = [ + 'tag', + 'type', + 'variable', + 'text', + ]; + /** * @var ?TypeNode */ - private $type; + public $type; /** * @var ?VariableNode */ - private $variable; + public $variable; /** * @var TextNode|null */ - private $text; + public $text; + + /** + * @var Token + */ + public $tag; - public function __construct(?TypeNode $type, ?VariableNode $variable, ?TextNode $text = null) + public function __construct(Token $tag, ?TypeNode $type, ?VariableNode $variable, ?TextNode $text = null) { $this->type = $type; $this->variable = $variable; $this->text = $text; + $this->tag = $tag; } public function type(): ?TypeNode diff --git a/lib/Parser.php b/lib/Parser.php index 9585e184..638fe767 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -95,7 +95,7 @@ private function parseTag(): TagNode private function parseParam(): ParamNode { $type = $variable = $textNode = null; - $this->tokens->chomp(Token::T_TAG); + $tag = $this->tokens->chomp(Token::T_TAG); if ($this->ifType()) { $type = $this->parseTypes(); @@ -105,7 +105,7 @@ private function parseParam(): ParamNode $variable = $this->parseVariable(); } - return new ParamNode($type, $variable, $this->parseText()); + return new ParamNode($tag, $type, $variable, $this->parseText()); } private function parseVar(): VarNode diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php new file mode 100644 index 00000000..89d6d2cd --- /dev/null +++ b/tests/Unit/Ast/NodeTest.php @@ -0,0 +1,24 @@ + + */ + public function provideNode(): Generator + { + yield [ + '@param Baz\Bar $foobar', + function (ParamNode $methodNode) { + } + ]; + } +} From 5cd1571e2bc0aa5b080c65b197fef3f4d0644100 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Mon, 25 Jan 2021 21:42:41 +0000 Subject: [PATCH 48/63] Return node --- lib/Ast/Node.php | 2 +- lib/Ast/ReturnNode.php | 20 ++++++++++++-- lib/Ast/Type/ClassNode.php | 1 - lib/Ast/VarNode.php | 19 +++++++++++-- lib/Parser.php | 10 +++---- tests/Unit/Ast/MethodNodeTest.php | 29 ------------------- tests/Unit/Ast/NodeTest.php | 46 +++++++++++++++++++++++++++++-- tests/Unit/Ast/NodeTestCase.php | 17 ++++-------- 8 files changed, 89 insertions(+), 55 deletions(-) delete mode 100644 tests/Unit/Ast/MethodNodeTest.php diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index d488ff0c..21e30298 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -132,7 +132,7 @@ public function end(): int } /** - * @param iterable> $elements + * @param iterable> $elements */ private function endOf(iterable $elements): int { diff --git a/lib/Ast/ReturnNode.php b/lib/Ast/ReturnNode.php index f6dea30a..c90de1d2 100644 --- a/lib/Ast/ReturnNode.php +++ b/lib/Ast/ReturnNode.php @@ -2,22 +2,36 @@ namespace Phpactor\Docblock\Ast; +use Phpactor\Docblock\Token; + class ReturnNode extends TagNode { + protected const CHILD_NAMES = [ + 'tag', + 'type', + 'text', + ]; + /** * @var TypeNode|null */ - private $type; + public $type; /** * @var TextNode|null */ - private $text; + public $text; + + /** + * @var Token + */ + public $tag; - public function __construct(?TypeNode $type, ?TextNode $text = null) + public function __construct(Token $tag, ?TypeNode $type, ?TextNode $text = null) { $this->type = $type; $this->text = $text; + $this->tag = $tag; } public function type(): ?TypeNode diff --git a/lib/Ast/Type/ClassNode.php b/lib/Ast/Type/ClassNode.php index 6d9b1382..c3cd5072 100644 --- a/lib/Ast/Type/ClassNode.php +++ b/lib/Ast/Type/ClassNode.php @@ -11,7 +11,6 @@ class ClassNode extends TypeNode 'name' ]; - /** * @var Token */ diff --git a/lib/Ast/VarNode.php b/lib/Ast/VarNode.php index aa25ee69..56d0c5b8 100644 --- a/lib/Ast/VarNode.php +++ b/lib/Ast/VarNode.php @@ -2,22 +2,35 @@ namespace Phpactor\Docblock\Ast; +use Phpactor\Docblock\Token; + class VarNode extends TagNode { + protected const CHILD_NAMES = [ + 'tag', + 'type', + 'variable', + ]; /** * @var ?TypeNode */ - private $type; + public $type; /** * @var ?VariableNode */ - private $variable; + public $variable; + + /** + * @var Token + */ + public $tag; - public function __construct(?TypeNode $type, ?VariableNode $variable) + public function __construct(Token $tag, ?TypeNode $type, ?VariableNode $variable) { $this->type = $type; $this->variable = $variable; + $this->tag = $tag; } public function type(): ?TypeNode diff --git a/lib/Parser.php b/lib/Parser.php index 638fe767..d8d9c4c8 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -110,7 +110,7 @@ private function parseParam(): ParamNode private function parseVar(): VarNode { - $this->tokens->chomp(Token::T_TAG); + $tag = $this->tokens->chomp(Token::T_TAG); $type = $variable = null; if ($this->ifType()) { $type = $this->parseTypes(); @@ -119,7 +119,7 @@ private function parseVar(): VarNode $variable = $this->parseVariable(); } - return new VarNode($type, $variable); + return new VarNode($tag, $type, $variable); } private function parseMethod(): MethodNode @@ -205,7 +205,7 @@ private function parseType(): ?TypeNode $type = $this->tokens->chomp(Token::T_LABEL); if (null === $this->tokens->current) { - return null; + return $this->createTypeFromToken($type); } $isList = false; @@ -334,14 +334,14 @@ private function parseMixin(): MixinNode private function parseReturn(): ReturnNode { - $this->tokens->chomp(); + $tag = $this->tokens->chomp(Token::T_TAG); $type = null; if ($this->tokens->if(Token::T_LABEL)) { $type = $this->parseTypes(); } - return new ReturnNode($type, $this->parseText()); + return new ReturnNode($tag, $type, $this->parseText()); } private function parseText(): ?TextNode diff --git a/tests/Unit/Ast/MethodNodeTest.php b/tests/Unit/Ast/MethodNodeTest.php deleted file mode 100644 index 68d9371f..00000000 --- a/tests/Unit/Ast/MethodNodeTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ - public function provideNode(): Generator - { - yield [ - '@method static Baz\Bar bar(string $boo, string $baz)', - function (MethodNode $methodNode) { - self::assertEquals('@method static Baz\Bar bar(string $boo, string $baz)', $methodNode->toString()); - self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); - self::assertEquals('static', $methodNode->static->value); - self::assertEquals('Baz\Bar', $methodNode->type->toString()); - self::assertEquals('bar', $methodNode->name->toString()); - self::assertEquals('(', $methodNode->parenOpen->toString()); - self::assertEquals(')', $methodNode->parenClose->toString()); - } - ]; - } -} diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 89d6d2cd..4aefdbad 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -14,11 +14,53 @@ class NodeTest extends NodeTestCase * @return Generator */ public function provideNode(): Generator + { + yield from $this->methodTag(); + yield from $this->paramTag(); + yield from $this->varTag(); + yield from $this->returnTag(); + } + + /** + * @return Generator + */ + private function methodTag(): Generator { yield [ - '@param Baz\Bar $foobar', - function (ParamNode $methodNode) { + '@method static Baz\Bar bar(string $boo, string $baz)', + function (MethodNode $methodNode) { + self::assertEquals('@method static Baz\Bar bar(string $boo, string $baz)', $methodNode->toString()); + self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); + self::assertEquals('static', $methodNode->static->value); + self::assertEquals('Baz\Bar', $methodNode->type->toString()); + self::assertEquals('bar', $methodNode->name->toString()); + self::assertEquals('(', $methodNode->parenOpen->toString()); + self::assertEquals(')', $methodNode->parenClose->toString()); } ]; } + + /** + * @return Generator + */ + private function paramTag(): Generator + { + yield ['@param Baz\Bar $foobar']; + } + + /** + * @return Generator + */ + private function varTag(): Generator + { + yield ['@var Baz\Bar $foobar']; + } + + /** + * @return Generator + */ + private function returnTag(): Generator + { + yield ['@return Baz\Bar']; + } } diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php index 001ed0b6..dbd9fb1a 100644 --- a/tests/Unit/Ast/NodeTestCase.php +++ b/tests/Unit/Ast/NodeTestCase.php @@ -3,15 +3,13 @@ namespace Phpactor\Docblock\Tests\Unit\Ast; use Closure; -use Generator; use PHPUnit\Framework\TestCase; use Phpactor\Docblock\Ast\Element; -use Phpactor\Docblock\Ast\ElementList; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Lexer; use Phpactor\Docblock\Parser; -abstract class NodeTestCase extends TestCase +class NodeTestCase extends TestCase { /** * @dataProvider provideNode @@ -21,10 +19,12 @@ public function testNode(string $doc, ?Closure $assertion = null): void $node = $this->parse($doc); $nodes = iterator_to_array($node->getDescendantNodes(), false); self::assertIsIterable($nodes); - self::assertEquals(0, $node->start()); - self::assertEquals(strlen($doc), $node->end()); + self::assertEquals(0, $node->start(), 'Start offset'); + self::assertEquals(strlen($doc), $node->end(), 'End offset'); - $assertion($node); + if ($assertion) { + $assertion($node); + } } /** @@ -50,11 +50,6 @@ public function testIsomorphism(string $doc): void $two = $this->parse($one->toString()); self::assertEquals($one, $two); } - - /** - * @return Generator - */ - abstract public function provideNode(): Generator; private function parse(string $doc): Node { From 9e6a73330db9d624c3d14963a3d08cf49e376b43 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Tue, 26 Jan 2021 21:08:36 +0000 Subject: [PATCH 49/63] Finish adding node tests --- lib/Ast/DeprecatedNode.php | 13 +++++-- lib/Ast/MixinNode.php | 16 +++++++-- lib/Ast/TextNode.php | 6 +++- lib/Ast/Type/GenericNode.php | 1 - lib/Ast/Type/UnionNode.php | 5 --- lib/Ast/TypeList.php | 51 ++++++++++++++++++++++------ lib/Parser.php | 30 ++++++++++------- tests/Unit/Ast/NodeTest.php | 60 +++++++++++++++++++++------------ tests/Unit/Ast/NodeTestCase.php | 2 +- 9 files changed, 127 insertions(+), 57 deletions(-) diff --git a/lib/Ast/DeprecatedNode.php b/lib/Ast/DeprecatedNode.php index f9918262..90f7479a 100644 --- a/lib/Ast/DeprecatedNode.php +++ b/lib/Ast/DeprecatedNode.php @@ -2,20 +2,29 @@ namespace Phpactor\Docblock\Ast; +use Phpactor\Docblock\Token; + class DeprecatedNode extends TagNode { public const CHILD_NAMES = [ + 'token', 'text', ]; /** * @var TextNode */ - private $text; + public $text; + + /** + * @var Token + */ + public $token; - public function __construct(?TextNode $text) + public function __construct(Token $token, ?TextNode $text) { $this->text = $text; + $this->token = $token; } public function text(): ?TextNode diff --git a/lib/Ast/MixinNode.php b/lib/Ast/MixinNode.php index 6ebb834b..e38d6eec 100644 --- a/lib/Ast/MixinNode.php +++ b/lib/Ast/MixinNode.php @@ -3,17 +3,29 @@ namespace Phpactor\Docblock\Ast; use Phpactor\Docblock\Ast\Type\ClassNode; +use Phpactor\Docblock\Token; class MixinNode extends TagNode { + protected const CHILD_NAMES = [ + 'tag', + 'class' + ]; + /** * @var ClassNode|null */ - private $class; + public $class; + + /** + * @var Token + */ + public $tag; - public function __construct(?ClassNode $class) + public function __construct(Token $tag, ?ClassNode $class) { $this->class = $class; + $this->tag = $tag; } public function class(): ?ClassNode diff --git a/lib/Ast/TextNode.php b/lib/Ast/TextNode.php index 3855ebe3..625f5b2b 100644 --- a/lib/Ast/TextNode.php +++ b/lib/Ast/TextNode.php @@ -6,10 +6,14 @@ class TextNode extends Node { + protected const CHILD_NAMES = [ + 'tokens', + ]; + /** * @var Token[] */ - private $tokens; + public $tokens; /** * @param Token[] $tokens diff --git a/lib/Ast/Type/GenericNode.php b/lib/Ast/Type/GenericNode.php index b84e3158..1305d74d 100644 --- a/lib/Ast/Type/GenericNode.php +++ b/lib/Ast/Type/GenericNode.php @@ -9,7 +9,6 @@ class GenericNode extends TypeNode { protected const CHILD_NAMES = [ - 'open', 'type', 'open', 'parameters', diff --git a/lib/Ast/Type/UnionNode.php b/lib/Ast/Type/UnionNode.php index 5cba708b..ad96e744 100644 --- a/lib/Ast/Type/UnionNode.php +++ b/lib/Ast/Type/UnionNode.php @@ -20,9 +20,4 @@ public function __construct(TypeList $types) { $this->types = $types; } - - public function types(): TypeList - { - return $this->types; - } } diff --git a/lib/Ast/TypeList.php b/lib/Ast/TypeList.php index 811f4f40..a477b13c 100644 --- a/lib/Ast/TypeList.php +++ b/lib/Ast/TypeList.php @@ -5,31 +5,38 @@ use ArrayIterator; use Countable; use IteratorAggregate; +use Phpactor\Docblock\Token; +use RuntimeException; /** - * @implements IteratorAggregate + * @template T of Element + * @implements IteratorAggregate */ -class TypeList implements IteratorAggregate, Countable +class TypeList extends Node implements IteratorAggregate, Countable { + protected const CHILD_NAMES = [ + 'list' + ]; + /** - * @var TypeNode[] + * @var array */ - private $typeList; + public $list; /** - * @param TypeNode[] $typeList + * @param array $list */ - public function __construct(array $typeList) + public function __construct(array $list) { - $this->typeList = $typeList; + $this->list = $list; } /** - * @return ArrayIterator + * @return ArrayIterator */ public function getIterator(): ArrayIterator { - return new ArrayIterator($this->typeList); + return new ArrayIterator($this->list); } /** @@ -37,6 +44,30 @@ public function getIterator(): ArrayIterator */ public function count() { - return count($this->typeList); + return count($this->list); + } + + /** + * @return T + */ + public function first(): Element + { + if (!isset($this->list[0])) { + throw new RuntimeException(sprintf( + 'List has no first element' + )); + } + + return $this->list[0]; + } + + /** + * @return TypeList + */ + public function types(): self + { + return new self(array_filter($this->list, function (Element $element) { + return $element instanceof TypeNode; + })); } } diff --git a/lib/Parser.php b/lib/Parser.php index d8d9c4c8..d59e33eb 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -32,7 +32,7 @@ final class Parser { private const SCALAR_TYPES = [ - 'int', 'float', 'bool', 'string', 'mixed', 'callable' + 'int', 'float', 'bool', 'string', 'mixed' ]; /** * @var Tokens @@ -171,12 +171,12 @@ private function parseTypes(): ?TypeNode if (null === $type) { return $type; } - $types = [$type]; + $elements = [$type]; while (true) { if ($this->tokens->if(Token::T_BAR)) { - $this->tokens->chomp(); - $types[] = $this->parseType(); + $elements[] = $this->tokens->chomp(); + $elements[] = $this->parseType(); if (null !== $type) { continue; } @@ -184,11 +184,11 @@ private function parseTypes(): ?TypeNode break; } - if (count($types) === 1) { - return $types[0]; + if (count($elements) === 1) { + return $elements[0]; } - return new UnionNode(new TypeList($types)); + return new UnionNode(new TypeList($elements)); } private function parseType(): ?TypeNode @@ -267,7 +267,7 @@ private function parseTypeList(string $delimiter = ','): TypeList $types[] = $this->parseTypes(); } if ($this->tokens->if(Token::T_COMMA)) { - $this->tokens->chomp(); + $types[] = $this->tokens->chomp(); continue; } break; @@ -313,13 +313,15 @@ private function parseParameter(): ParameterNode private function parseDeprecated(): DeprecatedNode { - $this->tokens->chomp(); - return new DeprecatedNode($this->parseText()); + return new DeprecatedNode( + $this->tokens->chomp(Token::T_TAG), + $this->parseText() + ); } private function parseMixin(): MixinNode { - $this->tokens->chomp(); + $tag = $this->tokens->chomp(Token::T_TAG); $type = null; if ($this->tokens->if(Token::T_LABEL)) { @@ -329,7 +331,7 @@ private function parseMixin(): MixinNode } } - return new MixinNode($type); + return new MixinNode($tag, $type); } private function parseReturn(): ReturnNode @@ -346,17 +348,19 @@ private function parseReturn(): ReturnNode private function parseText(): ?TextNode { - if (!$this->tokens->current) { + if (null === $this->tokens->current) { return null; } $text = []; + if ( $this->tokens->current->type === Token::T_WHITESPACE && $this->tokens->next()->type === Token::T_LABEL ) { $this->tokens->chomp(); } + while ($this->tokens->current) { if ($this->tokens->current->type === Token::T_PHPDOC_CLOSE) { break; diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 4aefdbad..b3730ee4 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -7,6 +7,10 @@ use Phpactor\Docblock\Ast\MethodNode; use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Ast\ParamNode; +use Phpactor\Docblock\Ast\ReturnNode; +use Phpactor\Docblock\Ast\Type\GenericNode; +use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\UnionNode; class NodeTest extends NodeTestCase { @@ -15,17 +19,19 @@ class NodeTest extends NodeTestCase */ public function provideNode(): Generator { - yield from $this->methodTag(); - yield from $this->paramTag(); - yield from $this->varTag(); - yield from $this->returnTag(); + yield from $this->provideTags(); + yield from $this->provideTypes(); } /** * @return Generator */ - private function methodTag(): Generator + private function provideTags() { + yield [ '@deprecated This is deprecated']; + yield [ '/** This is docblock @deprecated Foo */']; + yield [ '@mixin Foo\Bar']; + yield [ '@param string $foo This is a parameter']; yield [ '@method static Baz\Bar bar(string $boo, string $baz)', function (MethodNode $methodNode) { @@ -38,29 +44,39 @@ function (MethodNode $methodNode) { self::assertEquals(')', $methodNode->parenClose->toString()); } ]; - } - - /** - * @return Generator - */ - private function paramTag(): Generator - { - yield ['@param Baz\Bar $foobar']; - } - - /** - * @return Generator - */ - private function varTag(): Generator - { + yield ['@param Baz\Bar $foobar This is a parameter']; yield ['@var Baz\Bar $foobar']; + yield ['@return Baz\Bar']; } /** * @return Generator */ - private function returnTag(): Generator + private function provideTypes(): Generator { - yield ['@return Baz\Bar']; + yield 'scalar' => ['string']; + yield 'union' => [ + '@return string|int|bool|float|mixed', + function (ReturnNode $return) { + $type = $return->type; + assert($type instanceof UnionNode); + self::assertInstanceOf(UnionNode::class, $type); + self::assertEquals('string', $type->types->types()->first()->toString()); + self::assertCount(5, $type->types->types()); + } + ]; + yield 'list' => [ + '@return Foo[]', + function (ReturnNode $return) { + self::assertInstanceOf(ListNode::class, $return->type); + } + ]; + yield 'generic' => [ + '@return Foo, Baz|Bar>', + function (ReturnNode $return) { + self::assertInstanceOf(GenericNode::class, $return->type); + } + ]; } + } diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php index dbd9fb1a..bdde0e85 100644 --- a/tests/Unit/Ast/NodeTestCase.php +++ b/tests/Unit/Ast/NodeTestCase.php @@ -48,7 +48,7 @@ public function testIsomorphism(string $doc): void { $one = $this->parse($doc); $two = $this->parse($one->toString()); - self::assertEquals($one, $two); + self::assertEquals($one, $two, $one->toString()); } private function parse(string $doc): Node From c616bcbd8f628d779ce84486be7de2725c96c021 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Tue, 26 Jan 2021 21:13:12 +0000 Subject: [PATCH 50/63] Namespaced tag nodes --- lib/Ast/Node.php | 2 +- .../DeprecatedTag.php} | 13 ++--- lib/Ast/{MethodNode.php => Tag/MethodTag.php} | 45 +++-------------- lib/Ast/{MixinNode.php => Tag/MixinTag.php} | 7 +-- lib/Ast/{ParamNode.php => Tag/ParamTag.php} | 10 ++-- .../ParameterTag.php} | 9 +++- .../{PropertyNode.php => Tag/PropertyTag.php} | 8 +-- lib/Ast/{ReturnNode.php => Tag/ReturnTag.php} | 9 ++-- lib/Ast/{VarNode.php => Tag/VarTag.php} | 9 ++-- lib/Ast/TextNode.php | 2 +- lib/{ => Ast}/Token.php | 2 +- lib/{ => Ast}/Tokens.php | 3 +- lib/Ast/Type/ClassNode.php | 2 +- lib/Ast/Type/GenericNode.php | 2 +- lib/Ast/Type/ListNode.php | 2 +- lib/Ast/Type/NullNode.php | 2 +- lib/Ast/Type/NullableNode.php | 2 +- lib/Ast/Type/ScalarNode.php | 2 +- lib/Ast/TypeList.php | 2 +- lib/Ast/TypeNode.php | 2 +- lib/Ast/UnknownTag.php | 2 +- lib/Ast/Value/NullValue.php | 2 +- lib/Ast/VariableNode.php | 2 +- lib/Lexer.php | 3 ++ lib/Parser.php | 50 ++++++++++--------- lib/Printer/TestPrinter.php | 50 +++++++++---------- tests/Unit/Ast/NodeTest.php | 14 +++--- tests/Unit/LexerTest.php | 2 +- tests/Unit/ParserTest.php | 2 +- 29 files changed, 126 insertions(+), 136 deletions(-) rename lib/Ast/{DeprecatedNode.php => Tag/DeprecatedTag.php} (65%) rename lib/Ast/{MethodNode.php => Tag/MethodTag.php} (64%) rename lib/Ast/{MixinNode.php => Tag/MixinTag.php} (77%) rename lib/Ast/{ParamNode.php => Tag/ParamTag.php} (77%) rename lib/Ast/{ParameterNode.php => Tag/ParameterTag.php} (77%) rename lib/Ast/{PropertyNode.php => Tag/PropertyTag.php} (70%) rename lib/Ast/{ReturnNode.php => Tag/ReturnTag.php} (74%) rename lib/Ast/{VarNode.php => Tag/VarTag.php} (75%) rename lib/{ => Ast}/Token.php (98%) rename lib/{ => Ast}/Tokens.php (97%) diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 21e30298..deb35bef 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -3,7 +3,7 @@ namespace Phpactor\Docblock\Ast; use Generator; -use Phpactor\Docblock\Token; +use Phpactor\Docblock\Ast\Token; abstract class Node implements Element { diff --git a/lib/Ast/DeprecatedNode.php b/lib/Ast/Tag/DeprecatedTag.php similarity index 65% rename from lib/Ast/DeprecatedNode.php rename to lib/Ast/Tag/DeprecatedTag.php index 90f7479a..81500dab 100644 --- a/lib/Ast/DeprecatedNode.php +++ b/lib/Ast/Tag/DeprecatedTag.php @@ -1,10 +1,12 @@ text = $text; $this->token = $token; } - - public function text(): ?TextNode - { - return $this->text; - } } diff --git a/lib/Ast/MethodNode.php b/lib/Ast/Tag/MethodTag.php similarity index 64% rename from lib/Ast/MethodNode.php rename to lib/Ast/Tag/MethodTag.php index 0569adc4..f2657f3a 100644 --- a/lib/Ast/MethodNode.php +++ b/lib/Ast/Tag/MethodTag.php @@ -1,10 +1,14 @@ parenClose = $parenClose; $this->tag = $tag; } - - public function name(): ?Token - { - return $this->name; - } - - public function type(): ?TypeNode - { - return $this->type; - } - - public function static(): ?Token - { - return $this->static; - } - - public function parameters(): ?ParameterList - { - return $this->parameters; - } - - public function text(): ?TextNode - { - return $this->text; - } - - public function parenOpen(): ?Token - { - return $this->parenOpen; - } - - public function parenClose(): ?Token - { - return $this->parenClose; - } } diff --git a/lib/Ast/MixinNode.php b/lib/Ast/Tag/MixinTag.php similarity index 77% rename from lib/Ast/MixinNode.php rename to lib/Ast/Tag/MixinTag.php index e38d6eec..48dbf7bd 100644 --- a/lib/Ast/MixinNode.php +++ b/lib/Ast/Tag/MixinTag.php @@ -1,11 +1,12 @@ tokens->chomp()); } - private function parseParam(): ParamNode + private function parseParam(): ParamTag { $type = $variable = $textNode = null; $tag = $this->tokens->chomp(Token::T_TAG); @@ -105,10 +107,10 @@ private function parseParam(): ParamNode $variable = $this->parseVariable(); } - return new ParamNode($tag, $type, $variable, $this->parseText()); + return new ParamTag($tag, $type, $variable, $this->parseText()); } - private function parseVar(): VarNode + private function parseVar(): VarTag { $tag = $this->tokens->chomp(Token::T_TAG); $type = $variable = null; @@ -119,10 +121,10 @@ private function parseVar(): VarNode $variable = $this->parseVariable(); } - return new VarNode($tag, $type, $variable); + return new VarTag($tag, $type, $variable); } - private function parseMethod(): MethodNode + private function parseMethod(): MethodTag { $tag = $this->tokens->chomp(Token::T_TAG); $type = $name = $parameterList = $open = $close = null; @@ -148,10 +150,10 @@ private function parseMethod(): MethodNode $close = $this->tokens->chompIf(Token::T_PAREN_CLOSE); } - return new MethodNode($tag, $type, $name, $static, $open, $parameterList, $close, $this->parseText()); + return new MethodTag($tag, $type, $name, $static, $open, $parameterList, $close, $this->parseText()); } - private function parseProperty(): PropertyNode + private function parseProperty(): PropertyTag { $this->tokens->chomp(Token::T_TAG); $type = $name = null; @@ -162,7 +164,7 @@ private function parseProperty(): PropertyNode $name = $this->tokens->chomp(); } - return new PropertyNode($type, $name); + return new PropertyTag($type, $name); } private function parseTypes(): ?TypeNode @@ -295,7 +297,7 @@ private function parseParameterList(): ?ParameterList return new ParameterList($parameters); } - private function parseParameter(): ParameterNode + private function parseParameter(): ParameterTag { $type = $name = $default = null; if ($this->tokens->if(Token::T_LABEL)) { @@ -308,18 +310,18 @@ private function parseParameter(): ParameterNode $equals = $this->tokens->chomp(); $default = $this->parseValue(); } - return new ParameterNode($type, $name, $default); + return new ParameterTag($type, $name, $default); } - private function parseDeprecated(): DeprecatedNode + private function parseDeprecated(): DeprecatedTag { - return new DeprecatedNode( + return new DeprecatedTag( $this->tokens->chomp(Token::T_TAG), $this->parseText() ); } - private function parseMixin(): MixinNode + private function parseMixin(): MixinTag { $tag = $this->tokens->chomp(Token::T_TAG); $type = null; @@ -331,10 +333,10 @@ private function parseMixin(): MixinNode } } - return new MixinNode($tag, $type); + return new MixinTag($tag, $type); } - private function parseReturn(): ReturnNode + private function parseReturn(): ReturnTag { $tag = $this->tokens->chomp(Token::T_TAG); $type = null; @@ -343,7 +345,7 @@ private function parseReturn(): ReturnNode $type = $this->parseTypes(); } - return new ReturnNode($tag, $type, $this->parseText()); + return new ReturnTag($tag, $type, $this->parseText()); } private function parseText(): ?TextNode diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index f68553db..829fc615 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -2,20 +2,20 @@ namespace Phpactor\Docblock\Printer; -use Phpactor\Docblock\Ast\DeprecatedNode; +use Phpactor\Docblock\Ast\Tag\DeprecatedTag; use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; -use Phpactor\Docblock\Ast\MethodNode; -use Phpactor\Docblock\Ast\MixinNode; +use Phpactor\Docblock\Ast\Tag\MethodTag; +use Phpactor\Docblock\Ast\Tag\MixinTag; use Phpactor\Docblock\Ast\ParameterList; -use Phpactor\Docblock\Ast\ParameterNode; -use Phpactor\Docblock\Ast\PropertyNode; -use Phpactor\Docblock\Ast\ReturnNode; +use Phpactor\Docblock\Ast\Tag\ParameterTag; +use Phpactor\Docblock\Ast\Tag\PropertyTag; +use Phpactor\Docblock\Ast\Tag\ReturnTag; use Phpactor\Docblock\Ast\TextNode; use Phpactor\Docblock\Ast\TypeList; use Phpactor\Docblock\Ast\TypeNode; use Phpactor\Docblock\Ast\Node; -use Phpactor\Docblock\Ast\ParamNode; +use Phpactor\Docblock\Ast\Tag\ParamTag; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\Type\NullNode; @@ -23,10 +23,10 @@ use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\ValueNode; -use Phpactor\Docblock\Ast\VarNode; +use Phpactor\Docblock\Ast\Tag\VarTag; use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Printer; -use Phpactor\Docblock\Token; +use Phpactor\Docblock\Ast\Token; use RuntimeException; final class TestPrinter implements Printer @@ -62,12 +62,12 @@ private function render(?Element $node): void return; } - if ($node instanceof ParamNode) { + if ($node instanceof ParamTag) { $this->renderParam($node); return; } - if ($node instanceof VarNode) { + if ($node instanceof VarTag) { $this->renderVar($node); return; } @@ -117,32 +117,32 @@ private function render(?Element $node): void return; } - if ($node instanceof DeprecatedNode) { + if ($node instanceof DeprecatedTag) { $this->renderDeprecated($node); return; } - if ($node instanceof MethodNode) { + if ($node instanceof MethodTag) { $this->renderMethod($node); return; } - if ($node instanceof PropertyNode) { + if ($node instanceof PropertyTag) { $this->renderProperty($node); return; } - if ($node instanceof MixinNode) { + if ($node instanceof MixinTag) { $this->renderMixin($node); return; } - if ($node instanceof ReturnNode) { + if ($node instanceof ReturnTag) { $this->renderReturn($node); return; } - if ($node instanceof ParameterNode) { + if ($node instanceof ParameterTag) { $this->renderParameter($node); return; } @@ -165,7 +165,7 @@ private function renderDocblock(Docblock $node): void } } - private function renderParam(ParamNode $node): void + private function renderParam(ParamTag $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->type()); @@ -178,7 +178,7 @@ private function renderParam(ParamNode $node): void $this->out[] = ')'; } - private function renderVar(VarNode $node): void + private function renderVar(VarTag $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->type()); @@ -236,7 +236,7 @@ private function renderTextNode(TextNode $node): void $this->out[] = ')'; } - private function renderDeprecated(DeprecatedNode $node): void + private function renderDeprecated(DeprecatedTag $node): void { $this->out[] = $node->shortName() . '('; if ($node->text()) { @@ -245,7 +245,7 @@ private function renderDeprecated(DeprecatedNode $node): void $this->out[] = ')'; } - private function renderMethod(MethodNode $node): void + private function renderMethod(MethodTag $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->type()); @@ -267,7 +267,7 @@ private function renderMethod(MethodNode $node): void $this->out[] = ')'; } - private function renderMixin(MixinNode $node): void + private function renderMixin(MixinTag $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->class()); @@ -281,7 +281,7 @@ private function renderNullable(NullableNode $node): void $this->out[] = ')'; } - private function renderProperty(PropertyNode $node): void + private function renderProperty(PropertyTag $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->type()); @@ -292,7 +292,7 @@ private function renderProperty(PropertyNode $node): void $this->out[] = ')'; } - private function renderReturn(ReturnNode $node): void + private function renderReturn(ReturnTag $node): void { $this->out[] = $node->shortName() . '('; $this->render($node->type()); @@ -319,7 +319,7 @@ private function renderParameterList(ParameterList $list): void $this->out[] = ')'; } - private function renderParameter(ParameterNode $node): void + private function renderParameter(ParameterTag $node): void { $this->out[] = $node->shortName() . '('; if ($node->name()) { diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index b3730ee4..05b9616e 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -4,10 +4,10 @@ use Generator; use PHPUnit\Framework\TestCase; -use Phpactor\Docblock\Ast\MethodNode; +use Phpactor\Docblock\Ast\Tag\MethodTag; use Phpactor\Docblock\Ast\Node; -use Phpactor\Docblock\Ast\ParamNode; -use Phpactor\Docblock\Ast\ReturnNode; +use Phpactor\Docblock\Ast\Tag\ParamTag; +use Phpactor\Docblock\Ast\Tag\ReturnTag; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; use Phpactor\Docblock\Ast\Type\UnionNode; @@ -34,7 +34,7 @@ private function provideTags() yield [ '@param string $foo This is a parameter']; yield [ '@method static Baz\Bar bar(string $boo, string $baz)', - function (MethodNode $methodNode) { + function (MethodTag $methodNode) { self::assertEquals('@method static Baz\Bar bar(string $boo, string $baz)', $methodNode->toString()); self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); self::assertEquals('static', $methodNode->static->value); @@ -57,7 +57,7 @@ private function provideTypes(): Generator yield 'scalar' => ['string']; yield 'union' => [ '@return string|int|bool|float|mixed', - function (ReturnNode $return) { + function (ReturnTag $return) { $type = $return->type; assert($type instanceof UnionNode); self::assertInstanceOf(UnionNode::class, $type); @@ -67,13 +67,13 @@ function (ReturnNode $return) { ]; yield 'list' => [ '@return Foo[]', - function (ReturnNode $return) { + function (ReturnTag $return) { self::assertInstanceOf(ListNode::class, $return->type); } ]; yield 'generic' => [ '@return Foo, Baz|Bar>', - function (ReturnNode $return) { + function (ReturnTag $return) { self::assertInstanceOf(GenericNode::class, $return->type); } ]; diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 150c7333..4835aa8e 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -5,7 +5,7 @@ use Generator; use PHPUnit\Framework\TestCase; use Phpactor\Docblock\Lexer; -use Phpactor\Docblock\Token; +use Phpactor\Docblock\Ast\Token; class LexerTest extends TestCase { diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index 26949747..ae68a0e0 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -8,7 +8,7 @@ use Phpactor\Docblock\Ast\Node; use Phpactor\Docblock\Lexer; use Phpactor\Docblock\Parser; -use Phpactor\Docblock\Token; +use Phpactor\Docblock\Ast\Token; class ParserTest extends TestCase { From eaec94d067506c3ab115262bd54c1125ee7a3546 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Tue, 26 Jan 2021 21:40:20 +0000 Subject: [PATCH 51/63] Migrate printer tests --- lib/Ast/Node.php | 4 +- lib/Parser.php | 5 +- lib/Printer/TestPrinter.php | 309 ++---------------------- tests/Printer/examples/classname1.test | 8 +- tests/Printer/examples/classname2.test | 8 +- tests/Printer/examples/deprecated1.test | 6 +- tests/Printer/examples/deprecated2.test | 7 +- tests/Printer/examples/generic1.test | 12 +- tests/Printer/examples/generic2.test | 13 +- tests/Printer/examples/generic3.test | 16 +- tests/Printer/examples/generic4.test | 19 +- tests/Printer/examples/list1.test | 15 +- tests/Printer/examples/method1.test | 7 +- tests/Printer/examples/method2.test | 7 +- tests/Printer/examples/method3.test | 12 +- tests/Printer/examples/method4.test | 12 +- tests/Printer/examples/method5.test | 23 +- tests/Printer/examples/method6.test | 27 ++- tests/Printer/examples/mixin1.test | 7 +- tests/Printer/examples/nullable1.test | 8 +- tests/Printer/examples/param1.test | 8 +- tests/Printer/examples/param2.test | 13 +- tests/Printer/examples/param3.test | 18 +- tests/Printer/examples/param4.test | 7 +- tests/Printer/examples/param5.test | 9 +- tests/Printer/examples/param6.test | 15 +- tests/Printer/examples/php_core1.test | 30 ++- tests/Printer/examples/property1.test | 6 +- tests/Printer/examples/return1.test | 7 +- tests/Printer/examples/text1.test | 3 +- tests/Printer/examples/union1.test | 10 +- tests/Printer/examples/var1.test | 8 +- tests/Printer/examples/var2.test | 6 +- tests/Printer/examples/var3.test | 6 +- tests/Unit/Ast/NodeTestCase.php | 2 +- 35 files changed, 313 insertions(+), 360 deletions(-) diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index deb35bef..a02ca72f 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -59,7 +59,7 @@ public function shortName(): string /** * @return Generator */ - public function getDescendantNodes(): Generator + public function getDescendantElements(): Generator { yield $this; yield from $this->walkNodes($this->getChildElements()); @@ -80,7 +80,7 @@ private function walkNodes(iterable $nodes): Generator } if ($child instanceof Node) { - yield from $child->getDescendantNodes(); + yield from $child->getDescendantElements(); continue; } diff --git a/lib/Parser.php b/lib/Parser.php index 30e0c3ae..977a73f3 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -33,8 +33,11 @@ final class Parser { + /** + * TODO: callable is not a scalar + */ private const SCALAR_TYPES = [ - 'int', 'float', 'bool', 'string', 'mixed' + 'int', 'float', 'bool', 'string', 'mixed', 'callable', ]; /** * @var Tokens diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 829fc615..fb281ad7 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -36,308 +36,41 @@ final class TestPrinter implements Printer */ private $out = []; - public function print(Node $node): string - { - $this->out = []; - - $this->render($node); - - return implode('', $this->out); - } - - private function render(?Element $node): void - { - if (null === $node) { - $this->out[] = '#missing#'; - return; - } - - if ($node instanceof Docblock) { - $this->renderDocblock($node); - return; - } - - if ($node instanceof Token) { - $this->out[] = $node->value; - return; - } - - if ($node instanceof ParamTag) { - $this->renderParam($node); - return; - } - - if ($node instanceof VarTag) { - $this->renderVar($node); - return; - } - - if ($node instanceof ListNode) { - $this->renderListNode($node); - return; - } - - if ($node instanceof GenericNode) { - $this->renderGenericNode($node); - return; - } - - if ($node instanceof NullableNode) { - $this->renderNullable($node); - return; - } - - if ($node instanceof UnionNode) { - $this->renderUnion($node); - return; - } - - if ($node instanceof NullNode) { - $this->out[] = $node->shortName() . '()'; - return; - } - - if ($node instanceof TypeNode) { - $this->renderTypeNode($node); - return; - } + private $indent = 0; - if ($node instanceof TextNode) { - $this->renderTextNode($node); - return; - } - - if ($node instanceof VariableNode) { - $this->renderVariableNode($node); - return; - } - - if ($node instanceof UnknownTag) { - $this->out[] = $node->shortName(); - return; - } - - if ($node instanceof DeprecatedTag) { - $this->renderDeprecated($node); - return; - } - - if ($node instanceof MethodTag) { - $this->renderMethod($node); - return; - } - - if ($node instanceof PropertyTag) { - $this->renderProperty($node); - return; - } - - if ($node instanceof MixinTag) { - $this->renderMixin($node); - return; - } - - if ($node instanceof ReturnTag) { - $this->renderReturn($node); - return; - } - - if ($node instanceof ParameterTag) { - $this->renderParameter($node); - return; - } - - if ($node instanceof ValueNode) { - $this->renderValue($node); - return; - } - - throw new RuntimeException(sprintf( - 'Do not know how to render "%s"', - get_class($node) - )); - } - - private function renderDocblock(Docblock $node): void - { - foreach ($node->children() as $child) { - $this->render($child); - } - } - - private function renderParam(ParamTag $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - $this->out[] = ','; - $this->render($node->variable()); - if ($node->text()) { - $this->out[] = ','; - $this->render($node->text()); - } - $this->out[] = ')'; - } - - private function renderVar(VarTag $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - if ($node->variable()) { - $this->out[] = ','; - $this->render($node->variable()); - } - $this->out[] = ')'; - } - - private function renderTypeNode(TypeNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->name()); - $this->out[] = ')'; - } - - private function renderVariableNode(VariableNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->name()); - $this->out[] = ')'; - } - - private function renderListNode(ListNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - $this->out[] = ')'; - } - - private function renderGenericNode(GenericNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - $this->out[] = ','; - $this->renderTypeList($node->parameters()); - $this->out[] = ')'; - } - - private function renderTypeList(TypeList $typeList, string $delimiter = ','): void - { - foreach ($typeList as $i => $param) { - $this->render($param); - if ($i + 1 !== $typeList->count()) { - $this->out[] = $delimiter; - } - } - } - - private function renderTextNode(TextNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->out[] = $node->toString(); - $this->out[] = ')'; - } - - private function renderDeprecated(DeprecatedTag $node): void - { - $this->out[] = $node->shortName() . '('; - if ($node->text()) { - $this->render($node->text()); - } - $this->out[] = ')'; - } - - private function renderMethod(MethodTag $node): void + public function print(Node $node): string { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - if ($node->name()) { - $this->out[] = ','; - $this->render($node->name()); - } - if ($node->static()) { - $this->out[] = ',static'; - } - if ($node->parameters()) { - $this->out[] = ','; - $this->renderParameterList($node->parameters()); - } - if ($node->text()) { - $this->out[] = ','; - $this->render($node->text()); + $this->indent++; + $out = sprintf('%s: = ', $node->shortName()); + foreach ($node->getChildElements() as $child) { + $out .= $this->printElement($child); } - $this->out[] = ')'; - } + $this->indent--; - private function renderMixin(MixinTag $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->class()); - $this->out[] = ')'; + return $out; } - private function renderNullable(NullableNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - $this->out[] = ')'; - } - - private function renderProperty(PropertyTag $node): void + /** + * @param array|Element $element + */ + public function printElement($element): string { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - if ($node->name()) { - $this->out[] = ','; - $this->render($node->name()); + if ($element instanceof Token) { + return sprintf('%s', $element->value); } - $this->out[] = ')'; - } - private function renderReturn(ReturnTag $node): void - { - $this->out[] = $node->shortName() . '('; - $this->render($node->type()); - if ($node->text()) { - $this->out[] = ','; - $this->render($node->text()); + if ($element instanceof Node) { + return $this->newLine() . $this->print($element); } - $this->out[] = ')'; - } - private function renderUnion(UnionNode $node): void - { - $this->out[] = $node->shortName() . '('; - $this->renderTypeList($node->types(), '|'); - $this->out[] = ')'; + return implode('', array_map(function (Element $element) { + return $this->printElement($element); + }, (array)$element)); } - private function renderParameterList(ParameterList $list): void + private function newLine(): string { - $this->out[] = 'ParameterList('; - foreach ($list as $i => $parameter) { - $this->render($parameter); - } - $this->out[] = ')'; + return "\n".str_repeat(' ', $this->indent); } - private function renderParameter(ParameterTag $node): void - { - $this->out[] = $node->shortName() . '('; - if ($node->name()) { - $this->render($node->name()); - } - if ($node->type()) { - $this->out[] = ','; - $this->render($node->type()); - } - if ($node->default()) { - $this->out[] = ','; - $this->render($node->default()); - } - $this->out[] = ')'; - } - - private function renderValue(ValueNode $node): void - { - $this->out[] = json_encode($node->value()); - } } diff --git a/tests/Printer/examples/classname1.test b/tests/Printer/examples/classname1.test index 492b2b5b..e107e06e 100644 --- a/tests/Printer/examples/classname1.test +++ b/tests/Printer/examples/classname1.test @@ -2,7 +2,9 @@ * @var Foobar\Barfoo */ --- -/** - * VarNode(ClassNode(Foobar\Barfoo)) +Docblock: = + ElementList: = /** + * + VarTag: = @var + ClassNode: = Foobar\Barfoo */ - diff --git a/tests/Printer/examples/classname2.test b/tests/Printer/examples/classname2.test index 286959e4..3dab2b6b 100644 --- a/tests/Printer/examples/classname2.test +++ b/tests/Printer/examples/classname2.test @@ -2,7 +2,9 @@ * @var \foo_bar\bar_foo */ --- -/** - * VarNode(ClassNode(\foo_bar\bar_foo)) +Docblock: = + ElementList: = /** + * + VarTag: = @var + ClassNode: = \foo_bar\bar_foo */ - diff --git a/tests/Printer/examples/deprecated1.test b/tests/Printer/examples/deprecated1.test index dd9ff0d5..2189e884 100644 --- a/tests/Printer/examples/deprecated1.test +++ b/tests/Printer/examples/deprecated1.test @@ -2,6 +2,8 @@ * @deprecated */ --- -/** - * DeprecatedNode() +Docblock: = + ElementList: = /** + * + DeprecatedTag: = @deprecated */ diff --git a/tests/Printer/examples/deprecated2.test b/tests/Printer/examples/deprecated2.test index 9695d03a..721db036 100644 --- a/tests/Printer/examples/deprecated2.test +++ b/tests/Printer/examples/deprecated2.test @@ -2,6 +2,9 @@ * @deprecated This is because */ --- -/** - * DeprecatedNode(TextNode(This is because)) +Docblock: = + ElementList: = /** + * + DeprecatedTag: = @deprecated + TextNode: = This is because */ diff --git a/tests/Printer/examples/generic1.test b/tests/Printer/examples/generic1.test index 8b3b802d..3fd85b39 100644 --- a/tests/Printer/examples/generic1.test +++ b/tests/Printer/examples/generic1.test @@ -2,6 +2,14 @@ * @param Foobar $foobar */ --- -/** - * ParamNode(GenericNode(ClassNode(Foobar),ListNode(ClassNode(Barfoo))),VariableNode($foobar)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + GenericNode: = + ClassNode: = Foobar< + TypeList: = + ListNode: = + ClassNode: = Barfoo[]> + VariableNode: = $foobar */ diff --git a/tests/Printer/examples/generic2.test b/tests/Printer/examples/generic2.test index 94017cf8..60b522ec 100644 --- a/tests/Printer/examples/generic2.test +++ b/tests/Printer/examples/generic2.test @@ -2,6 +2,15 @@ * @param Foobar $foobar */ --- -/** - * ParamNode(GenericNode(ClassNode(Foobar),ListNode(ClassNode(Barfoo)),ScalarNode(string)),VariableNode($foobar)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + GenericNode: = + ClassNode: = Foobar< + TypeList: = + ListNode: = + ClassNode: = Barfoo[], + ScalarNode: = string> + VariableNode: = $foobar */ diff --git a/tests/Printer/examples/generic3.test b/tests/Printer/examples/generic3.test index 13b5eba0..5b2addde 100644 --- a/tests/Printer/examples/generic3.test +++ b/tests/Printer/examples/generic3.test @@ -2,6 +2,18 @@ * @param Foobar,string> $foobar */ --- -/** - * ParamNode(GenericNode(ClassNode(Foobar),GenericNode(ClassNode(Barfoo),ScalarNode(int),ScalarNode(string)),ScalarNode(string)),VariableNode($foobar)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + GenericNode: = + ClassNode: = Foobar< + TypeList: = + GenericNode: = + ClassNode: = Barfoo< + TypeList: = + ScalarNode: = int, + ScalarNode: = string>, + ScalarNode: = string> + VariableNode: = $foobar */ diff --git a/tests/Printer/examples/generic4.test b/tests/Printer/examples/generic4.test index d2e633da..97a8ef56 100644 --- a/tests/Printer/examples/generic4.test +++ b/tests/Printer/examples/generic4.test @@ -2,6 +2,21 @@ * @param Foobar,string, Baz> $foobar */ --- -/** - * ParamNode(GenericNode(ClassNode(Foobar),GenericNode(ClassNode(Barfoo),ListNode(ScalarNode(int)),ListNode(ScalarNode(int))),ScalarNode(string),ClassNode(Baz)),VariableNode($foobar)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + GenericNode: = + ClassNode: = Foobar< + TypeList: = + GenericNode: = + ClassNode: = Barfoo< + TypeList: = + ListNode: = + ScalarNode: = int[], + ListNode: = + ScalarNode: = int[]>, + ScalarNode: = string, + ClassNode: = Baz> + VariableNode: = $foobar */ diff --git a/tests/Printer/examples/list1.test b/tests/Printer/examples/list1.test index 94d5be11..9ee9cb00 100644 --- a/tests/Printer/examples/list1.test +++ b/tests/Printer/examples/list1.test @@ -3,7 +3,16 @@ * @param string[] $strings */ --- -/** - * ParamNode(ListNode(ClassNode(Foobar)),VariableNode($foobar)) - * ParamNode(ListNode(ScalarNode(string)),VariableNode($strings)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + ListNode: = + ClassNode: = Foobar[] + VariableNode: = $foobar + * + ParamTag: = @param + ListNode: = + ScalarNode: = string[] + VariableNode: = $strings */ diff --git a/tests/Printer/examples/method1.test b/tests/Printer/examples/method1.test index 826a9b5a..80770a95 100644 --- a/tests/Printer/examples/method1.test +++ b/tests/Printer/examples/method1.test @@ -2,6 +2,9 @@ * @method Foobar foobar() */ --- -/** - * MethodNode(ClassNode(Foobar),foobar) +Docblock: = + ElementList: = /** + * + MethodTag: = @method + ClassNode: = Foobarfoobar() */ diff --git a/tests/Printer/examples/method2.test b/tests/Printer/examples/method2.test index ab66ad1f..ba7d6680 100644 --- a/tests/Printer/examples/method2.test +++ b/tests/Printer/examples/method2.test @@ -2,6 +2,9 @@ * @method static Foobar foobar() */ --- -/** - * MethodNode(ClassNode(Foobar),foobar,static) +Docblock: = + ElementList: = /** + * + MethodTag: = @methodstatic + ClassNode: = Foobarfoobar() */ diff --git a/tests/Printer/examples/method3.test b/tests/Printer/examples/method3.test index 8600ba44..ef4e61d5 100644 --- a/tests/Printer/examples/method3.test +++ b/tests/Printer/examples/method3.test @@ -2,6 +2,14 @@ * @method static bool allAlnum(mixed $value) Assert that value is alphanumeric for all values. */ --- -/** - * MethodNode(ScalarNode(bool),allAlnum,static,ParameterList(ParameterNode(VariableNode($value),ScalarNode(mixed))),TextNode(Assert that value is alphanumeric for all values.)) +Docblock: = + ElementList: = /** + * + MethodTag: = @methodstatic + ScalarNode: = boolallAlnum( + ParameterList: = + ParameterTag: = + ScalarNode: = mixed + VariableNode: = $value) + TextNode: = Assert that value is alphanumeric for all values. */ diff --git a/tests/Printer/examples/method4.test b/tests/Printer/examples/method4.test index c6a80786..c2876fec 100644 --- a/tests/Printer/examples/method4.test +++ b/tests/Printer/examples/method4.test @@ -2,6 +2,14 @@ * @method bool is(string $value = null) */ --- -/** - * MethodNode(ScalarNode(bool),is,ParameterList(ParameterNode(VariableNode($value),ScalarNode(string),null))) +Docblock: = + ElementList: = /** + * + MethodTag: = @method + ScalarNode: = boolis( + ParameterList: = + ParameterTag: = + ScalarNode: = string + VariableNode: = $value + NullValue: = ) */ diff --git a/tests/Printer/examples/method5.test b/tests/Printer/examples/method5.test index ee5656aa..a27e6a65 100644 --- a/tests/Printer/examples/method5.test +++ b/tests/Printer/examples/method5.test @@ -2,6 +2,25 @@ * @method static bool allAlnum(mixed $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values. */ --- -/** - * MethodNode(ScalarNode(bool),allAlnum,static,ParameterList(ParameterNode(VariableNode($value),ScalarNode(mixed)),ParameterNode(VariableNode($message),UnionNode(ScalarNode(string)|ScalarNode(callable)),null),ParameterNode(VariableNode($propertyPath),ScalarNode(string),null)),TextNode(Assert that value is alphanumeric for all values.)) +Docblock: = + ElementList: = /** + * + MethodTag: = @methodstatic + ScalarNode: = boolallAlnum( + ParameterList: = + ParameterTag: = + ScalarNode: = mixed + VariableNode: = $value, + ParameterTag: = + UnionNode: = + TypeList: = + ScalarNode: = string| + ScalarNode: = callable + VariableNode: = $message + NullValue: = , + ParameterTag: = + ScalarNode: = string + VariableNode: = $propertyPath + NullValue: = ) + TextNode: = Assert that value is alphanumeric for all values. */ diff --git a/tests/Printer/examples/method6.test b/tests/Printer/examples/method6.test index 60452406..9a301be1 100644 --- a/tests/Printer/examples/method6.test +++ b/tests/Printer/examples/method6.test @@ -2,6 +2,29 @@ * @method static bool allIpv4(string $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address for all values. */ --- -/** - * MethodNode(ScalarNode(bool),allIpv4,static,ParameterList(ParameterNode(VariableNode($value),ScalarNode(string)),ParameterNode(VariableNode($flag),ScalarNode(int),null),ParameterNode(VariableNode($message),UnionNode(ScalarNode(string)|ScalarNode(callable)),null),ParameterNode(VariableNode($propertyPath),ScalarNode(string),null)),TextNode(Assert that value is an IPv4 address for all values.)) +Docblock: = + ElementList: = /** + * + MethodTag: = @methodstatic + ScalarNode: = boolallIpv4( + ParameterList: = + ParameterTag: = + ScalarNode: = string + VariableNode: = $value, + ParameterTag: = + ScalarNode: = int + VariableNode: = $flag + NullValue: = , + ParameterTag: = + UnionNode: = + TypeList: = + ScalarNode: = string| + ScalarNode: = callable + VariableNode: = $message + NullValue: = , + ParameterTag: = + ScalarNode: = string + VariableNode: = $propertyPath + NullValue: = ) + TextNode: = Assert that value is an IPv4 address for all values. */ diff --git a/tests/Printer/examples/mixin1.test b/tests/Printer/examples/mixin1.test index acdadcfa..a2fde0b2 100644 --- a/tests/Printer/examples/mixin1.test +++ b/tests/Printer/examples/mixin1.test @@ -2,6 +2,9 @@ * @mixin Foobar */ --- -/** - * MixinNode(ClassNode(Foobar)) +Docblock: = + ElementList: = /** + * + MixinTag: = @mixin + ClassNode: = Foobar */ diff --git a/tests/Printer/examples/nullable1.test b/tests/Printer/examples/nullable1.test index 9014ebf9..806112b9 100644 --- a/tests/Printer/examples/nullable1.test +++ b/tests/Printer/examples/nullable1.test @@ -2,6 +2,10 @@ * @var ?Foo */ --- -/** - * VarNode(NullableNode(ClassNode(Foo))) +Docblock: = + ElementList: = /** + * + VarTag: = @var + NullableNode: = ? + ClassNode: = Foo */ diff --git a/tests/Printer/examples/param1.test b/tests/Printer/examples/param1.test index ee64e32a..971f3a7e 100644 --- a/tests/Printer/examples/param1.test +++ b/tests/Printer/examples/param1.test @@ -2,6 +2,10 @@ * @param Foobar $foobar */ --- -/** - * ParamNode(ClassNode(Foobar),VariableNode($foobar)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + ClassNode: = Foobar + VariableNode: = $foobar */ diff --git a/tests/Printer/examples/param2.test b/tests/Printer/examples/param2.test index 2eb6c34d..25e0bf36 100644 --- a/tests/Printer/examples/param2.test +++ b/tests/Printer/examples/param2.test @@ -3,7 +3,14 @@ * @param string $barfoo */ --- -/** - * ParamNode(ClassNode(Foobar),VariableNode($foobar)) - * ParamNode(ScalarNode(string),VariableNode($barfoo)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + ClassNode: = Foobar + VariableNode: = $foobar + * + ParamTag: = @param + ScalarNode: = string + VariableNode: = $barfoo */ diff --git a/tests/Printer/examples/param3.test b/tests/Printer/examples/param3.test index eb7e6809..45cb617b 100644 --- a/tests/Printer/examples/param3.test +++ b/tests/Printer/examples/param3.test @@ -8,12 +8,22 @@ * @param bool $bool */ --- -/** +Docblock: = + ElementList: = /** * Hello World * - * ParamNode(ClassNode(Foobar),VariableNode($foobar)) + * + ParamTag: = @param + ClassNode: = Foobar + VariableNode: = $foobar * - * ParamNode(ScalarNode(string),VariableNode($barfoo)) + * + ParamTag: = @param + ScalarNode: = string + VariableNode: = $barfoo * - * ParamNode(ScalarNode(bool),VariableNode($bool)) + * + ParamTag: = @param + ScalarNode: = bool + VariableNode: = $bool */ diff --git a/tests/Printer/examples/param4.test b/tests/Printer/examples/param4.test index b189e757..bb1afcd6 100644 --- a/tests/Printer/examples/param4.test +++ b/tests/Printer/examples/param4.test @@ -2,6 +2,9 @@ * @param bool */ --- -/** - * ParamNode(ScalarNode(bool),#missing#) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + ScalarNode: = bool */ diff --git a/tests/Printer/examples/param5.test b/tests/Printer/examples/param5.test index 477e13bd..bf093d8a 100644 --- a/tests/Printer/examples/param5.test +++ b/tests/Printer/examples/param5.test @@ -2,6 +2,11 @@ * @param bool $bool This is a boolean */ --- -/** - * ParamNode(ScalarNode(bool),VariableNode($bool),TextNode(This is a boolean)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + ScalarNode: = bool + VariableNode: = $bool + TextNode: = This is a boolean */ diff --git a/tests/Printer/examples/param6.test b/tests/Printer/examples/param6.test index 5e3e4f75..2a0ff792 100644 --- a/tests/Printer/examples/param6.test +++ b/tests/Printer/examples/param6.test @@ -4,8 +4,17 @@ * @param bool $bool This is a boolean */ --- -/** - * ParamNode(ScalarNode(bool),VariableNode($bool),TextNode(This is a boolean)) +Docblock: = + ElementList: = /** + * + ParamTag: = @param + ScalarNode: = bool + VariableNode: = $bool + TextNode: = This is a boolean * multiline comments not currently supported - * ParamNode(ScalarNode(bool),VariableNode($bool),TextNode(This is a boolean)) + * + ParamTag: = @param + ScalarNode: = bool + VariableNode: = $bool + TextNode: = This is a boolean */ diff --git a/tests/Printer/examples/php_core1.test b/tests/Printer/examples/php_core1.test index e555336e..cdd3916a 100644 --- a/tests/Printer/examples/php_core1.test +++ b/tests/Printer/examples/php_core1.test @@ -24,10 +24,19 @@ * and the method name. */ --- -/** +Docblock: = + ElementList: = /** * Sets a user-defined error handler function - * UnknownTag https://php.net/manual/en/function.set-error-handler.php - * ParamNode(UnionNode(ScalarNode(callable)|NullNode()),VariableNode($error_handler),TextNode(

)) + * + UnknownTag: = https://php.net/manual/en/function.set-error-handler.php + * + ParamTag: = @param + UnionNode: = + TypeList: = + ScalarNode: = callable| + NullNode: = null + VariableNode: = $error_handler + TextNode: =

* The user function needs to accept two parameters... *

*

@@ -36,16 +45,27 @@ * errno * The first parameter, errno, contains the * level of the error raised, as an integer. - * ParamNode(ScalarNode(int),VariableNode($error_types),TextNode( [optional]

)) + * + ParamTag: = @param + ScalarNode: = int + VariableNode: = $error_types + TextNode: = [optional]

* Can be used to mask the triggering of the * error_handler function just like the error_reporting ini setting * controls which errors are shown. Without this mask set the * error_handler will be called for every error * regardless to the setting of the error_reporting setting. *

- * ReturnNode(UnionNode(ScalarNode(callable)|NullNode()),TextNode(a string containing the previously defined error handler (if any). If)) + * + ReturnTag: = @return + UnionNode: = + TypeList: = + ScalarNode: = callable| + NullNode: = null + TextNode: = a string containing the previously defined error handler (if any). If * the built-in error handler is used null is returned. null is also returned * in case of an error such as an invalid callback. If the previous error handler * was a class method, this function will return an indexed array with the class * and the method name. */ + \ No newline at end of file diff --git a/tests/Printer/examples/property1.test b/tests/Printer/examples/property1.test index d05b05b0..4dcc98f4 100644 --- a/tests/Printer/examples/property1.test +++ b/tests/Printer/examples/property1.test @@ -2,6 +2,8 @@ * @property string $foo */ --- -/** - * PropertyNode(ScalarNode(string),$foo) +Docblock: = + ElementList: = /** + * + PropertyTag: = */ diff --git a/tests/Printer/examples/return1.test b/tests/Printer/examples/return1.test index 9e8ab1db..573dc13e 100644 --- a/tests/Printer/examples/return1.test +++ b/tests/Printer/examples/return1.test @@ -2,6 +2,9 @@ * @return Foobar */ --- -/** - * ReturnNode(ClassNode(Foobar)) +Docblock: = + ElementList: = /** + * + ReturnTag: = @return + ClassNode: = Foobar */ diff --git a/tests/Printer/examples/text1.test b/tests/Printer/examples/text1.test index bf18af6d..b87fca2c 100644 --- a/tests/Printer/examples/text1.test +++ b/tests/Printer/examples/text1.test @@ -4,7 +4,8 @@ * This is some text */ --- -/** +Docblock: = + ElementList: = /** * Hello World * * This is some text diff --git a/tests/Printer/examples/union1.test b/tests/Printer/examples/union1.test index 379eba23..3e648291 100644 --- a/tests/Printer/examples/union1.test +++ b/tests/Printer/examples/union1.test @@ -1,3 +1,11 @@ /** @var string|bool|?Bar */ --- -/** VarNode(UnionNode(ScalarNode(string)|ScalarNode(bool)|NullableNode(ClassNode(Bar)))) */ +Docblock: = + ElementList: = /** + VarTag: = @var + UnionNode: = + TypeList: = + ScalarNode: = string| + ScalarNode: = bool| + NullableNode: = ? + ClassNode: = Bar */ diff --git a/tests/Printer/examples/var1.test b/tests/Printer/examples/var1.test index 5926e58d..52dbf287 100644 --- a/tests/Printer/examples/var1.test +++ b/tests/Printer/examples/var1.test @@ -2,6 +2,10 @@ * @var string[] */ --- -/** - * VarNode(ListNode(ScalarNode(string))) +Docblock: = + ElementList: = /** + * + VarTag: = @var + ListNode: = + ScalarNode: = string[] */ diff --git a/tests/Printer/examples/var2.test b/tests/Printer/examples/var2.test index 4c21c5b4..63e6b43b 100644 --- a/tests/Printer/examples/var2.test +++ b/tests/Printer/examples/var2.test @@ -1,3 +1,7 @@ /** @var string $foobar */ --- -/** VarNode(ScalarNode(string),VariableNode($foobar)) */ +Docblock: = + ElementList: = /** + VarTag: = @var + ScalarNode: = string + VariableNode: = $foobar */ diff --git a/tests/Printer/examples/var3.test b/tests/Printer/examples/var3.test index 4c21c5b4..63e6b43b 100644 --- a/tests/Printer/examples/var3.test +++ b/tests/Printer/examples/var3.test @@ -1,3 +1,7 @@ /** @var string $foobar */ --- -/** VarNode(ScalarNode(string),VariableNode($foobar)) */ +Docblock: = + ElementList: = /** + VarTag: = @var + ScalarNode: = string + VariableNode: = $foobar */ diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php index bdde0e85..53c74f5a 100644 --- a/tests/Unit/Ast/NodeTestCase.php +++ b/tests/Unit/Ast/NodeTestCase.php @@ -17,7 +17,7 @@ class NodeTestCase extends TestCase public function testNode(string $doc, ?Closure $assertion = null): void { $node = $this->parse($doc); - $nodes = iterator_to_array($node->getDescendantNodes(), false); + $nodes = iterator_to_array($node->getDescendantElements(), false); self::assertIsIterable($nodes); self::assertEquals(0, $node->start(), 'Start offset'); self::assertEquals(strlen($doc), $node->end(), 'End offset'); From 3bc0fbf754b72d0a0b0c24a09332d4c728f55469 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 20:29:36 +0000 Subject: [PATCH 52/63] Add property traversable support --- lib/Ast/Tag/PropertyTag.php | 28 ++++++++++++++------------- lib/Parser.php | 4 ++-- tests/Printer/examples/property1.test | 3 ++- tests/Unit/Ast/NodeTest.php | 1 + 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/Ast/Tag/PropertyTag.php b/lib/Ast/Tag/PropertyTag.php index 28e5f450..623a389c 100644 --- a/lib/Ast/Tag/PropertyTag.php +++ b/lib/Ast/Tag/PropertyTag.php @@ -8,29 +8,31 @@ class PropertyTag extends TagNode { + protected const CHILD_NAMES = [ + 'tag', + 'type', + 'name', + ]; + /** * @var TypeNode|null */ - private $type; + public $type; /** * @var Token|null */ - private $name; + public $name; - public function __construct(?TypeNode $type, ?Token $name) + /** + * @var Token + */ + public $tag; + + public function __construct(Token $tag, ?TypeNode $type, ?Token $name) { $this->type = $type; $this->name = $name; - } - - public function name(): ?Token - { - return $this->name; - } - - public function type(): ?TypeNode - { - return $this->type; + $this->tag = $tag; } } diff --git a/lib/Parser.php b/lib/Parser.php index 977a73f3..61207016 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -158,7 +158,7 @@ private function parseMethod(): MethodTag private function parseProperty(): PropertyTag { - $this->tokens->chomp(Token::T_TAG); + $tag = $this->tokens->chomp(Token::T_TAG); $type = $name = null; if ($this->ifType()) { $type = $this->parseTypes(); @@ -167,7 +167,7 @@ private function parseProperty(): PropertyTag $name = $this->tokens->chomp(); } - return new PropertyTag($type, $name); + return new PropertyTag($tag, $type, $name); } private function parseTypes(): ?TypeNode diff --git a/tests/Printer/examples/property1.test b/tests/Printer/examples/property1.test index 4dcc98f4..8842aa05 100644 --- a/tests/Printer/examples/property1.test +++ b/tests/Printer/examples/property1.test @@ -5,5 +5,6 @@ Docblock: = ElementList: = /** * - PropertyTag: = + PropertyTag: = @property + ScalarNode: = string$foo */ diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 05b9616e..9713b961 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -47,6 +47,7 @@ function (MethodTag $methodNode) { yield ['@param Baz\Bar $foobar This is a parameter']; yield ['@var Baz\Bar $foobar']; yield ['@return Baz\Bar']; + yield ['@property Baz\Bar $foobar']; } /** From 013368c85a906d473cce4da8e70d370de4c5e365 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 20:45:14 +0000 Subject: [PATCH 53/63] Improving API --- lib/Ast/Docblock.php | 9 --- lib/Ast/ElementList.php | 19 ------ lib/Ast/Node.php | 103 ++++++++++++++++++-------------- lib/Printer/TestPrinter.php | 2 +- tests/Unit/Ast/NodeTestCase.php | 4 +- 5 files changed, 60 insertions(+), 77 deletions(-) diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index bf63d68c..40e87431 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -20,13 +20,4 @@ public function __construct(array $children) { $this->children = new ElementList($children); } - - /** - * @return ElementList - */ - public function children(): ElementList - - { - return $this->children; - } } diff --git a/lib/Ast/ElementList.php b/lib/Ast/ElementList.php index 7e67f267..93d0480c 100644 --- a/lib/Ast/ElementList.php +++ b/lib/Ast/ElementList.php @@ -37,25 +37,6 @@ public function getIterator(): Iterator return new ArrayIterator($this->elements); } - /** - * @template T - * @param class-string $classFqn - * @return ElementList - */ - public function byClass(string $classFqn): ElementList - { - return new self(array_filter($this->elements, function (Element $element) use ($classFqn): bool { - return get_class($element) === $classFqn; - })); - } - - public function byName(string $name): ElementList - { - return new self(array_filter($this->elements, function (Element $element) use ($classFqn): bool { - return get_class($element) === $classFqn; - })); - } - /** * @return Element[] */ diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index a02ca72f..b2746f4f 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -26,31 +26,12 @@ public function toString(): string */ public function tokens(): Generator { - yield from $this->findTokens($this->getChildElements()); + yield from $this->findTokens($this->children()); } /** - * @return Generator - * @param iterable> $nodes + * Return the short name of the node class (e.g. ParamTag) */ - private function findTokens(iterable $nodes): Generator - { - foreach ($nodes as $node) { - if ($node instanceof Token) { - yield $node; - continue; - } - - if ($node instanceof Node) { - yield from $node->tokens(); - } - - if (is_array($node)) { - yield from $this->findTokens($node); - } - } - } - public function shortName(): string { return substr(get_class($this), strrpos(get_class($this), '\\') + 1); @@ -59,10 +40,18 @@ public function shortName(): string /** * @return Generator */ - public function getDescendantElements(): Generator + public function selfAndDescendantElements(): Generator { yield $this; - yield from $this->walkNodes($this->getChildElements()); + yield from $this->traverseNodes($this->children()); + } + + /** + * @return Generator + */ + public function descendantElements(): Generator + { + yield from $this->traverseNodes($this->children()); } /** @@ -70,17 +59,17 @@ public function getDescendantElements(): Generator * * @return Generator */ - private function walkNodes(iterable $nodes): Generator + private function traverseNodes(iterable $nodes): Generator { $result = []; foreach ($nodes as $child) { if (is_array($child)) { - yield from $this->walkNodes($child); + yield from $this->traverseNodes($child); continue; } if ($child instanceof Node) { - yield from $child->getDescendantElements(); + yield from $child->selfAndDescendantElements(); continue; } @@ -94,7 +83,7 @@ private function walkNodes(iterable $nodes): Generator /** * @return Generator */ - public function getChildElements(): Generator + public function children(): Generator { foreach (static::CHILD_NAMES as $name) { $child = $this->$name; @@ -106,29 +95,12 @@ public function getChildElements(): Generator public function start(): int { - return $this->startOf($this->getChildElements()); - } - - /** - * @param iterable> $elements - */ - public function startOf(iterable $elements): int - { - foreach ($elements as $element) { - if ($element instanceof Element) { - return $element->start(); - } - if (is_array($element)) { - return $this->startOf($element); - } - } - - return 0; + return $this->startOf($this->children()); } public function end(): int { - return $this->endOf(array_reverse(iterator_to_array($this->getChildElements(), false))); + return $this->endOf(array_reverse(iterator_to_array($this->children(), false))); } /** @@ -156,4 +128,43 @@ private function length(): int { return $this->end() - $this->start(); } + + /** + * @param iterable> $elements + */ + private function startOf(iterable $elements): int + { + foreach ($elements as $element) { + if ($element instanceof Element) { + return $element->start(); + } + if (is_array($element)) { + return $this->startOf($element); + } + } + + return 0; + } + + /** + * @return Generator + * @param iterable> $nodes + */ + private function findTokens(iterable $nodes): Generator + { + foreach ($nodes as $node) { + if ($node instanceof Token) { + yield $node; + continue; + } + + if ($node instanceof Node) { + yield from $node->tokens(); + } + + if (is_array($node)) { + yield from $this->findTokens($node); + } + } + } } diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index fb281ad7..79fa260f 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -42,7 +42,7 @@ public function print(Node $node): string { $this->indent++; $out = sprintf('%s: = ', $node->shortName()); - foreach ($node->getChildElements() as $child) { + foreach ($node->children() as $child) { $out .= $this->printElement($child); } $this->indent--; diff --git a/tests/Unit/Ast/NodeTestCase.php b/tests/Unit/Ast/NodeTestCase.php index 53c74f5a..85d36fe5 100644 --- a/tests/Unit/Ast/NodeTestCase.php +++ b/tests/Unit/Ast/NodeTestCase.php @@ -17,7 +17,7 @@ class NodeTestCase extends TestCase public function testNode(string $doc, ?Closure $assertion = null): void { $node = $this->parse($doc); - $nodes = iterator_to_array($node->getDescendantElements(), false); + $nodes = iterator_to_array($node->selfAndDescendantElements(), false); self::assertIsIterable($nodes); self::assertEquals(0, $node->start(), 'Start offset'); self::assertEquals(strlen($doc), $node->end(), 'End offset'); @@ -34,7 +34,7 @@ public function testPartialParse(string $doc): void { $node = $this->parse($doc); $partial = []; - foreach ($node->getChildElements() as $child) { + foreach ($node->children() as $child) { $partial[] = $child->toString(); $node = $this->parse(implode(' ', $partial)); self::assertInstanceOf(Element::class, $node); From 1384d0b9da4e1396eb17095988e0031a86d63e73 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 20:45:47 +0000 Subject: [PATCH 54/63] Apply CS fix --- lib/Ast/Node.php | 51 ++++++++++++++++++------------------- lib/Ast/TextNode.php | 2 -- lib/Ast/Token.php | 2 -- lib/Ast/Tokens.php | 1 - lib/Ast/TypeList.php | 1 - lib/Ast/TypeNode.php | 2 -- lib/Ast/UnknownTag.php | 2 -- lib/Ast/VariableNode.php | 2 -- lib/Printer/TestPrinter.php | 23 ----------------- tests/Unit/Ast/NodeTest.php | 12 +++------ 10 files changed, 29 insertions(+), 69 deletions(-) diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index b2746f4f..1ba65525 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -3,7 +3,6 @@ namespace Phpactor\Docblock\Ast; use Generator; -use Phpactor\Docblock\Ast\Token; abstract class Node implements Element { @@ -12,7 +11,8 @@ abstract class Node implements Element public function toString(): string { - $out = str_repeat(' ', $this->length());; + $out = str_repeat(' ', $this->length()); + ; $start = $this->start(); foreach ($this->tokens() as $token) { $out = substr_replace($out, $token->value, $token->start() - $start, $token->length()); @@ -54,6 +54,29 @@ public function descendantElements(): Generator yield from $this->traverseNodes($this->children()); } + /** + * @return Generator + */ + public function children(): Generator + { + foreach (static::CHILD_NAMES as $name) { + $child = $this->$name; + if (null !== $child) { + yield $child; + } + } + } + + public function start(): int + { + return $this->startOf($this->children()); + } + + public function end(): int + { + return $this->endOf(array_reverse(iterator_to_array($this->children(), false))); + } + /** * @param iterable> $nodes * @@ -80,36 +103,12 @@ private function traverseNodes(iterable $nodes): Generator } } - /** - * @return Generator - */ - public function children(): Generator - { - foreach (static::CHILD_NAMES as $name) { - $child = $this->$name; - if (null !== $child) { - yield $child; - } - } - } - - public function start(): int - { - return $this->startOf($this->children()); - } - - public function end(): int - { - return $this->endOf(array_reverse(iterator_to_array($this->children(), false))); - } - /** * @param iterable> $elements */ private function endOf(iterable $elements): int { foreach ($elements as $element) { - if (null === $element) { continue; } diff --git a/lib/Ast/TextNode.php b/lib/Ast/TextNode.php index a09d8c78..4168c69f 100644 --- a/lib/Ast/TextNode.php +++ b/lib/Ast/TextNode.php @@ -2,8 +2,6 @@ namespace Phpactor\Docblock\Ast; -use Phpactor\Docblock\Ast\Token; - class TextNode extends Node { protected const CHILD_NAMES = [ diff --git a/lib/Ast/Token.php b/lib/Ast/Token.php index 89bbb3bd..15cf7588 100644 --- a/lib/Ast/Token.php +++ b/lib/Ast/Token.php @@ -2,8 +2,6 @@ namespace Phpactor\Docblock\Ast; -use Phpactor\Docblock\Ast\Element; - final class Token implements Element { public const T_PHPDOC_OPEN = 'PHPDOC_OPEN'; diff --git a/lib/Ast/Tokens.php b/lib/Ast/Tokens.php index 6db6d5b5..9a697ad6 100644 --- a/lib/Ast/Tokens.php +++ b/lib/Ast/Tokens.php @@ -5,7 +5,6 @@ use ArrayIterator; use IteratorAggregate; use RuntimeException; -use Phpactor\Docblock\Ast\Token; final class Tokens implements IteratorAggregate { diff --git a/lib/Ast/TypeList.php b/lib/Ast/TypeList.php index 220dc1b7..a3d1d8d4 100644 --- a/lib/Ast/TypeList.php +++ b/lib/Ast/TypeList.php @@ -5,7 +5,6 @@ use ArrayIterator; use Countable; use IteratorAggregate; -use Phpactor\Docblock\Ast\Token; use RuntimeException; /** diff --git a/lib/Ast/TypeNode.php b/lib/Ast/TypeNode.php index a6f58279..4cefe3ea 100644 --- a/lib/Ast/TypeNode.php +++ b/lib/Ast/TypeNode.php @@ -2,8 +2,6 @@ namespace Phpactor\Docblock\Ast; -use Phpactor\Docblock\Ast\Token; - abstract class TypeNode extends Node { } diff --git a/lib/Ast/UnknownTag.php b/lib/Ast/UnknownTag.php index f16436e7..6ca6fefc 100644 --- a/lib/Ast/UnknownTag.php +++ b/lib/Ast/UnknownTag.php @@ -2,8 +2,6 @@ namespace Phpactor\Docblock\Ast; -use Phpactor\Docblock\Ast\Token; - class UnknownTag extends TagNode { /** diff --git a/lib/Ast/VariableNode.php b/lib/Ast/VariableNode.php index 846f6882..f53301f5 100644 --- a/lib/Ast/VariableNode.php +++ b/lib/Ast/VariableNode.php @@ -2,8 +2,6 @@ namespace Phpactor\Docblock\Ast; -use Phpactor\Docblock\Ast\Token; - class VariableNode extends Node { protected const CHILD_NAMES = [ diff --git a/lib/Printer/TestPrinter.php b/lib/Printer/TestPrinter.php index 79fa260f..90fdd8fe 100644 --- a/lib/Printer/TestPrinter.php +++ b/lib/Printer/TestPrinter.php @@ -2,32 +2,10 @@ namespace Phpactor\Docblock\Printer; -use Phpactor\Docblock\Ast\Tag\DeprecatedTag; -use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Element; -use Phpactor\Docblock\Ast\Tag\MethodTag; -use Phpactor\Docblock\Ast\Tag\MixinTag; -use Phpactor\Docblock\Ast\ParameterList; -use Phpactor\Docblock\Ast\Tag\ParameterTag; -use Phpactor\Docblock\Ast\Tag\PropertyTag; -use Phpactor\Docblock\Ast\Tag\ReturnTag; -use Phpactor\Docblock\Ast\TextNode; -use Phpactor\Docblock\Ast\TypeList; -use Phpactor\Docblock\Ast\TypeNode; use Phpactor\Docblock\Ast\Node; -use Phpactor\Docblock\Ast\Tag\ParamTag; -use Phpactor\Docblock\Ast\Type\GenericNode; -use Phpactor\Docblock\Ast\Type\ListNode; -use Phpactor\Docblock\Ast\Type\NullNode; -use Phpactor\Docblock\Ast\Type\NullableNode; -use Phpactor\Docblock\Ast\Type\UnionNode; -use Phpactor\Docblock\Ast\UnknownTag; -use Phpactor\Docblock\Ast\ValueNode; -use Phpactor\Docblock\Ast\Tag\VarTag; -use Phpactor\Docblock\Ast\VariableNode; use Phpactor\Docblock\Printer; use Phpactor\Docblock\Ast\Token; -use RuntimeException; final class TestPrinter implements Printer { @@ -72,5 +50,4 @@ private function newLine(): string { return "\n".str_repeat(' ', $this->indent); } - } diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 9713b961..e371a2e9 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -3,10 +3,7 @@ namespace Phpactor\Docblock\Tests\Unit\Ast; use Generator; -use PHPUnit\Framework\TestCase; use Phpactor\Docblock\Ast\Tag\MethodTag; -use Phpactor\Docblock\Ast\Node; -use Phpactor\Docblock\Ast\Tag\ParamTag; use Phpactor\Docblock\Ast\Tag\ReturnTag; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; @@ -34,7 +31,7 @@ private function provideTags() yield [ '@param string $foo This is a parameter']; yield [ '@method static Baz\Bar bar(string $boo, string $baz)', - function (MethodTag $methodNode) { + function (MethodTag $methodNode): void { self::assertEquals('@method static Baz\Bar bar(string $boo, string $baz)', $methodNode->toString()); self::assertEquals('string $boo, string $baz', $methodNode->parameters->toString()); self::assertEquals('static', $methodNode->static->value); @@ -58,7 +55,7 @@ private function provideTypes(): Generator yield 'scalar' => ['string']; yield 'union' => [ '@return string|int|bool|float|mixed', - function (ReturnTag $return) { + function (ReturnTag $return): void { $type = $return->type; assert($type instanceof UnionNode); self::assertInstanceOf(UnionNode::class, $type); @@ -68,16 +65,15 @@ function (ReturnTag $return) { ]; yield 'list' => [ '@return Foo[]', - function (ReturnTag $return) { + function (ReturnTag $return): void { self::assertInstanceOf(ListNode::class, $return->type); } ]; yield 'generic' => [ '@return Foo, Baz|Bar>', - function (ReturnTag $return) { + function (ReturnTag $return): void { self::assertInstanceOf(GenericNode::class, $return->type); } ]; } - } From aa7e948d727f070f810edb95eb0b92cabb2b2600 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 21:05:51 +0000 Subject: [PATCH 55/63] Added more API methods --- lib/Ast/Node.php | 69 ++++++++++++++++++++++++++++++++++--- tests/Unit/Ast/NodeTest.php | 34 +++++++++++++++--- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index 1ba65525..fe44c695 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -49,34 +49,95 @@ public function selfAndDescendantElements(): Generator /** * @return Generator */ - public function descendantElements(): Generator + public function descendantElements(?string $elementFqn = null): Generator { - yield from $this->traverseNodes($this->children()); + if (null === $elementFqn) { + yield from $this->traverseNodes($this->children()); + return; + } + + foreach ($this->traverseNodes($this->children()) as $element) { + if ($element instanceof $elementFqn) { + yield $element; + } + } + } + + public function hasDescendant(string $elementFqn): bool + { + foreach ($this->descendantElements($elementFqn) as $element) { + return true; + } + + return false; } /** + * @template T of Element + * @param class-string $elementFqn + * @return T|null + */ + public function firstDescendant(string $elementFqn): ?Element + { + foreach ($this->descendantElements($elementFqn) as $element) { + return $element; + } + + return null; + } + + /** + * @param class-string $elementFqn * @return Generator */ - public function children(): Generator + public function children(?string $elementFqn = null): Generator { + if (!$elementFqn) { + foreach (static::CHILD_NAMES as $name) { + $child = $this->$name; + if (null !== $child) { + yield $child; + } + } + + return; + } + foreach (static::CHILD_NAMES as $name) { $child = $this->$name; - if (null !== $child) { + if ($child instanceof $elementFqn) { yield $child; } } } + /** + * Return the bytes offset for the start of this node. + */ public function start(): int { return $this->startOf($this->children()); } + /** + * Return the bytes offset for the end of this node. + */ public function end(): int { return $this->endOf(array_reverse(iterator_to_array($this->children(), false))); } + public function hasChild(string $elementFqn): bool + { + foreach ($this->children() as $child) { + if ($child instanceof $elementFqn) { + return true; + } + } + + return false; + } + /** * @param iterable> $nodes * diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index e371a2e9..01f1e9d3 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -5,9 +5,12 @@ use Generator; use Phpactor\Docblock\Ast\Tag\MethodTag; use Phpactor\Docblock\Ast\Tag\ReturnTag; +use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Type\GenericNode; use Phpactor\Docblock\Ast\Type\ListNode; +use Phpactor\Docblock\Ast\Type\ScalarNode; use Phpactor\Docblock\Ast\Type\UnionNode; +use Prophecy\Doubler\Generator\Node\MethodNode; class NodeTest extends NodeTestCase { @@ -16,19 +19,36 @@ class NodeTest extends NodeTestCase */ public function provideNode(): Generator { + yield from $this->provideApiTest(); yield from $this->provideTags(); yield from $this->provideTypes(); } + /** + * @return Generator + */ + private function provideApiTest(): Generator + { + yield [ + '@method static Baz\Bar bar(string $boo, string $baz)', + function (MethodTag $methodNode): void { + self::assertTrue($methodNode->hasChild(ClassNode::class)); + self::assertFalse($methodNode->hasChild(MethodTag::class)); + self::assertCount(7, iterator_to_array($methodNode->children())); + self::assertCount(1, iterator_to_array($methodNode->children(ClassNode::class))); + self::assertTrue($methodNode->hasDescendant(ScalarNode::class)); + self::assertFalse($methodNode->hasDescendant(MethodNode::class)); + self::assertCount(2, iterator_to_array($methodNode->descendantElements(ScalarNode::class))); + self::assertInstanceOf(ScalarNode::class, $methodNode->firstDescendant(ScalarNode::class)); + } + ]; + } + /** * @return Generator */ private function provideTags() { - yield [ '@deprecated This is deprecated']; - yield [ '/** This is docblock @deprecated Foo */']; - yield [ '@mixin Foo\Bar']; - yield [ '@param string $foo This is a parameter']; yield [ '@method static Baz\Bar bar(string $boo, string $baz)', function (MethodTag $methodNode): void { @@ -39,8 +59,14 @@ function (MethodTag $methodNode): void { self::assertEquals('bar', $methodNode->name->toString()); self::assertEquals('(', $methodNode->parenOpen->toString()); self::assertEquals(')', $methodNode->parenClose->toString()); + self::assertTrue($methodNode->hasChild(ClassNode::class)); + self::assertFalse($methodNode->hasChild(MethodTag::class)); } ]; + yield [ '@deprecated This is deprecated']; + yield [ '/** This is docblock @deprecated Foo */']; + yield [ '@mixin Foo\Bar']; + yield [ '@param string $foo This is a parameter']; yield ['@param Baz\Bar $foobar This is a parameter']; yield ['@var Baz\Bar $foobar']; yield ['@return Baz\Bar']; From 878745c0744a908ccbcb1750548bae50b2a784e1 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 21:07:20 +0000 Subject: [PATCH 56/63] Updated composer, removed baseline --- composer.json | 9 +- lsp-client.log | 437 ++++++++++++++++++++++++++++++++++++ phpstan-baseline.neon | 387 ------------------------------- phpstan.neon | 2 - test.php | 4 + tests/Unit/Ast/NodeTest.php | 2 +- 6 files changed, 450 insertions(+), 391 deletions(-) create mode 100644 lsp-client.log delete mode 100644 phpstan-baseline.neon create mode 100644 test.php diff --git a/composer.json b/composer.json index bd7deaf2..d15867d4 100644 --- a/composer.json +++ b/composer.json @@ -39,5 +39,12 @@ } }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "scripts": { + "integrate": [ + "vendor/bin/phpunit", + "vendor/bin/php-cs-fixer fix --allow-risky=yes", + "vendor/bin/phpstan analyse" + ] + } } diff --git a/lsp-client.log b/lsp-client.log new file mode 100644 index 00000000..c0eb594a --- /dev/null +++ b/lsp-client.log @@ -0,0 +1,437 @@ +####### +LanguageClient 0.1.120 +####### +12:36:37 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[!!get(g:, 'LanguageClient_autoStart', 1), get(g:, 'LanguageClient_serverCommands', {}), get(g:, 'LanguageClient_selectionUI', v:null), get(g:, 'LanguageClient_trace', v:null), expand(get(g:, 'LanguageClient_settingsPath', '.vim/settings.json')), !!get(g:, 'LanguageClient_loadSettings', 1), get(g:, 'LanguageClient_rootMarkers', v:null), get(g:, 'LanguageClient_changeThrottle', v:null), get(g:, 'LanguageClient_waitOutputTimeout', v:null), !!get(g:, 'LanguageClient_diagnosticsEnable', 1), get(g:, 'LanguageClient_diagnosticsList', 'Quickfix'), get(g:, 'LanguageClient_diagnosticsDisplay', {}), get(g:, 'LanguageClient_windowLogMessageLevel', 'Warning'), get(g:, 'LanguageClient_hoverPreview', 'Auto'), get(g:, 'LanguageClient_completionPreferTextEdit', 0), has('nvim')]"],"id":41} +12:36:37 INFO reader-main src/vim.rs:380 <= None {"id": 41, "jsonrpc": "2.0", "result": [1, {}, null, null, ".vim/settings.json", 1, null, null, null, 1, "Quickfix", {}, "Warning", "Auto", 1, 1]} +12:36:37 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[get(g:, 'LanguageClient_diagnosticsSignsMax', v:null), get(g:, 'LanguageClient_documentHighlightDisplay', {})]"],"id":42} +12:36:37 INFO reader-main src/vim.rs:380 <= None {"id": 42, "jsonrpc": "2.0", "result": [null, {}]} +12:36:37 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["get(g:, 'loaded_fzf')"],"id":43} +12:36:37 INFO reader-main src/vim.rs:380 <= None {"id": 43, "jsonrpc": "2.0", "result": 1} +12:36:37 WARN main src/languageclient.rs:2173 Failed to start language server automatically. No language server command found for file type: markdown. +12:36:37 INFO main src/languageclient.rs:2178 End languageClient/handleBufReadPost +12:36:37 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:37 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(0)] +12:36:37 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:37 INFO main src/languageclient.rs:2283 Updating signs: [] +12:36:37 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"execute","params":[],"id":44} +12:36:37 INFO reader-main src/vim.rs:380 <= None {"id": 44, "jsonrpc": "2.0", "result": 0} +12:36:37 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"nvim_buf_clear_highlight","params":[0,1,0,22]} +12:36:37 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"s:AddHighlights","params":[1,[]]} +12:36:37 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:37 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 1, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:37 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:37 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(1)] +12:36:37 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:37 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 2, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(2)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 3, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(3)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 4, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(4)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 5, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(5)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 6, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(6)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 7, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(7)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 8, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(8)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:36:38 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 9, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:36:38 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:36:38 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(9)] +12:36:38 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:36:38 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:37:06 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 9, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:37:06 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:37:06 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(9)] +12:37:06 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:37:06 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:37:06 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 10, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:37:06 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:37:06 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(10)] +12:37:06 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:37:06 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:37:07 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 11, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +12:37:07 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:37:07 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(11)] +12:37:07 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +12:37:07 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:29 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 2, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:29 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:29 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(2)] +12:47:29 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:29 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:30 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 1, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:30 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:30 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(1)] +12:47:30 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:30 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:31 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 2, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:31 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:31 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(2)] +12:47:31 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:31 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 3, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(3)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 4, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(4)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 5, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(5)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 6, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(6)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 7, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(7)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 8, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(8)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 9, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(9)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 10, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(10)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 11, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(11)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 12, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(12)] +12:47:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:33 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 13, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:33 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:33 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(13)] +12:47:33 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:33 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:33 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 14, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:33 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:33 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(14)] +12:47:33 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:33 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:33 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 13, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:33 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:33 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(13)] +12:47:33 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:33 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 14, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(14)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 15, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(15)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 16, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(16)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 17, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(17)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 18, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(18)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 19, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(19)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 20, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(20)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:34 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 21, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:34 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:34 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(21)] +12:47:34 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:34 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 22, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(22)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 23, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(23)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 24, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(24)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 25, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(25)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 26, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(26)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 27, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(27)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 28, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(28)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 29, "LSP#visible_line_start()": 1, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(29)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(1), Number(29)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 28, "LSP#visible_line_start()": 1, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(28)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(1), Number(29)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 27, "LSP#visible_line_start()": 1, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:35 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(27)] +12:47:35 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(1), Number(29)] +12:47:35 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +12:47:36 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 26, "LSP#visible_line_start()": 1, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +12:47:36 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +12:47:36 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(26)] +12:47:36 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(1), Number(29)] +12:47:36 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:00 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 26, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:00 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:00 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(26)] +21:05:00 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(29)] +21:05:00 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:02 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 25, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:02 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:02 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(25)] +21:05:02 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:02 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:02 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 24, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:02 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:02 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(24)] +21:05:02 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:02 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:06 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 24, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:06 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:06 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(24)] +21:05:06 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:06 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:06 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 25, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:06 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:06 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(25)] +21:05:06 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:06 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:07 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 26, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:07 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:07 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(26)] +21:05:07 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:07 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:09 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:09 INFO main src/languageclient.rs:2183 Begin languageClient/handleTextChanged +21:05:09 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:09 INFO main src/languageclient.rs:1760 Begin textDocument/didChange +21:05:09 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:09 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["text"]. Exps: ["LSP#text()"] +21:05:09 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#text()]"],"id":45} +21:05:09 INFO reader-main src/vim.rs:380 <= None {"id": 45, "jsonrpc": "2.0", "result": [["{", " \"name\": \"phpactor/docblock\",", " \"description\": \"Simple Docblock Parser\",", " \"type\": \"library\",", " \"autoload\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\\": \"lib/\"", " }", " },", " \"autoload-dev\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"", " }", " },", " \"require-dev\": {", " \"phpunit/phpunit\": \"^6.5\"", " },", " \"authors\": [", " {", " \"name\": \"Daniel Leech\",", " \"email\": \"daniel@dantleech.com\"", " }", " ],", " \"license\": \"MIT\",", " \"extra\": {", " \"branch-alias\": {", " \"dev-master\": \"\"", " }", " }", "}", ""]]} +21:05:09 INFO main src/languageclient.rs:48 gather_args: [Text] = [Array([String("{"), String(" \"name\": \"phpactor/docblock\","), String(" \"description\": \"Simple Docblock Parser\","), String(" \"type\": \"library\","), String(" \"autoload\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\\": \"lib/\""), String(" }"), String(" },"), String(" \"autoload-dev\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\""), String(" }"), String(" },"), String(" \"require-dev\": {"), String(" \"phpunit/phpunit\": \"^6.5\""), String(" },"), String(" \"authors\": ["), String(" {"), String(" \"name\": \"Daniel Leech\","), String(" \"email\": \"daniel@dantleech.com\""), String(" }"), String(" ],"), String(" \"license\": \"MIT\","), String(" \"extra\": {"), String(" \"branch-alias\": {"), String(" \"dev-master\": \"\""), String(" }"), String(" }"), String("}"), String("")])] +21:05:09 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.text: "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"1.0.x-dev\"\n }\n }\n}\n" ==> "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"\"\n }\n }\n}\n" +21:05:09 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.version: 13 ==> 14 +21:05:09 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"text":"{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"\"\n }\n }\n}\n"}],"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json","version":14}}} +21:05:09 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:09 INFO main src/languageclient.rs:2183 Begin languageClient/handleTextChanged +21:05:09 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:09 INFO main src/languageclient.rs:1760 Begin textDocument/didChange +21:05:09 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:09 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["text"]. Exps: ["LSP#text()"] +21:05:09 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#text()]"],"id":46} +21:05:09 INFO reader-main src/vim.rs:380 <= None {"id": 46, "jsonrpc": "2.0", "result": [["{", " \"name\": \"phpactor/docblock\",", " \"description\": \"Simple Docblock Parser\",", " \"type\": \"library\",", " \"autoload\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\\": \"lib/\"", " }", " },", " \"autoload-dev\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"", " }", " },", " \"require-dev\": {", " \"phpunit/phpunit\": \"^6.5\"", " },", " \"authors\": [", " {", " \"name\": \"Daniel Leech\",", " \"email\": \"daniel@dantleech.com\"", " }", " ],", " \"license\": \"MIT\",", " \"extra\": {", " \"branch-alias\": {", " \"dev-master\": \"0\"", " }", " }", "}", ""]]} +21:05:09 INFO main src/languageclient.rs:48 gather_args: [Text] = [Array([String("{"), String(" \"name\": \"phpactor/docblock\","), String(" \"description\": \"Simple Docblock Parser\","), String(" \"type\": \"library\","), String(" \"autoload\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\\": \"lib/\""), String(" }"), String(" },"), String(" \"autoload-dev\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\""), String(" }"), String(" },"), String(" \"require-dev\": {"), String(" \"phpunit/phpunit\": \"^6.5\""), String(" },"), String(" \"authors\": ["), String(" {"), String(" \"name\": \"Daniel Leech\","), String(" \"email\": \"daniel@dantleech.com\""), String(" }"), String(" ],"), String(" \"license\": \"MIT\","), String(" \"extra\": {"), String(" \"branch-alias\": {"), String(" \"dev-master\": \"0\""), String(" }"), String(" }"), String("}"), String("")])] +21:05:09 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.text: "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"\"\n }\n }\n}\n" ==> "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"0\"\n }\n }\n}\n" +21:05:09 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.version: 14 ==> 15 +21:05:09 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"text":"{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"0\"\n }\n }\n}\n"}],"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json","version":15}}} +21:05:11 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:11 INFO main src/languageclient.rs:2183 Begin languageClient/handleTextChanged +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:11 INFO main src/languageclient.rs:1760 Begin textDocument/didChange +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:11 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["text"]. Exps: ["LSP#text()"] +21:05:11 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#text()]"],"id":47} +21:05:11 INFO reader-main src/vim.rs:380 <= None {"id": 47, "jsonrpc": "2.0", "result": [["{", " \"name\": \"phpactor/docblock\",", " \"description\": \"Simple Docblock Parser\",", " \"type\": \"library\",", " \"autoload\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\\": \"lib/\"", " }", " },", " \"autoload-dev\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"", " }", " },", " \"require-dev\": {", " \"phpunit/phpunit\": \"^6.5\"", " },", " \"authors\": [", " {", " \"name\": \"Daniel Leech\",", " \"email\": \"daniel@dantleech.com\"", " }", " ],", " \"license\": \"MIT\",", " \"extra\": {", " \"branch-alias\": {", " \"dev-master\": \"\"", " }", " }", "}", ""]]} +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Text] = [Array([String("{"), String(" \"name\": \"phpactor/docblock\","), String(" \"description\": \"Simple Docblock Parser\","), String(" \"type\": \"library\","), String(" \"autoload\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\\": \"lib/\""), String(" }"), String(" },"), String(" \"autoload-dev\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\""), String(" }"), String(" },"), String(" \"require-dev\": {"), String(" \"phpunit/phpunit\": \"^6.5\""), String(" },"), String(" \"authors\": ["), String(" {"), String(" \"name\": \"Daniel Leech\","), String(" \"email\": \"daniel@dantleech.com\""), String(" }"), String(" ],"), String(" \"license\": \"MIT\","), String(" \"extra\": {"), String(" \"branch-alias\": {"), String(" \"dev-master\": \"\""), String(" }"), String(" }"), String("}"), String("")])] +21:05:11 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.text: "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"0\"\n }\n }\n}\n" ==> "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"\"\n }\n }\n}\n" +21:05:11 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.version: 15 ==> 16 +21:05:11 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"text":"{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"\"\n }\n }\n}\n"}],"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json","version":16}}} +21:05:11 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:11 INFO main src/languageclient.rs:2183 Begin languageClient/handleTextChanged +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:11 INFO main src/languageclient.rs:1760 Begin textDocument/didChange +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:11 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["text"]. Exps: ["LSP#text()"] +21:05:11 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#text()]"],"id":48} +21:05:11 INFO reader-main src/vim.rs:380 <= None {"id": 48, "jsonrpc": "2.0", "result": [["{", " \"name\": \"phpactor/docblock\",", " \"description\": \"Simple Docblock Parser\",", " \"type\": \"library\",", " \"autoload\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\\": \"lib/\"", " }", " },", " \"autoload-dev\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"", " }", " },", " \"require-dev\": {", " \"phpunit/phpunit\": \"^6.5\"", " },", " \"authors\": [", " {", " \"name\": \"Daniel Leech\",", " \"email\": \"daniel@dantleech.com\"", " }", " ],", " \"license\": \"MIT\",", " \"extra\": {", " \"branch-alias\": {", " \"dev-master\": \"1.0.x-dev\"", " }", " }", "}", ""]]} +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Text] = [Array([String("{"), String(" \"name\": \"phpactor/docblock\","), String(" \"description\": \"Simple Docblock Parser\","), String(" \"type\": \"library\","), String(" \"autoload\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\\": \"lib/\""), String(" }"), String(" },"), String(" \"autoload-dev\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\""), String(" }"), String(" },"), String(" \"require-dev\": {"), String(" \"phpunit/phpunit\": \"^6.5\""), String(" },"), String(" \"authors\": ["), String(" {"), String(" \"name\": \"Daniel Leech\","), String(" \"email\": \"daniel@dantleech.com\""), String(" }"), String(" ],"), String(" \"license\": \"MIT\","), String(" \"extra\": {"), String(" \"branch-alias\": {"), String(" \"dev-master\": \"1.0.x-dev\""), String(" }"), String(" }"), String("}"), String("")])] +21:05:11 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.version: 16 ==> 17 +21:05:11 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.text: "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"\"\n }\n }\n}\n" ==> "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"1.0.x-dev\"\n }\n }\n}\n" +21:05:11 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"text":"{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"1.0.x-dev\"\n }\n }\n}\n"}],"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json","version":17}}} +21:05:11 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleBufWritePost", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:11 INFO main src/languageclient.rs:2213 Begin languageClient/handleBufWritePost +21:05:11 INFO main src/languageclient.rs:1830 Begin textDocument/didSave +21:05:11 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:11 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didSave","params":{"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json"}}} +21:05:14 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 25, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:14 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:14 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(25)] +21:05:14 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:14 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:14 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 24, "LSP#visible_line_start()": 12, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:14 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:14 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(24)] +21:05:14 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(12), Number(29)] +21:05:14 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:15 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleBufWritePost", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:15 INFO main src/languageclient.rs:2213 Begin languageClient/handleBufWritePost +21:05:15 INFO main src/languageclient.rs:1830 Begin textDocument/didSave +21:05:15 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:15 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didSave","params":{"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json"}}} +21:05:15 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 23, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:15 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:15 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(23)] +21:05:15 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(29)] +21:05:15 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:17 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 24, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:17 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:17 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(24)] +21:05:17 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(29)] +21:05:17 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:17 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 25, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:17 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:17 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(25)] +21:05:17 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(29)] +21:05:17 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 26, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(26)] +21:05:18 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(29)] +21:05:18 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 27, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(27)] +21:05:18 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(29)] +21:05:18 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 28, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 29, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(28)] +21:05:18 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(29)] +21:05:18 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 24, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 24, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(24)] +21:05:18 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(24)] +21:05:18 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2183 Begin languageClient/handleTextChanged +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:18 INFO main src/languageclient.rs:1760 Begin textDocument/didChange +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:18 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["text"]. Exps: ["LSP#text()"] +21:05:18 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#text()]"],"id":49} +21:05:18 INFO reader-main src/vim.rs:380 <= None {"id": 49, "jsonrpc": "2.0", "result": [["{", " \"name\": \"phpactor/docblock\",", " \"description\": \"Simple Docblock Parser\",", " \"type\": \"library\",", " \"autoload\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\\": \"lib/\"", " }", " },", " \"autoload-dev\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"", " }", " },", " \"require-dev\": {", " \"phpunit/phpunit\": \"^6.5\"", " },", " \"authors\": [", " {", " \"name\": \"Daniel Leech\",", " \"email\": \"daniel@dantleech.com\"", " }", " ],", " \"license\": \"MIT\",", "}", ""]]} +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Text] = [Array([String("{"), String(" \"name\": \"phpactor/docblock\","), String(" \"description\": \"Simple Docblock Parser\","), String(" \"type\": \"library\","), String(" \"autoload\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\\": \"lib/\""), String(" }"), String(" },"), String(" \"autoload-dev\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\""), String(" }"), String(" },"), String(" \"require-dev\": {"), String(" \"phpunit/phpunit\": \"^6.5\""), String(" },"), String(" \"authors\": ["), String(" {"), String(" \"name\": \"Daniel Leech\","), String(" \"email\": \"daniel@dantleech.com\""), String(" }"), String(" ],"), String(" \"license\": \"MIT\","), String("}"), String("")])] +21:05:18 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.version: 17 ==> 18 +21:05:18 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.text: "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"1.0.x-dev\"\n }\n }\n}\n" ==> "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n}\n" +21:05:18 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"text":"{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n}\n"}],"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json","version":18}}} +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 23, "LSP#visible_line_start()": 10, "LSP#visible_line_end()": 24, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(23)] +21:05:18 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(10), Number(24)] +21:05:18 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:05:18 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:18 INFO main src/languageclient.rs:2183 Begin languageClient/handleTextChanged +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:18 INFO main src/languageclient.rs:1760 Begin textDocument/didChange +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:18 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["text"]. Exps: ["LSP#text()"] +21:05:18 INFO main src/vim.rs:92 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#text()]"],"id":50} +21:05:18 INFO reader-main src/vim.rs:380 <= None {"id": 50, "jsonrpc": "2.0", "result": [["{", " \"name\": \"phpactor/docblock\",", " \"description\": \"Simple Docblock Parser\",", " \"type\": \"library\",", " \"autoload\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\\": \"lib/\"", " }", " },", " \"autoload-dev\": {", " \"psr-4\": {", " \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"", " }", " },", " \"require-dev\": {", " \"phpunit/phpunit\": \"^6.5\"", " },", " \"authors\": [", " {", " \"name\": \"Daniel Leech\",", " \"email\": \"daniel@dantleech.com\"", " }", " ],", " \"license\": \"MIT\"", "}", ""]]} +21:05:18 INFO main src/languageclient.rs:48 gather_args: [Text] = [Array([String("{"), String(" \"name\": \"phpactor/docblock\","), String(" \"description\": \"Simple Docblock Parser\","), String(" \"type\": \"library\","), String(" \"autoload\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\\": \"lib/\""), String(" }"), String(" },"), String(" \"autoload-dev\": {"), String(" \"psr-4\": {"), String(" \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\""), String(" }"), String(" },"), String(" \"require-dev\": {"), String(" \"phpunit/phpunit\": \"^6.5\""), String(" },"), String(" \"authors\": ["), String(" {"), String(" \"name\": \"Daniel Leech\","), String(" \"email\": \"daniel@dantleech.com\""), String(" }"), String(" ],"), String(" \"license\": \"MIT\""), String("}"), String("")])] +21:05:18 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.text: "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\",\n}\n" ==> "{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\"\n}\n" +21:05:18 DEBUG main src/vim.rs:320 state.text_documents./home/daniel/www/phpactor/docblock/composer.json.version: 18 ==> 19 +21:05:18 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"text":"{\n \"name\": \"phpactor/docblock\",\n \"description\": \"Simple Docblock Parser\",\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\\": \"lib/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Phpactor\\\\Docblock\\\\Tests\\\\\": \"tests/\"\n }\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^6.5\"\n },\n \"authors\": [\n {\n \"name\": \"Daniel Leech\",\n \"email\": \"daniel@dantleech.com\"\n }\n ],\n \"license\": \"MIT\"\n}\n"}],"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json","version":19}}} +21:05:19 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleBufWritePost", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:05:19 INFO main src/languageclient.rs:2213 Begin languageClient/handleBufWritePost +21:05:19 INFO main src/languageclient.rs:1830 Begin textDocument/didSave +21:05:19 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:05:19 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didSave","params":{"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json"}}} +21:05:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "markdown", "line": 11, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 22, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/README.md"}} +21:05:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:05:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/README.md"), Number(11)] +21:05:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(22)] +21:05:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:07:32 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "json", "line": 23, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 24, "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:07:32 INFO main src/languageclient.rs:2235 Begin languageClient/handleCursorMoved +21:07:32 INFO main src/languageclient.rs:48 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/daniel/www/phpactor/docblock/composer.json"), Number(23)] +21:07:32 INFO main src/languageclient.rs:48 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(24)] +21:07:32 INFO main src/languageclient.rs:2325 End languageClient/handleCursorMoved +21:07:35 INFO reader-main src/vim.rs:380 <= None {"method": "languageClient/handleBufWritePost", "jsonrpc": "2.0", "params": {"languageId": "json", "buftype": "", "filename": "/home/daniel/www/phpactor/docblock/composer.json"}} +21:07:35 INFO main src/languageclient.rs:2213 Begin languageClient/handleBufWritePost +21:07:35 INFO main src/languageclient.rs:1830 Begin textDocument/didSave +21:07:35 INFO main src/languageclient.rs:48 gather_args: [Buftype, LanguageId, Filename] = [String(""), String("json"), String("/home/daniel/www/phpactor/docblock/composer.json")] +21:07:35 INFO main src/vim.rs:92 => Some("json") {"jsonrpc":"2.0","method":"textDocument/didSave","params":{"textDocument":{"uri":"file:///home/daniel/www/phpactor/docblock/composer.json"}}} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon deleted file mode 100644 index 54238413..00000000 --- a/phpstan-baseline.neon +++ /dev/null @@ -1,387 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Property Phpactor\\\\Docblock\\\\DefaultValue\\:\\:\\$value has no typehint specified\\.$#" - count: 1 - path: lib/DefaultValue.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DefaultValue\\:\\:__construct\\(\\) has parameter \\$value with no typehint specified\\.$#" - count: 1 - path: lib/DefaultValue.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DefaultValue\\:\\:none\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/DefaultValue.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DefaultValue\\:\\:ofValue\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/DefaultValue.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DefaultValue\\:\\:ofValue\\(\\) has parameter \\$value with no typehint specified\\.$#" - count: 1 - path: lib/DefaultValue.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DefaultValue\\:\\:value\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/DefaultValue.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Docblock\\:\\:\\$tags type has no value type specified in iterable type Phpactor\\\\Docblock\\\\Tags\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Docblock\\:\\:__construct\\(\\) has parameter \\$tags with no value type specified in iterable type Phpactor\\\\Docblock\\\\Tags\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Docblock\\:\\:fromTags\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Docblock\\:\\:fromTags\\(\\) has parameter \\$tags with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Docblock\\:\\:fromProseAndTags\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Docblock\\:\\:fromProseAndTags\\(\\) has parameter \\$tags with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Docblock\\:\\:tags\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\Tags\\.$#" - count: 1 - path: lib/Docblock.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createVarTag\\(\\) has parameter \\$metadata with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createParamTag\\(\\) has parameter \\$metadata with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createMethodTag\\(\\) has parameter \\$metadata with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createReturnTag\\(\\) has parameter \\$metadata with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createPropertyTag\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createPropertyTag\\(\\) has parameter \\$metadata with no typehint specified\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockFactory\\:\\:createDeprecatedTag\\(\\) has parameter \\$metadata with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/DocblockFactory.php - - - - message: "#^Class Phpactor\\\\Docblock\\\\DocblockTypes implements generic interface IteratorAggregate but does not specify its types\\: TKey, TValue$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:\\$docblocktypes has no typehint specified\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:__construct\\(\\) has parameter \\$docblocktypes with no typehint specified\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:empty\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:fromStringTypes\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:fromStringTypes\\(\\) has parameter \\$types with no typehint specified\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:fromDocblockTypes\\(\\) has parameter \\$docblocktypes with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:fromDocblockTypes\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:getIterator\\(\\) return type has no value type specified in iterable type Traversable\\\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\DocblockTypes\\:\\:add\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/DocblockTypes.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\InheritTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/InheritTag.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Method\\\\Parameter\\:\\:\\$types type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Method/Parameter.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Method\\\\Parameter\\:\\:__construct\\(\\) has parameter \\$types with no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Method/Parameter.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Method\\\\Parameter\\:\\:types\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Method/Parameter.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\:\\:parse\\(\\) has parameter \\$docblock with no typehint specified\\.$#" - count: 1 - path: lib/Parser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Parser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\:\\:extractProse\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Parser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\\\MethodParser\\:\\:parseMethod\\(\\) has parameter \\$parts with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Parser/MethodParser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\\\MethodParser\\:\\:methodInfo\\(\\) has parameter \\$parts with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Parser/MethodParser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\\\MethodParser\\:\\:methodInfo\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Parser/MethodParser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\\\MethodParser\\:\\:parseParameters\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Parser/MethodParser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\\\ParameterParser\\:\\:extractParts\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Parser/ParameterParser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Parser\\\\TypesParser\\:\\:parseTypes\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Parser/TypesParser.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\DeprecatedTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/DeprecatedTag.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:\\$types type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:__construct\\(\\) has parameter \\$methodName with no typehint specified\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:__construct\\(\\) has parameter \\$parameters with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:__construct\\(\\) has parameter \\$types with no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:types\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:methodName\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\MethodTag\\:\\:parameters\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Tag/MethodTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\ParamTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/ParamTag.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Tag\\\\PropertyTag\\:\\:\\$types type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/PropertyTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\PropertyTag\\:\\:__construct\\(\\) has parameter \\$types with no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/PropertyTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\PropertyTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/PropertyTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\PropertyTag\\:\\:propertyName\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/PropertyTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\PropertyTag\\:\\:types\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/PropertyTag.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Tag\\\\ReturnTag\\:\\:\\$types type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/ReturnTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\ReturnTag\\:\\:__construct\\(\\) has parameter \\$types with no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/ReturnTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\ReturnTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/ReturnTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\ReturnTag\\:\\:types\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/ReturnTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\VarTag\\:\\:name\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/VarTag.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Tag\\\\VarTag\\:\\:\\$types type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/VarTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\VarTag\\:\\:__construct\\(\\) has parameter \\$types with no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/VarTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\VarTag\\:\\:__construct\\(\\) has parameter \\$varName with no typehint specified\\.$#" - count: 1 - path: lib/Tag/VarTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\VarTag\\:\\:types\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\DocblockTypes\\.$#" - count: 1 - path: lib/Tag/VarTag.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tag\\\\VarTag\\:\\:varName\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tag/VarTag.php - - - - message: "#^Class Phpactor\\\\Docblock\\\\Tags implements generic interface IteratorAggregate but does not specify its types\\: TKey, TValue$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Property Phpactor\\\\Docblock\\\\Tags\\:\\:\\$tags has no typehint specified\\.$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tags\\:\\:__construct\\(\\) has parameter \\$tags with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tags\\:\\:fromArray\\(\\) has parameter \\$tags with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tags\\:\\:fromArray\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\Tags\\.$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tags\\:\\:getIterator\\(\\) return type has no value type specified in iterable type Traversable\\\\.$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tags\\:\\:byName\\(\\) return type has no value type specified in iterable type Phpactor\\\\Docblock\\\\Tags\\.$#" - count: 1 - path: lib/Tags.php - - - - message: "#^Method Phpactor\\\\Docblock\\\\Tags\\:\\:add\\(\\) has no return typehint specified\\.$#" - count: 1 - path: lib/Tags.php - diff --git a/phpstan.neon b/phpstan.neon index 09c3a5c2..0e807a0d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,3 @@ -includes: - - phpstan-baseline.neon parameters: inferPrivatePropertyTypeFromConstructor: true paths: diff --git a/test.php b/test.php new file mode 100644 index 00000000..c583f974 --- /dev/null +++ b/test.php @@ -0,0 +1,4 @@ +hasDescendant(ScalarNode::class)); self::assertFalse($methodNode->hasDescendant(MethodNode::class)); self::assertCount(2, iterator_to_array($methodNode->descendantElements(ScalarNode::class))); - self::assertInstanceOf(ScalarNode::class, $methodNode->firstDescendant(ScalarNode::class)); + self::assertInstanceOf(ScalarNode::class, $methodNode->firstDescendant(ScalarNode::class)); } ]; } From 0ffbbe02e9cf2d0427649e95f76ac916541c5708 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 22:07:36 +0000 Subject: [PATCH 57/63] Render text --- lib/Ast/Docblock.php | 17 ++++++++++++++++ lib/Lexer.php | 9 +++++---- tests/Printer/examples/method1.test | 4 ++-- tests/Printer/examples/method2.test | 4 ++-- tests/Unit/Ast/NodeTest.php | 30 +++++++++++++++++++++++++++++ tests/Unit/LexerTest.php | 13 ++++++------- 6 files changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index 40e87431..43846ba4 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -20,4 +20,21 @@ public function __construct(array $children) { $this->children = new ElementList($children); } + + public function prose(): string + { + return trim(implode('', array_map(function (Element $token): string { + if ($token instanceof Token) { + if (in_array($token->type, [ + Token::T_PHPDOC_OPEN, + Token::T_PHPDOC_CLOSE, + Token::T_PHPDOC_LEADING + ])) { + return ''; + } + return $token->value; + } + return ''; + }, iterator_to_array($this->children, false)))); + } } diff --git a/lib/Lexer.php b/lib/Lexer.php index 3806eeb0..735992d8 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -13,11 +13,12 @@ final class Lexer private const PATTERNS = [ '/\*+', // start tag '\*/', // close tag - '\*', // leading tag - '\[\]', //tag + ' {1}\* {1}', + '\[\]', // list '\?', //tag '@\w+', //tag - '\s+', // whitespace + '\R', // newline + ' *', // space ',', // comma '\|', // bar (union) '=', // equals @@ -97,7 +98,7 @@ private function resolveType(string $value, ?array $prevChunk = null): string return Token::T_PHPDOC_CLOSE; } - if ($prevChunk && 0 === strpos($prevChunk[0], "\n") && trim($value) === '*') { + if (trim($value) === '*') { return Token::T_PHPDOC_LEADING; } diff --git a/tests/Printer/examples/method1.test b/tests/Printer/examples/method1.test index 80770a95..f85a3223 100644 --- a/tests/Printer/examples/method1.test +++ b/tests/Printer/examples/method1.test @@ -1,10 +1,10 @@ /** - * @method Foobar foobar() + * @method Foobar foobar() */ --- Docblock: = ElementList: = /** * MethodTag: = @method - ClassNode: = Foobarfoobar() + ClassNode: = Foobarfoobar() */ diff --git a/tests/Printer/examples/method2.test b/tests/Printer/examples/method2.test index ba7d6680..4a7a21e6 100644 --- a/tests/Printer/examples/method2.test +++ b/tests/Printer/examples/method2.test @@ -1,10 +1,10 @@ /** - * @method static Foobar foobar() + * @method static Foobar foobar() */ --- Docblock: = ElementList: = /** * MethodTag: = @methodstatic - ClassNode: = Foobarfoobar() + ClassNode: = Foobarfoobar() */ diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 5e045b63..709725da 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -3,6 +3,7 @@ namespace Phpactor\Docblock\Tests\Unit\Ast; use Generator; +use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Tag\MethodTag; use Phpactor\Docblock\Ast\Tag\ReturnTag; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -20,6 +21,7 @@ class NodeTest extends NodeTestCase public function provideNode(): Generator { yield from $this->provideApiTest(); + yield from $this->provideDocblock(); yield from $this->provideTags(); yield from $this->provideTypes(); } @@ -102,4 +104,32 @@ function (ReturnTag $return): void { } ]; } + + private function provideDocblock() + { + yield 'docblock' => [ + <<<'EOT' +/** + * This is a docblock + * With some text - + * and maybe some + * ``` + * Markdown + * ``` + * @param This $should not be included + */ +EOT + , function (Docblock $docblock): void { + self::assertEquals(<<<'EOT' +This is a docblock +With some text - +and maybe some +``` +Markdown +``` +EOT +, $docblock->prose()); + } + ]; + } } diff --git a/tests/Unit/LexerTest.php b/tests/Unit/LexerTest.php index 4835aa8e..86943555 100644 --- a/tests/Unit/LexerTest.php +++ b/tests/Unit/LexerTest.php @@ -42,19 +42,18 @@ public function provideLex(): Generator ,[ [Token::T_PHPDOC_OPEN, '/**'], - [Token::T_WHITESPACE, "\n "], - [Token::T_PHPDOC_LEADING, '*'], - [Token::T_WHITESPACE, ' '], + [Token::T_WHITESPACE, "\n"], + [Token::T_PHPDOC_LEADING, ' * '], [Token::T_LABEL, 'Hello'], [Token::T_WHITESPACE, ' '], [Token::T_LABEL, 'this'], [Token::T_WHITESPACE, ' '], [Token::T_LABEL, 'is'], - [Token::T_WHITESPACE, "\n "], - [Token::T_PHPDOC_LEADING, '*'], - [Token::T_WHITESPACE, ' '], + [Token::T_WHITESPACE, "\n"], + [Token::T_PHPDOC_LEADING, ' * '], [Token::T_LABEL, 'Multi'], - [Token::T_WHITESPACE, "\n "], + [Token::T_WHITESPACE, "\n"], + [Token::T_WHITESPACE, " "], [Token::T_PHPDOC_CLOSE, '*/'], ] ]; From 59af98ef0f4eba011743f4fccb5e56851b70c94b Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 22:26:07 +0000 Subject: [PATCH 58/63] Add API for tags to docblock --- lib/Ast/Docblock.php | 35 +++++++++++++++++++++++++++++++++++ lib/Ast/Node.php | 10 +++++++--- tests/Unit/Ast/NodeTest.php | 9 +++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index 43846ba4..08b1ac0d 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -2,6 +2,8 @@ namespace Phpactor\Docblock\Ast; +use Generator; + class Docblock extends Node { protected const CHILD_NAMES = [ @@ -21,6 +23,39 @@ public function __construct(array $children) $this->children = new ElementList($children); } + /** + * @param class-string $tagFqn + */ + public function hasTag(string $tagFqn): bool + { + foreach ($this->tags() as $tag) { + if ($tag instanceof $tagFqn) { + return true; + } + } + + return false; + } + + /** + * @template T + * @param ?class-string $tagFqn + * @return Generator + */ + public function tags(?string $tagFqn = null): Generator + { + foreach ($this->children as $child) { + if ($tagFqn && $child instanceof $tagFqn) { + yield $child; + continue; + } + if ($child instanceof TagNode) { + yield $child; + continue; + } + } + } + public function prose(): string { return trim(implode('', array_map(function (Element $token): string { diff --git a/lib/Ast/Node.php b/lib/Ast/Node.php index fe44c695..0d7ff43a 100644 --- a/lib/Ast/Node.php +++ b/lib/Ast/Node.php @@ -147,7 +147,7 @@ private function traverseNodes(iterable $nodes): Generator { $result = []; foreach ($nodes as $child) { - if (is_array($child)) { + if (is_iterable($child)) { yield from $this->traverseNodes($child); continue; } @@ -178,6 +178,10 @@ private function endOf(iterable $elements): int return $this->endOf(array_reverse($element)); } + if (is_iterable($element)) { + return $this->endOf(array_reverse(iterator_to_array($element))); + } + return $element->end(); } @@ -198,7 +202,7 @@ private function startOf(iterable $elements): int if ($element instanceof Element) { return $element->start(); } - if (is_array($element)) { + if (is_iterable($element)) { return $this->startOf($element); } } @@ -222,7 +226,7 @@ private function findTokens(iterable $nodes): Generator yield from $node->tokens(); } - if (is_array($node)) { + if (is_iterable($node)) { yield from $this->findTokens($node); } } diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 709725da..f4afe0e0 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -4,6 +4,7 @@ use Generator; use Phpactor\Docblock\Ast\Docblock; +use Phpactor\Docblock\Ast\Tag\DeprecatedTag; use Phpactor\Docblock\Ast\Tag\MethodTag; use Phpactor\Docblock\Ast\Tag\ReturnTag; use Phpactor\Docblock\Ast\Type\ClassNode; @@ -65,7 +66,15 @@ function (MethodTag $methodNode): void { self::assertFalse($methodNode->hasChild(MethodTag::class)); } ]; + yield [ '@deprecated This is deprecated']; + yield 'deprecated' => [ + '/** @deprecated This is deprecated */', + function (Docblock $block) { + self::assertTrue($block->hasTag(DeprecatedTag::class)); + } + ]; + yield [ '/** This is docblock @deprecated Foo */']; yield [ '@mixin Foo\Bar']; yield [ '@param string $foo This is a parameter']; From eb60d2c93227fc5c4f93c3103d3eca7b73cbd9e8 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 31 Jan 2021 08:19:30 +0000 Subject: [PATCH 59/63] FIx docblock get tag --- lib/Ast/Docblock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index 08b1ac0d..98d37435 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -49,7 +49,7 @@ public function tags(?string $tagFqn = null): Generator yield $child; continue; } - if ($child instanceof TagNode) { + if (!$tagFqn && $child instanceof TagNode) { yield $child; continue; } From 91d2d921af482a708bdbe5b203c528847df80a65 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 31 Jan 2021 10:01:32 +0000 Subject: [PATCH 60/63] Accessor for parameters --- lib/Ast/Docblock.php | 2 +- lib/Ast/ParameterList.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/Ast/Docblock.php b/lib/Ast/Docblock.php index 98d37435..40e10805 100644 --- a/lib/Ast/Docblock.php +++ b/lib/Ast/Docblock.php @@ -39,7 +39,7 @@ public function hasTag(string $tagFqn): bool /** * @template T - * @param ?class-string $tagFqn + * @param class-string|null $tagFqn * @return Generator */ public function tags(?string $tagFqn = null): Generator diff --git a/lib/Ast/ParameterList.php b/lib/Ast/ParameterList.php index f01f2f6e..5fae446c 100644 --- a/lib/Ast/ParameterList.php +++ b/lib/Ast/ParameterList.php @@ -4,7 +4,9 @@ use ArrayIterator; use Countable; +use Generator; use IteratorAggregate; +use Phpactor\Docblock\Ast\Tag\ParameterTag; /** * @implements IteratorAggregate @@ -28,6 +30,18 @@ public function __construct(array $list) $this->list = $list; } + /** + * @return Generator + */ + public function parameters(): Generator + { + foreach ($this->list as $element) { + if ($element instanceof ParameterTag) { + yield $element; + } + } + } + /** * @return ArrayIterator */ From 60064204457ed1f3dad74b19006fb48f9ba149f8 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Wed, 27 Jan 2021 22:34:57 +0000 Subject: [PATCH 61/63] Removed core PHP docs from benchmark --- tests/Benchmark/AbstractParserBenchCase.php | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/tests/Benchmark/AbstractParserBenchCase.php b/tests/Benchmark/AbstractParserBenchCase.php index 9cdb1639..f8a10be8 100644 --- a/tests/Benchmark/AbstractParserBenchCase.php +++ b/tests/Benchmark/AbstractParserBenchCase.php @@ -26,26 +26,9 @@ public function benchParse(): void } /** - * @ParamProviders({"provideCoreDocs"}) + * @Revs(5) + * @Iterations(10) */ - public function benchPhpCore(array $params): void - { - $this->parse(trim($params['doc'])); - } - - /** - * @return Generator - */ - public function provideCoreDocs(): Generator - { - $contents = file_get_contents(__DIR__ . '/examples/php_core.example'); - foreach (explode('#!---!#', $contents) as $doc) { - yield str_replace("\n", '', substr($doc, 0, 10)) => [ - 'doc' => $doc - ]; - } - } - public function benchAssert(): void { $this->parse(file_get_contents(__DIR__ . '/examples/assert.example')); From beae2699ef9346696bb9721bacd187e334bd38a2 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 31 Jan 2021 10:12:36 +0000 Subject: [PATCH 62/63] Prop assertion --- tests/Unit/Ast/NodeTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index f4afe0e0..9d777542 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -6,6 +6,7 @@ use Phpactor\Docblock\Ast\Docblock; use Phpactor\Docblock\Ast\Tag\DeprecatedTag; use Phpactor\Docblock\Ast\Tag\MethodTag; +use Phpactor\Docblock\Ast\Tag\PropertyTag; use Phpactor\Docblock\Ast\Tag\ReturnTag; use Phpactor\Docblock\Ast\Type\ClassNode; use Phpactor\Docblock\Ast\Type\GenericNode; @@ -66,6 +67,12 @@ function (MethodTag $methodNode): void { self::assertFalse($methodNode->hasChild(MethodTag::class)); } ]; + yield [ + '@property Baz\Bar $foobar', + function (PropertyTag $property): void { + self::assertEquals('$foobar', $property->name->toString());t + } + ]; yield [ '@deprecated This is deprecated']; yield 'deprecated' => [ @@ -81,7 +88,6 @@ function (Docblock $block) { yield ['@param Baz\Bar $foobar This is a parameter']; yield ['@var Baz\Bar $foobar']; yield ['@return Baz\Bar']; - yield ['@property Baz\Bar $foobar']; } /** From 6d4b18e67d7b043e0ff28ac1a99d0a7f571b15f8 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sun, 31 Jan 2021 10:17:47 +0000 Subject: [PATCH 63/63] Support for return $this --- lib/Ast/Type/ThisNode.php | 23 +++++++++++++++++++++++ lib/Parser.php | 8 ++++++++ tests/Printer/examples/return2.test | 10 ++++++++++ tests/Unit/Ast/NodeTest.php | 3 ++- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 lib/Ast/Type/ThisNode.php create mode 100644 tests/Printer/examples/return2.test diff --git a/lib/Ast/Type/ThisNode.php b/lib/Ast/Type/ThisNode.php new file mode 100644 index 00000000..7b159688 --- /dev/null +++ b/lib/Ast/Type/ThisNode.php @@ -0,0 +1,23 @@ +name = $name; + } +} diff --git a/lib/Parser.php b/lib/Parser.php index 61207016..c4263cef 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -22,6 +22,7 @@ use Phpactor\Docblock\Ast\Type\NullNode; use Phpactor\Docblock\Ast\Type\NullableNode; use Phpactor\Docblock\Ast\Type\ScalarNode; +use Phpactor\Docblock\Ast\Type\ThisNode; use Phpactor\Docblock\Ast\Type\UnionNode; use Phpactor\Docblock\Ast\UnknownTag; use Phpactor\Docblock\Ast\ValueNode; @@ -348,6 +349,13 @@ private function parseReturn(): ReturnTag $type = $this->parseTypes(); } + if ($this->tokens->if(Token::T_VARIABLE)) { + $variable = $this->tokens->chomp(Token::T_VARIABLE); + if ($variable->value === '$this') { + $type = new ThisNode($variable); + } + } + return new ReturnTag($tag, $type, $this->parseText()); } diff --git a/tests/Printer/examples/return2.test b/tests/Printer/examples/return2.test new file mode 100644 index 00000000..2e64713a --- /dev/null +++ b/tests/Printer/examples/return2.test @@ -0,0 +1,10 @@ +/** + * @return $this + */ +--- +Docblock: = + ElementList: = /** + * + ReturnTag: = @return + ThisNode: = $this + */ diff --git a/tests/Unit/Ast/NodeTest.php b/tests/Unit/Ast/NodeTest.php index 9d777542..6e5d88da 100644 --- a/tests/Unit/Ast/NodeTest.php +++ b/tests/Unit/Ast/NodeTest.php @@ -70,7 +70,7 @@ function (MethodTag $methodNode): void { yield [ '@property Baz\Bar $foobar', function (PropertyTag $property): void { - self::assertEquals('$foobar', $property->name->toString());t + self::assertEquals('$foobar', $property->name->toString()); } ]; @@ -88,6 +88,7 @@ function (Docblock $block) { yield ['@param Baz\Bar $foobar This is a parameter']; yield ['@var Baz\Bar $foobar']; yield ['@return Baz\Bar']; + yield ['@return $this']; } /**