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');
- }
-}