diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index db97a49..6385049 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -26,15 +26,6 @@ jobs: php-version: ${{ matrix.php }} - name: Validate Composer run: composer validate - - name: Get Composer Cache Directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Restore Composer Cache - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- - name: Install Dependencies uses: nick-invision/retry@v2 with: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 3019d6c..939d4a2 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -26,24 +26,5 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - - name: Validate Composer - run: composer validate - - name: Get Composer Cache Directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Restore Composer Cache - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- - - name: Install Dependencies - uses: nick-invision/retry@v2 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --prefer-dist --no-interaction --no-progress - name: Composer Audit run: composer audit - - name: Security Advisories - run: composer require --dev roave/security-advisories:dev-latest diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 3d93fd2..3fccdb7 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -26,15 +26,6 @@ jobs: php-version: ${{ matrix.php }} - name: Validate Composer run: composer validate - - name: Get Composer Cache Directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Restore Composer Cache - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- - name: Install Dependencies uses: nick-invision/retry@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 42d2ffb..3deff09 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,15 +31,6 @@ jobs: ini-values: "memory_limit=-1" - name: Validate Composer run: composer validate - - name: Get Composer Cache Directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Restore Composer Cache - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- - name: Install Dependencies uses: nick-invision/retry@v2 with: diff --git a/README.md b/README.md index de3abe9..9335280 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Reference implementation for TypeLang PHPDoc Parser. -Read [documentation pages](https://phpdoc.io) for more information. +Read [documentation pages](https://typelang.dev) for more information. ## Installation diff --git a/composer.json b/composer.json index 22cc4d6..a7ed485 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,14 @@ } }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.42", + "friendsofphp/php-cs-fixer": "^3.53", + "jetbrains/phpstorm-attributes": "^1.0", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-strict-rules": "^1.5", "phpunit/phpunit": "^10.5", "rector/rector": "^1.0", - "type-lang/parser": "^1.0", - "vimeo/psalm": "^5.18" + "type-lang/parser": "^1.0" }, "autoload-dev": { "psr-4": { @@ -41,16 +44,19 @@ "optimize-autoloader": true, "preferred-install": { "*": "dist" + }, + "allow-plugins": { + "phpstan/extension-installer": true } }, "scripts": { "test": ["@test:unit", "@test:functional"], "test:unit": "phpunit --testdox --testsuite=unit", - "test:functional": "phpunit --testdox --testsuite=functional", + "test:functional": "phpunit --testsuite=functional", "linter": "@linter:check", - "linter:check": "psalm --no-cache", - "linter:fix": "psalm --no-cache --alter", + "linter:check": "phpstan analyse --configuration phpstan.neon", + "linter:baseline": "phpstan analyse --configuration phpstan.neon --generate-baseline", "phpcs": "@phpcs:check", "phpcs:check": "php-cs-fixer fix --config=.php-cs-fixer.php --allow-risky=yes --dry-run --verbose --diff", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..73151ee --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,12 @@ +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon +parameters: + level: 9 + strictRules: + allRules: true + fileExtensions: + - php + paths: + - src + tmpDir: vendor/.cache.phpstan + reportUnmatchedIgnoredErrors: false diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 887dad6..0000000 --- a/psalm.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/DocBlock.php b/src/DocBlock.php index d59b8e0..9bcd21c 100644 --- a/src/DocBlock.php +++ b/src/DocBlock.php @@ -14,8 +14,8 @@ * This class represents structure containing a description and a set of tags * that describe an arbitrary DocBlock Comment in the code. * - * @template-implements \ArrayAccess, TagInterface|null> - * @template-implements \IteratorAggregate, TagInterface> + * @template-implements \ArrayAccess + * @template-implements \IteratorAggregate */ final class DocBlock implements OptionalDescriptionProviderInterface, @@ -28,7 +28,6 @@ final class DocBlock implements /** * @var list - * @psalm-suppress PropertyNotSetInConstructor */ private readonly array $tags; @@ -54,6 +53,9 @@ public function getDescription(): DescriptionInterface return $this->description; } + /** + * @return list + */ public function getTags(): array { return $this->tags; diff --git a/src/Exception/ParsingException.php b/src/Exception/ParsingException.php index 761da92..0c27ae8 100644 --- a/src/Exception/ParsingException.php +++ b/src/Exception/ParsingException.php @@ -18,7 +18,7 @@ final public function __construct( public readonly int $offset = 0, string $message = "", int $code = 0, - ?\Throwable $previous = null + ?\Throwable $previous = null, ) { parent::__construct($message, $code, $previous); } diff --git a/src/Parser/Description/RegexDescriptionParser.php b/src/Parser/Description/RegexDescriptionParser.php index acfc5f4..a3f3604 100644 --- a/src/Parser/Description/RegexDescriptionParser.php +++ b/src/Parser/Description/RegexDescriptionParser.php @@ -91,7 +91,7 @@ private function getDocBlockChunks(string $contents): array } /** @var list */ - return \array_filter($result); + return \array_filter($result, static fn(string $chunk): bool => $chunk !== ''); } /** diff --git a/src/Parser/Tag/RegexTagParser.php b/src/Parser/Tag/RegexTagParser.php index 61c8e3e..9415e92 100644 --- a/src/Parser/Tag/RegexTagParser.php +++ b/src/Parser/Tag/RegexTagParser.php @@ -85,9 +85,9 @@ public function parse(string $tag, DescriptionParserInterface $parser): TagInter try { return $this->tags->create($name, new Content($trimmed), $parser); } catch (RuntimeExceptionInterface $e) { - /** @var int<0, max> */ $offset += \strlen($content) - \strlen($trimmed); + /** @var int<0, max> $offset */ throw $e->withSource($tag, $offset); } } diff --git a/src/Tag/Content.php b/src/Tag/Content.php index 8aaa28b..d333efc 100644 --- a/src/Tag/Content.php +++ b/src/Tag/Content.php @@ -50,7 +50,7 @@ public function shift(int $offset, bool $ltrim = true): void $this->value = \ltrim($this->value); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ + // @phpstan-ignore-next-line : Offset already greater than 0 $this->offset += $size - \strlen($this->value); } @@ -175,6 +175,15 @@ public function toDescription(DescriptionParserInterface $descriptions): Descrip return $descriptions->parse($this->value); } + public function toOptionalDescription(DescriptionParserInterface $descriptions): ?DescriptionInterface + { + if (\trim($this->value) === '') { + return null; + } + + return $descriptions->parse(\rtrim($this->value)); + } + public function __toString(): string { return $this->value; diff --git a/src/Tag/Content/OptionalTypeParserApplicator.php b/src/Tag/Content/OptionalTypeParserApplicator.php index f655e33..b7261bd 100644 --- a/src/Tag/Content/OptionalTypeParserApplicator.php +++ b/src/Tag/Content/OptionalTypeParserApplicator.php @@ -15,9 +15,6 @@ */ final class OptionalTypeParserApplicator extends Applicator { - /** - * @param non-empty-string $tag - */ public function __construct( private readonly TypesParserInterface $parser, ) {} @@ -36,10 +33,7 @@ public function __invoke(Content $lexer): ?TypeStatement return null; } - /** - * @psalm-suppress MixedArgument - * @psalm-suppress NoInterfaceProperties - */ + // @phpstan-ignore-next-line : Property is defined $lexer->shift($this->parser->lastProcessedTokenOffset); return $type; diff --git a/src/Tag/Content/TypeParserApplicator.php b/src/Tag/Content/TypeParserApplicator.php index 5fdd32a..fd3263c 100644 --- a/src/Tag/Content/TypeParserApplicator.php +++ b/src/Tag/Content/TypeParserApplicator.php @@ -32,7 +32,6 @@ public function __construct( public function __invoke(Content $lexer): TypeStatement { try { - /** @var TypeStatement $type */ $type = $this->parser->parse($lexer->value); } catch (ParserExceptionInterface $e) { /** @psalm-suppress InvalidArgument */ @@ -42,10 +41,7 @@ public function __invoke(Content $lexer): TypeStatement ); } - /** - * @psalm-suppress MixedArgument - * @psalm-suppress NoInterfaceProperties - */ + // @phpstan-ignore-next-line : Property is defined $lexer->shift($this->parser->lastProcessedTokenOffset); return $type; diff --git a/src/Tag/Content/ValueApplicator.php b/src/Tag/Content/ValueApplicator.php index 35d344c..822665b 100644 --- a/src/Tag/Content/ValueApplicator.php +++ b/src/Tag/Content/ValueApplicator.php @@ -13,6 +13,9 @@ */ final class ValueApplicator extends Applicator { + /** + * @var OptionalValueApplicator + */ private readonly OptionalValueApplicator $identifier; /** @@ -21,7 +24,7 @@ final class ValueApplicator extends Applicator */ public function __construct( private readonly string $tag, - private readonly string $value + private readonly string $value, ) { $this->identifier = new OptionalValueApplicator($value); } diff --git a/src/Tag/Description/TaggedDescription.php b/src/Tag/Description/TaggedDescription.php index 3494a23..9c0148d 100644 --- a/src/Tag/Description/TaggedDescription.php +++ b/src/Tag/Description/TaggedDescription.php @@ -7,8 +7,8 @@ use TypeLang\PHPDoc\Tag\TagInterface; /** - * @template-implements \ArrayAccess, TagInterface|DescriptionInterface|null> - * @template-implements \IteratorAggregate, TagInterface|DescriptionInterface> + * @template-implements \ArrayAccess + * @template-implements \IteratorAggregate */ class TaggedDescription implements TaggedDescriptionInterface, @@ -60,7 +60,7 @@ public function count(): int public function offsetExists(mixed $offset): bool { - return isset($this->tags[$offset]); + return isset($this->components[$offset]); } public function offsetGet(mixed $offset): TagInterface|DescriptionInterface|null diff --git a/src/Tag/Description/TaggedDescriptionInterface.php b/src/Tag/Description/TaggedDescriptionInterface.php index 7e0de1e..9328097 100644 --- a/src/Tag/Description/TaggedDescriptionInterface.php +++ b/src/Tag/Description/TaggedDescriptionInterface.php @@ -12,7 +12,7 @@ * containing an arbitrary set of nested tags ({@see TagInterface}) and, * like a parent {@see DescriptionInterface}, can be represented as a string. * - * @template-extends \Traversable, DescriptionInterface|TagInterface> + * @template-extends \Traversable */ interface TaggedDescriptionInterface extends TagsProviderInterface, diff --git a/src/Tag/Factory/TagFactory.php b/src/Tag/Factory/TagFactory.php index 8d5b582..4b5771b 100644 --- a/src/Tag/Factory/TagFactory.php +++ b/src/Tag/Factory/TagFactory.php @@ -5,7 +5,6 @@ namespace TypeLang\PHPDoc\Tag\Factory; use TypeLang\PHPDoc\Exception\InvalidTagException; -use TypeLang\PHPDoc\Exception\ParsingException; use TypeLang\PHPDoc\Exception\RuntimeExceptionInterface; use TypeLang\PHPDoc\Parser\Description\DescriptionParserInterface; use TypeLang\PHPDoc\Tag\InvalidTag; diff --git a/src/Tag/InvalidTag.php b/src/Tag/InvalidTag.php index a74f26c..03a9235 100644 --- a/src/Tag/InvalidTag.php +++ b/src/Tag/InvalidTag.php @@ -4,7 +4,7 @@ namespace TypeLang\PHPDoc\Tag; -final class InvalidTag extends Tag +final class InvalidTag extends Tag implements InvalidTagInterface { /** * @var non-empty-string diff --git a/src/Tag/InvalidTagInterface.php b/src/Tag/InvalidTagInterface.php new file mode 100644 index 0000000..8209413 --- /dev/null +++ b/src/Tag/InvalidTagInterface.php @@ -0,0 +1,13 @@ + - * @psalm-suppress PropertyNotSetInConstructor - */ - protected readonly array $tags; - - /** - * @param iterable $tags - * @psalm-suppress InaccessibleProperty - */ - protected function bootTagProvider(iterable $tags): void - { - $this->tags = \array_values([...$tags]); - } - - /** - * @see TagsProviderInterface::getTags() - * - * @return list - */ - public function getTags(): array - { - return $this->tags; - } - - public function offsetExists(mixed $offset): bool - { - assert(\is_int($offset)); - - return isset($this->tags[$offset]); - } - - public function offsetGet(mixed $offset): ?TagInterface - { - assert(\is_int($offset)); - - return $this->tags[$offset] ?? null; - } - - /** - * {@inheritDoc} - * - * @throws \BadMethodCallException - */ - public function offsetSet(mixed $offset, mixed $value): void - { - assert(\is_int($offset)); - assert($value instanceof TagInterface); - - throw new \BadMethodCallException(self::class . ' objects are immutable'); - } - - /** - * {@inheritDoc} - * - * @throws \BadMethodCallException - */ - public function offsetUnset(mixed $offset): void - { - assert(\is_int($offset)); - - throw new \BadMethodCallException(self::class . ' objects are immutable'); - } -}