diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6a902cc18440dafbac4bbdcca843523d3a62edab..dc54673af6bbb85355ecdc401000c9092f3b9b1e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,8 +13,8 @@ include: # ----- Build stage .build-setup-composer: &build-setup-composer script: - - curl -sSL https://getcomposer.org/download/2.0.1/composer.phar --output composer.phar - - echo "4b4b118cc54662e4813ba86efb215fdb19c5b29944c5919b4f2803c915aa2234 composer.phar" > checksums + - curl -sSL https://getcomposer.org/download/2.3.5/composer.phar --output composer.phar + - echo "3b3b5a899c06a46aec280727bdf50aad14334f6bc40436ea76b07b650870d8f4 composer.phar" > checksums - sha256sum -c checksums - php composer.phar update --prefer-dist --optimize-autoloader --no-interaction --no-progress @@ -43,13 +43,17 @@ build:php-8.0: image: php:8.0-cli-alpine <<: *build-job-shared +build:php-8.1: + image: php:8.1-cli-alpine + <<: *build-job-shared + # ----- Test stage test:run-composer-require-checker: - image: php:7.4-cli-alpine + image: php:8.0-cli-alpine stage: test script: - - curl -sSL https://github.com/maglnet/ComposerRequireChecker/releases/download/2.1.0/composer-require-checker.phar --output composer-require-checker.phar - - echo "f5b57c8f4305eb3d5ec605943b5250b10aacb939778be005360f815860474495 composer-require-checker.phar" > checksums + - curl -sSL https://github.com/maglnet/ComposerRequireChecker/releases/download/4.1.0/composer-require-checker.phar --output composer-require-checker.phar + - echo "2a4c652de474e5dbbda1662ff82f207be7aa86b44912093cf5fb135345506e66 composer-require-checker.phar" > checksums - sha256sum -c checksums - php ./composer-require-checker.phar check ./composer.json @@ -117,12 +121,19 @@ test:php-8.0: - job: build:php-8.0 artifacts: true +test:php-8.1: + image: php:8.1-cli-alpine + <<: *test-job-shared + needs: + - job: build:php-8.1 + artifacts: true + # ----- Finalization stage, which contains finishing steps such as metrics and packaging. .finalization-package-job-shared: &finalization-package-job-shared stage: finalization script: - - curl -sSL https://getcomposer.org/download/2.0.1/composer.phar --output composer.phar - - echo "4b4b118cc54662e4813ba86efb215fdb19c5b29944c5919b4f2803c915aa2234 composer.phar" > checksums + - curl -sSL https://getcomposer.org/download/2.3.5/composer.phar --output composer.phar + - echo "3b3b5a899c06a46aec280727bdf50aad14334f6bc40436ea76b07b650870d8f4 composer.phar" > checksums - sha256sum -c checksums # Do a clean install so we can avoid including dev dependencies. @@ -153,6 +164,10 @@ finalization:package-php-8.0: image: php:8.0-cli-alpine <<: *finalization-package-job-shared +finalization:package-php-8.1: + image: php:8.1-cli-alpine + <<: *finalization-package-job-shared + finalization:code-coverage: image: php:8.0-cli-alpine stage: finalization diff --git a/composer.json b/composer.json index f0658f17cc208a362d85f3ebaa96ee5ef14b35fc..ea374c0dbb54710f5f17c6e4df30e06209532038 100644 --- a/composer.json +++ b/composer.json @@ -37,12 +37,12 @@ }, "require-dev": { "brianium/paratest": "^6.2", - "pepakriz/phpstan-exception-rules": "^0.11", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-doctrine": "^0.12", - "phpstan/phpstan-php-parser": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", + "pepakriz/phpstan-exception-rules": "^0.12", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-php-parser": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.0", "slevomat/coding-standard": "^6.3", "symfony/process": "^5.1", @@ -60,7 +60,10 @@ }, "config": { "sort-packages": true, - "preferred-install": "dist" + "preferred-install": "dist", + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } }, "bin": [ "src/Main.php" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 15b54244fa5f625351b5ce713ed10d9210317d36..100f6d7040a873b1ecdd727e9218c312e3656f0e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -65,11 +65,6 @@ parameters: count: 1 path: src/Indexing/Indexer.php - - - message: "#^Missing @throws Doctrine\\\\ORM\\\\ORMException annotation$#" - count: 1 - path: src/Indexing/ManagerRegistry.php - - message: "#^Missing @throws Doctrine\\\\ORM\\\\Tools\\\\ToolsException annotation$#" count: 1 @@ -130,11 +125,6 @@ parameters: count: 1 path: src/Parsing/PhpstanDocblockTypeParser.php - - - message: "#^Unused @throws UnderflowException annotation$#" - count: 1 - path: src/Sockets/JsonRpcQueue.php - - message: "#^Useless @throws Serenata\\\\Sockets\\\\RequestParsingException annotation$#" count: 1 @@ -310,16 +300,6 @@ parameters: count: 1 path: tests/Integration/AbstractIntegrationTest.php - - - message: "#^Cannot call method clear\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Integration/AbstractIntegrationTest.php - - - - message: "#^Cannot call method run\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Integration/AbstractIntegrationTest.php - - message: "#^Cannot call method getManager\\(\\) on object\\|null\\.$#" count: 3 @@ -515,11 +495,6 @@ parameters: count: 1 path: tests/Integration/Analysis/Typing/Deduction/ExpressionTypeDeducerTest.php - - - message: "#^Method Serenata\\\\Tests\\\\Integration\\\\Analysis\\\\Typing\\\\Deduction\\\\ExpressionTypeDeducerTest\\:\\:getMarkerOffset\\(\\) should return int but returns int\\|false\\.$#" - count: 1 - path: tests/Integration/Analysis/Typing/Deduction/ExpressionTypeDeducerTest.php - - message: "#^Cannot call method getFileByUri\\(\\) on object\\|null\\.$#" count: 1 @@ -570,11 +545,6 @@ parameters: count: 1 path: tests/Integration/Autocompletion/Providers/AbstractAutocompletionProviderTest.php - - - message: "#^Method Serenata\\\\Tests\\\\Integration\\\\Autocompletion\\\\Providers\\\\AbstractAutocompletionProviderTest\\:\\:getMarkerOffset\\(\\) should return int but returns int\\|false\\.$#" - count: 1 - path: tests/Integration/Autocompletion/Providers/AbstractAutocompletionProviderTest.php - - message: "#^Cannot call method create\\(\\) on object\\|null\\.$#" count: 2 @@ -905,11 +875,6 @@ parameters: count: 1 path: tests/Unit/Analysis/Visiting/ScopeLimitingVisitorTest.php - - - message: "#^Unable to resolve the template type T in call to method Serenata\\\\Autocompletion\\\\ApproximateStringMatching\\\\ApproximateStringMatchingBestStringApproximationDeterminer\\:\\:determine\\(\\)$#" - count: 1 - path: tests/Unit/Autocompletion/ApproximateStringMatching/ApproximateStringMatchingBestStringApproximationDeterminerTest.php - - message: "#^Property Serenata\\\\Tests\\\\Unit\\\\Autocompletion\\\\AutocompletionPrefixDeterminerTest\\:\\:\\$boundaryTokenRetrieverMock has unknown class PHPUnit_Framework_MockObject_MockObject as its type\\.$#" count: 1 @@ -969,9 +934,3 @@ parameters: message: "#^Access to an undefined property PhpParser\\\\Node\\\\Expr\\:\\:\\$value\\.$#" count: 2 path: tests/Unit/Parsing/PartialParserTest.php - - - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'PhpParser\\\\\\\\Node\\\\\\\\Arg' and PhpParser\\\\Node\\\\Arg will always evaluate to true\\.$#" - count: 12 - path: tests/Unit/Parsing/PartialParserTest.php - diff --git a/phpstan.neon b/phpstan.neon index 7087e4d1ff11aa9f84cc62ab38a90bae27f0bcf6..e13d912f9418806e741ec671348c42d339e45f49 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -32,14 +32,7 @@ parameters: # able to use inheritDoc here. - '#Method [A-Za-z\\_]+::jsonSerialize\(\) return type has no value type specified in iterable type array.#' - '#Method [A-Za-z\\_]+::getSubNodeNames\(\) return type has no value type specified in iterable type array.#' - - '#no value type specified in iterable type Symfony\\Component\\Process\\Process.#' - message: '#Variable property access on#' path: 'src/Analysis/Visiting/ScopeLimitingVisitor.php' - - # I think this one is a bug in PHPStan's stubs. - - - message: "#^Call to an undefined method Ds\\\\PriorityQueue\\\\:\\:pop\\(\\)\\.$#" - count: 1 - path: src/Sockets/JsonRpcQueue.php diff --git a/src/Analysis/ClasslikeListProviderInterface.php b/src/Analysis/ClasslikeListProviderInterface.php index 3dd3cc1c817b37617bf7ea730ce34f76aacd1922..a2080eae7b598dc8d5beca46dcb7d5a862bff898 100644 --- a/src/Analysis/ClasslikeListProviderInterface.php +++ b/src/Analysis/ClasslikeListProviderInterface.php @@ -8,7 +8,7 @@ namespace Serenata\Analysis; interface ClasslikeListProviderInterface { /** - * @return array mapping FQCN's to classlikes. + * @return array> mapping FQCN's to classlikes. */ public function getAll(): array; } diff --git a/src/Analysis/Conversion/AbstractConverter.php b/src/Analysis/Conversion/AbstractConverter.php index 90402a521343002d747db58bd1d17c10c1a04321..b271d34a1029490727ebdd82c0950d89726dc307 100644 --- a/src/Analysis/Conversion/AbstractConverter.php +++ b/src/Analysis/Conversion/AbstractConverter.php @@ -14,7 +14,7 @@ abstract class AbstractConverter /** * @param TypeNode $type * - * @return array[] + * @return array */ protected function convertDocblockType(TypeNode $type): array { diff --git a/src/Analysis/FunctionListProviderInterface.php b/src/Analysis/FunctionListProviderInterface.php index 5bd506876ef81540064ad00fa5b1796e0ecbf0a1..d7ca7dae986ebfc7718beff6f3dc127901fe9270 100644 --- a/src/Analysis/FunctionListProviderInterface.php +++ b/src/Analysis/FunctionListProviderInterface.php @@ -8,7 +8,7 @@ namespace Serenata\Analysis; interface FunctionListProviderInterface { /** - * @return array> mapping FQCN's to functions. + * @return array>> mapping FQCN's to functions. */ public function getAll(): array; } diff --git a/src/Analysis/Node/MethodCallMethodInfoRetriever.php b/src/Analysis/Node/MethodCallMethodInfoRetriever.php index b763e48f15ed8f6454fb5f3bac75e2a54b0e53b9..62d2e9875f03283bfe621c0d6dc35f5281260b6c 100644 --- a/src/Analysis/Node/MethodCallMethodInfoRetriever.php +++ b/src/Analysis/Node/MethodCallMethodInfoRetriever.php @@ -62,7 +62,7 @@ final class MethodCallMethodInfoRetriever * @param TextDocumentItem $textDocumentItem * @param Position $position * - * @return array[] + * @return array> */ public function retrieve(Node\Expr $node, TextDocumentItem $textDocumentItem, Position $position): array { diff --git a/src/Analysis/SourceCodeReading/TextToUtf8Converter.php b/src/Analysis/SourceCodeReading/TextToUtf8Converter.php index 00fee900cf3237d554621fdff0801b89faa11074..f9e312222ff3bc0f2884b03951a43bc6434a1549 100644 --- a/src/Analysis/SourceCodeReading/TextToUtf8Converter.php +++ b/src/Analysis/SourceCodeReading/TextToUtf8Converter.php @@ -2,6 +2,10 @@ namespace Serenata\Analysis\SourceCodeReading; +use function mb_detect_encoding; +use function mb_detect_order; +use function mb_convert_encoding; + /** * Converts text to UTF-8. */ @@ -12,16 +16,20 @@ final class TextToUtf8Converter implements TextEncodingConverterInterface */ public function convert(string $code): string { - $encoding = mb_detect_encoding($code, null, true); + // BUG: https://github.com/php/php-src/issues/9008 + // The workaround is, we suppose the encoding is UTF-8 by default. If it is already, no + // action will be done, otherwise, it tries to detect and convert the encoding. Notice that + // the latter case would be a rare situation, so no worries. + $utf8 = mb_detect_encoding($code, ['UTF-8'], true); - if ($encoding === false) { - $encoding = 'ASCII'; + if ($utf8 !== false) { + return $code; } - if (!in_array($encoding, ['UTF-8', 'ASCII'], true)) { - $code = mb_convert_encoding($code, 'UTF-8', $encoding); - } + $encoding = mb_detect_encoding($code, mb_detect_order()); - return $code; + // Safe assumption: $encoding is neither equal to UTF-8 nor ASCII + // (as every ASCII encoding is also a UTF-8 one) + return mb_convert_encoding($code, 'UTF-8', $encoding); } } diff --git a/src/Analysis/Visiting/UseStatementFetchingVisitor.php b/src/Analysis/Visiting/UseStatementFetchingVisitor.php index 3dda4c2db39c29424099d6d38854c26b49c34565..2a77d83d02e08d7db5ef55135a11c649d720bd11 100644 --- a/src/Analysis/Visiting/UseStatementFetchingVisitor.php +++ b/src/Analysis/Visiting/UseStatementFetchingVisitor.php @@ -92,7 +92,7 @@ final class UseStatementFetchingVisitor extends NodeVisitorAbstract /** * Retrieves a list of namespaces. * - * @return array[] + * @return array> */ public function getNamespaces(): array { diff --git a/src/Autocompletion/CompletionItem.php b/src/Autocompletion/CompletionItem.php index 87c7983e615dc189b0a44115754408a970f27018..d0226af06ec913033d66c65b8ec15014e30f4e9f 100644 --- a/src/Autocompletion/CompletionItem.php +++ b/src/Autocompletion/CompletionItem.php @@ -245,7 +245,7 @@ final class CompletionItem implements JsonSerializable, ArrayAccess /** * @inheritDoc */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { throw new LogicException('Setting properties directly is not allowed, use setters instead'); } @@ -253,7 +253,7 @@ final class CompletionItem implements JsonSerializable, ArrayAccess /** * @inheritDoc */ - public function offsetExists($offset) + public function offsetExists($offset): bool { $array = $this->jsonSerialize(); @@ -263,7 +263,7 @@ final class CompletionItem implements JsonSerializable, ArrayAccess /** * @inheritDoc */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { throw new LogicException('Unsetting properties is not allowed'); } diff --git a/src/Indexing/Iterating/AbsolutePathFilterIterator.php b/src/Indexing/Iterating/AbsolutePathFilterIterator.php index ead4bc4637b6d1782d030b900ec07978b285528d..c17faddb9bc64e7cd1d5a406d97c6ea2c00b85c5 100644 --- a/src/Indexing/Iterating/AbsolutePathFilterIterator.php +++ b/src/Indexing/Iterating/AbsolutePathFilterIterator.php @@ -14,7 +14,7 @@ final class AbsolutePathFilterIterator extends PathFilterIterator /** * @inheritDoc */ - public function accept() + public function accept(): bool { $filename = $this->current()->getPathname(); diff --git a/src/Indexing/SchemaInitializer.php b/src/Indexing/SchemaInitializer.php index 7a33edc114d7f4d219198f432a8c380cab08c068..04ae4e6acaa28c411c84c1dee59cee4cec197d55 100644 --- a/src/Indexing/SchemaInitializer.php +++ b/src/Indexing/SchemaInitializer.php @@ -21,7 +21,7 @@ final class SchemaInitializer public const SCHEMA_VERSION = 30; /** - * @var int + * @var string */ public const VERSION_SETTING_NAME = 'version'; diff --git a/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest.php b/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest.php index 231100392bfff5b643953b853536d136ab22a22e..c4b9588a121aa8cf6ce83fee058d0aa2a67b5d25 100644 --- a/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest.php +++ b/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest.php @@ -39,7 +39,7 @@ final class GlobalConstantsJsonRpcQueueItemHandlerTest extends AbstractIntegrati { $output = $this->getGlobalConstants('DefineWithExpression.phpt'); - self::assertSame('(($version{0} * 10000) + ($version{2} * 100) + $version{4})', $output['\TEST_CONSTANT']['defaultValue']); + self::assertSame('(($version[0] * 10000) + ($version[2] * 100) + $version[4])', $output['\TEST_CONSTANT']['defaultValue']); } /** diff --git a/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest/DefineWithExpression.phpt b/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest/DefineWithExpression.phpt index b6c5b72265445ed67a822181dc0a194b0967ffea..4a04851a245c12f11c85046f9c6c69f0f5fdcbb1 100644 --- a/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest/DefineWithExpression.phpt +++ b/tests/Integration/UserInterface/JsonRpcQueueItemHandler/GlobalConstantsJsonRpcQueueItemHandlerTest/DefineWithExpression.phpt @@ -1,3 +1,3 @@