From 559ad183809c6176b09b09e5eeb3394eeabc9acd Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 24 Feb 2023 12:15:17 +0100 Subject: [PATCH 01/18] Support ArgumentTypeDeclaration --- .../CodeQuality/ArgumentTypeDeclarationSniff.php | 2 +- tests/fixtures/argument-type-declaration.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php b/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php index c715c6d..a22609c 100644 --- a/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php +++ b/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php @@ -40,7 +40,7 @@ public function register() { // phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration - return [T_FUNCTION, T_CLOSURE]; + return [T_FUNCTION, T_CLOSURE, T_FN]; } /** diff --git a/tests/fixtures/argument-type-declaration.php b/tests/fixtures/argument-type-declaration.php index e48435e..72ec9eb 100644 --- a/tests/fixtures/argument-type-declaration.php +++ b/tests/fixtures/argument-type-declaration.php @@ -129,6 +129,18 @@ function () } +fn() => true; + +// @phpcsWarningOnNextLine +fn($foo) => $foo*2; + +fn(int $foo) => $foo*2; + +// @phpcsWarningOnNextLine +fn(int $foo, $bar) => true; + +fn(int $foo, string $bar) => true; + // @phpcsWarningOnNextLine function ($foo) { From 8a5febb67468f909086f954202935c4dd9528685 Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 24 Feb 2023 12:28:52 +0100 Subject: [PATCH 02/18] Add missing tests --- tests/fixtures/argument-type-declaration.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/fixtures/argument-type-declaration.php b/tests/fixtures/argument-type-declaration.php index e48435e..64d70ea 100644 --- a/tests/fixtures/argument-type-declaration.php +++ b/tests/fixtures/argument-type-declaration.php @@ -279,6 +279,21 @@ function (PHPUnit\Exception $meh = null) use ($baz) } } + // @phpcsWarningOnNextLine + public function d(...$foo) { + + } + + // @phpcsWarningOnNextLine + public function e(...$foo) { + $bar = [1, 2, 3]; + + // @phpcsWarningOnNextLine + return function (...$bar) { + + }; + } + public function buildCallback(): callable { /** From d11762b2c20f3d8238d99196942fd3802b33588b Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 24 Feb 2023 12:30:56 +0100 Subject: [PATCH 03/18] Cover array spread operator ase --- tests/fixtures/argument-type-declaration.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/fixtures/argument-type-declaration.php b/tests/fixtures/argument-type-declaration.php index 72ec9eb..1cbca56 100644 --- a/tests/fixtures/argument-type-declaration.php +++ b/tests/fixtures/argument-type-declaration.php @@ -141,6 +141,11 @@ function () fn(int $foo, string $bar) => true; +// @phpcsWarningOnNextLine +fn(...$foo) => true; + +fn(int ...$foo) => true; + // @phpcsWarningOnNextLine function ($foo) { From 063d49a386172b64161c09c6320b51184889cfb6 Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 24 Feb 2023 17:20:22 +0100 Subject: [PATCH 04/18] Add rules for ReturnTypeDeclaration --- Inpsyde/PhpcsHelpers.php | 13 +++++++++++-- .../CodeQuality/ReturnTypeDeclarationSniff.php | 6 ++++-- tests/fixtures/return-type-declaration.php | 9 +++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Inpsyde/PhpcsHelpers.php b/Inpsyde/PhpcsHelpers.php index fc6bd50..1048bdd 100644 --- a/Inpsyde/PhpcsHelpers.php +++ b/Inpsyde/PhpcsHelpers.php @@ -288,7 +288,7 @@ public static function isHookClosure( /** @var array> $tokens */ $tokens = $file->getTokens(); - if (($tokens[$position]['code'] ?? '') !== T_CLOSURE) { + if (!in_array(($tokens[$position]['code'] ?? ''), [T_CLOSURE, T_FN], true)) { return false; } @@ -486,7 +486,7 @@ public static function functionBoundaries(File $file, int $position): array /** @var array> $tokens */ $tokens = $file->getTokens(); - if (!in_array(($tokens[$position]['code'] ?? null), [T_FUNCTION, T_CLOSURE], true)) { + if (!in_array(($tokens[$position]['code'] ?? null), [T_FUNCTION, T_CLOSURE, T_FN], true)) { return [-1, -1]; } @@ -539,6 +539,15 @@ public static function returnsCountInfo(File $file, int $position): array /** @var array> $tokens */ $tokens = $file->getTokens(); + if (T_FN === $tokens[$position]['code'] ?? null) { + return [ + 'nonEmpty' => 1, + 'void' => 0, + 'null' => 0, + 'total' => 1, + ]; + } + $pos = $start + 1; while ($pos < $end) { list(, $innerFunctionEnd) = self::functionBoundaries($file, $pos); diff --git a/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php b/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php index 44561e3..e2dd329 100644 --- a/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php +++ b/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php @@ -38,7 +38,7 @@ public function register() { // phpcs:enable Inpsyde.CodeQuality - return [T_FUNCTION, T_CLOSURE]; + return [T_FUNCTION, T_CLOSURE, T_FN]; } /** @@ -170,7 +170,9 @@ private function maybeErrors( return; } - $name = (string)$file->getDeclarationName($position); + $tokenCode = $file->getTokens()[$position]['code'] ?? ''; + // TODO Remove check on T_FN if getDeclarationName() will support T_FN out of the box + $name = $tokenCode !== T_FN ? (string)$file->getDeclarationName($position) : ''; if ( PhpcsHelpers::functionIsMethod($file, $position) && (in_array($name, self::METHODS_WHITELIST, true) || strpos($name, '__') === 0) diff --git a/tests/fixtures/return-type-declaration.php b/tests/fixtures/return-type-declaration.php index 7586634..72e7b67 100644 --- a/tests/fixtures/return-type-declaration.php +++ b/tests/fixtures/return-type-declaration.php @@ -218,6 +218,11 @@ function filter_wrapper(): bool return true; } +// @phpcsWarningCodeOnNextLine NoReturnType +fn() => true; + +fn(): bool => true; + /** * @return string * @wp-hook Meh @@ -266,6 +271,8 @@ public function filterWrapper(string $x, int $y): bool return "$x, $y"; }); + add_filter('x', fn($x, $y) => "$x, $y"); + return true; } @@ -283,6 +290,8 @@ function ($x, $y) { 10, 2 ); + + add_action('x', fn($x, $y) => "$x, $y"); } // @phpcsWarningCodeOnNextLine NoReturnType From f36c4665187b081b8f84bbf2c88b0cbcbf52089d Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 24 Feb 2023 17:26:03 +0100 Subject: [PATCH 05/18] Cover hooks cases for ArgumentTypeDeclaration --- tests/fixtures/argument-type-declaration.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/fixtures/argument-type-declaration.php b/tests/fixtures/argument-type-declaration.php index 1cbca56..0249ad1 100644 --- a/tests/fixtures/argument-type-declaration.php +++ b/tests/fixtures/argument-type-declaration.php @@ -91,6 +91,8 @@ function i(string $foo, ArrayObject ...$bar) } +add_action('foo', fn ($foo) => 'x'); + add_action( 'foo', function ($foo) { @@ -117,6 +119,8 @@ static function ($foo) { } ); +add_filter('foo', fn ($foo) => 'x'); + array_map( function ($foo) { // @phpcsWarningOnThisLine From 62544a2d4022b958b1de86ff66551b07a8b9a99e Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 5 May 2023 12:29:07 +0200 Subject: [PATCH 06/18] Remove pointless TODO message Apparently `getDeclarationName` will never support T_FN. --- Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php b/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php index e2dd329..af97b61 100644 --- a/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php +++ b/Inpsyde/Sniffs/CodeQuality/ReturnTypeDeclarationSniff.php @@ -171,8 +171,8 @@ private function maybeErrors( } $tokenCode = $file->getTokens()[$position]['code'] ?? ''; - // TODO Remove check on T_FN if getDeclarationName() will support T_FN out of the box $name = $tokenCode !== T_FN ? (string)$file->getDeclarationName($position) : ''; + if ( PhpcsHelpers::functionIsMethod($file, $position) && (in_array($name, self::METHODS_WHITELIST, true) || strpos($name, '__') === 0) From 1e85d9a53bf6312a5eec4ffd5dea7e73e44d8f82 Mon Sep 17 00:00:00 2001 From: Antonio Mangiacapra <11173091+antonioeatgoat@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:55:22 +0200 Subject: [PATCH 07/18] Update dependencies --- composer.json | 7 +++---- phpcs.xml | 10 ++++++---- tests/cases/FixturesTest.php | 1 + tests/src/FixtureContentParser.php | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index ff6f4fa..7644e18 100644 --- a/composer.json +++ b/composer.json @@ -29,11 +29,10 @@ ], "minimum-stability": "stable", "require": { - "php": ">=7", + "php": ">=7.4", "squizlabs/php_codesniffer": "^3.6.0", - "dealerdirect/phpcodesniffer-composer-installer": "~0.7.0", - "wp-coding-standards/wpcs": "^2.3", - "automattic/vipwpcs": "^2.2", + "dealerdirect/phpcodesniffer-composer-installer": "~1.0.0", + "automattic/vipwpcs": "dev-3.0/updates-for-wpcs-3.0", "phpcompatibility/php-compatibility": "^9.3.5", "automattic/phpcs-neutron-standard": "^1.6.0" }, diff --git a/phpcs.xml b/phpcs.xml index 8a81c7a..dec7c58 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -60,9 +60,7 @@ Curated list of WordPress specific rules. See https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards --> - - - + @@ -98,7 +96,6 @@ - @@ -152,6 +149,8 @@ Generic --> + + @@ -161,6 +160,7 @@ + @@ -173,6 +173,8 @@ + + - - + - - - - - - warning - - - warning - - - warning - - - warning - - - warning - - - warning - - - warning - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 76b6d6e..d997102 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,22 +1,20 @@ - - - tests/cases/FixturesTest.php - - - tests/cases/PhpcsHelpersTest.php - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" + bootstrap="tests/bootstrap.php" + colors="true"> + + + ./Inpsyde + + + + + tests/cases/FixturesTest.php + + + tests/cases/PhpcsHelpersTest.php + + diff --git a/psalm.xml b/psalm.xml index ee09b08..53e46c1 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,11 +1,14 @@ - - - diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 878abe6..6fd220d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,6 @@ -parse($fixtureFile); + [$sniffClass, $expected, $properties] = $parser->parse($fixtureFile); $file = $this->createPhpcsForFixture($sniffClass, $fixtureFile, $properties); $actual = (new SniffMessagesExtractor($file))->extractMessages(); @@ -105,7 +103,7 @@ private function validateCodes( SniffMessages $actual, string $fixture, string $sniffClass - ) { + ): void { $where = sprintf("\nin fixture file '%s' line %%d\nfor sniff '%s'", $fixture, $sniffClass); @@ -136,7 +134,7 @@ private function validateCode( string $code, string $where, string $actualCode = null - ) { + ): void { $message = $code ? sprintf('Expected %s code \'%s\' was not found', $type, $code) @@ -158,7 +156,7 @@ private function validateTotals( SniffMessages $actual, string $fixtureFile, string $sniffClass - ) { + ): void { $expectedTotal = $expected->total(); $actualTotal = $actual->total(); @@ -195,22 +193,20 @@ private function createPhpcsForFixture( array $properties ): File { - $sniffName = str_replace('.', DIRECTORY_SEPARATOR, $sniffName) . 'Sniff'; - $sniffFile = getenv('SNIFFS_PATH') . DIRECTORY_SEPARATOR . "{$sniffName}.php"; - if (!file_exists($sniffFile) || !is_readable($sniffFile)) { + $sniffFile = str_replace('.', '/', "{$sniffName}Sniff"); + $sniffPath = getenv('SNIFFS_PATH') . "/{$sniffFile}.php"; + if (!file_exists($sniffPath) || !is_readable($sniffPath)) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - throw new Exception("Non-existent of unreadable sniff file '$sniffFile' found."); + throw new Exception("Non-existent of unreadable sniff file '{$sniffPath}' found."); } $config = new Config(); - $config->standards = []; - /** @var Ruleset $ruleset */ - $ruleset = (new \ReflectionClass(Ruleset::class))->newInstanceWithoutConstructor(); - $ruleset->registerSniffs([$sniffFile], [], []); - $ruleset->populateTokenListeners(); + $config->standards = [dirname(getenv('SNIFFS_PATH'))]; + $config->sniffs = ["Inpsyde.{$sniffName}"]; + $ruleset = new Ruleset($config); $baseSniffNamespace = getenv('SNIFFS_NAMESPACE'); - $sniffFqn = str_replace(DIRECTORY_SEPARATOR, '\\', $sniffName); + $sniffFqn = str_replace('/', '\\', $sniffFile); foreach ($properties as $name => $value) { $ruleset->setSniffProperty("{$baseSniffNamespace}\\{$sniffFqn}", $name, $value); } diff --git a/tests/cases/PhpcsHelpersTest.php b/tests/cases/PhpcsHelpersTest.php index d94b1e3..bbdc19e 100644 --- a/tests/cases/PhpcsHelpersTest.php +++ b/tests/cases/PhpcsHelpersTest.php @@ -1,5 +1,14 @@ readFile($fixturePath) as $lineNum => $line) { $this->readLine($lineNum, $line, $accumulator); } - // phpcs:enable return $this->processResults($accumulator, $fixturePath); } /** - * @param \stdClass $accumulator + * @param object $accumulator * @param string $fixturePath * @return array */ - private function processResults(\stdClass $accumulator, string $fixturePath): array + private function processResults(object $accumulator, string $fixturePath): array { - $results = [ - $accumulator->sniff, - $accumulator->messages, - $accumulator->warnings, - $accumulator->errors, - $accumulator->properties->values, - ]; - if (!$accumulator->process->content) { return [ - $this->checkSniffName(array_shift($results)), - new SniffMessages($results[1], $results[2], $results[0]), + $this->checkSniffName($accumulator->sniff), + new SniffMessages( + $accumulator->warnings, + $accumulator->errors, + $accumulator->messages + ), $accumulator->properties->values, ]; } // phpcs:disable eval("\$cb = {$accumulator->process->content};"); - /** @var callable $cb */ - $results = $cb(...$results); + $params = [ + $accumulator->sniff, + $accumulator->messages, + $accumulator->warnings, + $accumulator->errors, + $accumulator->properties->values, + ]; + /** @var mixed $cb */ + $results = is_callable($cb) ? $cb(...$params) : null; // phpcs:enable + $results = array_values(array_pad(is_array($results) ? $results : [], 5, null)); + [$sniff, $messages, $warnings, $errors, $properties] = $results; + if ( - $accumulator->process->content - && !is_array($results) - || count($results) !== 5 - || !is_string($results[0] ?? null) - || !is_array($results[1] ?? null) - || !is_array($results[2] ?? null) - || !is_array($results[3] ?? null) - || !is_array($results[4] ?? null) + !is_string($sniff) + || !is_array($messages) + || !is_array($warnings) + || !is_array($errors) + || !is_array($properties) ) { - throw new Exception( - // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped + throw new \Error( sprintf( "Process callback for fixture '%s' (lines #%s:#%s) returned invalid output.", $fixturePath, $accumulator->process->start, $accumulator->process->end ) - // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped ); } return [ - $this->checkSniffName(array_shift($results)), - new SniffMessages($results[1], $results[2], $results[0]), - $results[3], + $this->checkSniffName($sniff), + new SniffMessages($warnings, $errors, $messages), + $properties, ]; } @@ -127,10 +122,10 @@ private function processResults(\stdClass $accumulator, string $fixturePath): ar * @param string|null $sniff * @return string */ - private function checkSniffName(string $sniff = null): string + private function checkSniffName(?string $sniff): string { if ($sniff === null) { - throw new Exception("No sniff class found for the test."); + throw new \Error("No sniff class found for the test."); } static $regex; @@ -140,8 +135,7 @@ private function checkSniffName(string $sniff = null): string } if (!preg_match('~^' . $regex . '$~', $sniff)) { - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - throw new Exception("Invalid sniff name '{$sniff}'."); + throw new \Error("Invalid sniff name '{$sniff}'."); } return $sniff; @@ -154,6 +148,10 @@ private function checkSniffName(string $sniff = null): string private function readFile(string $file): \Generator { $handle = fopen($file, 'rb'); + if ($handle === false) { + throw new \Error("Could not open '{$file}' for reading."); + } + $lineNum = 1; $line = fgets($handle); @@ -168,9 +166,9 @@ private function readFile(string $file): \Generator /** * @param int $lineNum * @param string $line - * @param \stdClass $accumulator + * @param object $accumulator */ - private function readLine(int $lineNum, string $line, \stdClass $accumulator) + private function readLine(int $lineNum, string $line, object $accumulator): void { if ( !$this->readProcessLine($lineNum, $line, $accumulator) @@ -184,10 +182,10 @@ private function readLine(int $lineNum, string $line, \stdClass $accumulator) /** * @param int $lineNum * @param string $line - * @param \stdClass $accumulator + * @param object $accumulator * @return bool */ - private function readProcessLine(int $lineNum, string $line, \stdClass $accumulator): bool + private function readProcessLine(int $lineNum, string $line, object $accumulator): bool { if ($accumulator->process->end !== false) { return false; @@ -213,10 +211,10 @@ private function readProcessLine(int $lineNum, string $line, \stdClass $accumula /** * @param string $line - * @param \stdClass $accumulator + * @param object $accumulator * @return bool */ - private function readSniffLine(string $line, \stdClass $accumulator): bool + private function readSniffLine(string $line, object $accumulator): bool { if ($accumulator->sniff) { return false; @@ -238,10 +236,10 @@ private function readSniffLine(string $line, \stdClass $accumulator): bool /** * @param int $lineNum * @param string $line - * @param \stdClass $accumulator + * @param object $accumulator * @return bool */ - private function readPropertiesLine(int $lineNum, string $line, \stdClass $accumulator): bool + private function readPropertiesLine(int $lineNum, string $line, object $accumulator): bool { static $pattern; $pattern or $pattern = '~\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*=\s*([^;]+);~'; @@ -274,9 +272,9 @@ private function readPropertiesLine(int $lineNum, string $line, \stdClass $accum /** * @param int $lineNum * @param string $line - * @param \stdClass $accumulator + * @param object $accumulator */ - private function readTokenLine(int $lineNum, string $line, \stdClass $accumulator) + private function readTokenLine(int $lineNum, string $line, object $accumulator): void { static $pattern; if (!$pattern) { diff --git a/tests/src/SniffMessages.php b/tests/src/SniffMessages.php index c5aa846..339b8f5 100644 --- a/tests/src/SniffMessages.php +++ b/tests/src/SniffMessages.php @@ -1,9 +1,6 @@ messagesContainTotal = true; } @@ -69,7 +53,7 @@ public function messages(): array * @param int $line * @return string|null */ - public function messageIn(int $line) + public function messageIn(int $line): ?string { return $this->messages[$line] ?? null; } @@ -99,7 +83,7 @@ public function errors(): array * @param int $line * @return string|null */ - public function errorIn(int $line) + public function errorIn(int $line): ?string { return $this->errors[$line] ?? null; } @@ -124,7 +108,7 @@ public function warnings(): array * @param int $line * @return string|null */ - public function warningIn(int $line) + public function warningIn(int $line): ?string { return $this->warnings[$line] ?? null; } diff --git a/tests/src/SniffMessagesExtractor.php b/tests/src/SniffMessagesExtractor.php index 6f8f8b7..1f98b27 100644 --- a/tests/src/SniffMessagesExtractor.php +++ b/tests/src/SniffMessagesExtractor.php @@ -1,9 +1,6 @@ file->process(); - list($warnings, $errors) = $this->normalize( + [$warnings, $errors] = $this->normalize( $this->file->getWarnings(), $this->file->getErrors() ); From 6a404be1eb4dfbfe3ba28013a3f783c30a64e72f Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Sat, 26 Aug 2023 00:29:04 +0200 Subject: [PATCH 14/18] Make use of reusable workflows for QA --- .github/workflows/php-qa.yml | 76 --------------------- .github/workflows/quality-assurance-php.yml | 51 ++++++++++++++ 2 files changed, 51 insertions(+), 76 deletions(-) delete mode 100644 .github/workflows/php-qa.yml create mode 100644 .github/workflows/quality-assurance-php.yml diff --git a/.github/workflows/php-qa.yml b/.github/workflows/php-qa.yml deleted file mode 100644 index 8cf444a..0000000 --- a/.github/workflows/php-qa.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: PHP Quality Assurance -on: - push: - pull_request: - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -jobs: - static-qa: - runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'no static qa') }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - coverage: none - tools: cs2pr - - - name: Install dependencies - uses: ramsey/composer-install@v2 - - - name: Check code styles - run: | - ./vendor/bin/phpcs -q --report-full --report-checkstyle="phpcs-report.xml" --runtime-set testVersion 7.0- --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 - cs2pr --graceful-warnings phpcs-report.xml - - - name: Check Psalm - run: ./vendor/bin/psalm --output-format=github --no-cache - - unit-tests: - runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'no unit tests') }} - strategy: - fail-fast: true - matrix: - php-ver: [ '7.4', '8.0', '8.1', '8.2' ] - deps-mode: [ 'lowest', 'highest' ] - include: - - php-ver: '8.1' - dependency-versions: 'highest' - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-ver }} - ini-values: zend.assertions=1, error_reporting=-1, display_errors=On - coverage: none - tools: cs2pr, parallel-lint:^1.3.1 - - - name: Check syntax error in sources - if: ${{ (matrix.deps-mode == 'highest') }} - run: parallel-lint ./Inpsyde/ ./tests/src/ ./tests/cases/ --checkstyle | cs2pr - - - name: Remove Psalm before tests to prevent installation conflicts - run: composer remove vimeo/psalm --no-update - - - name: Install dependencies - uses: ramsey/composer-install@v2 - with: - dependency-versions: ${{ matrix.deps-mode }} - - - name: Migrate PHPUnit config for PHPUnit 9 - if: ${{ (matrix.php-ver >= 7.3) && (matrix.deps-mode == 'highest') }} - run: ./vendor/bin/phpunit --migrate-configuration - - - name: Run unit tests - run: ./vendor/bin/phpunit --testsuite=unit --no-coverage diff --git a/.github/workflows/quality-assurance-php.yml b/.github/workflows/quality-assurance-php.yml new file mode 100644 index 0000000..cee505f --- /dev/null +++ b/.github/workflows/quality-assurance-php.yml @@ -0,0 +1,51 @@ +name: PHP Quality Assurance + +on: + push: + paths: + - '**workflows/quality-assurance-php.yml' + - '**.php' + - '**phpcs.xml.dist' + - '**phpunit.xml.dist' + - '**psalm.xml' + workflow_dispatch: + inputs: + jobs: + required: true + type: choice + default: 'Run all' + description: 'Choose jobs to run' + options: + - 'Run all' + - 'Run PHPCS only' + - 'Run Psalm only' + - 'Run lint only' + - 'Run static analysis' + - 'Run unit tests only' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-php: + uses: inpsyde/reusable-workflows/.github/workflows/lint-php.yml@main + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run lint only') || (github.event.inputs.jobs == 'Run static analysis')) }} + with: + PHP_MATRIX: '["7.4", "8.0", "8.1", "8.2"]' + LINT_ARGS: '-e php --colors --show-deprecated ./Inpsyde' + + coding-standards-analysis-php: + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run PHPCS only') || (github.event.inputs.jobs == 'Run static analysis')) }} + uses: inpsyde/reusable-workflows/.github/workflows/coding-standards-php.yml@main + + static-code-analysis-php: + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run Psalm only') || (github.event.inputs.jobs == 'Run static analysis')) }} + uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-php.yml@main + + tests-unit-php: + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run unit tests only')) }} + uses: inpsyde/reusable-workflows/.github/workflows/tests-unit-php.yml@main + with: + PHP_MATRIX: '["7.4", "8.0", "8.1", "8.2"]' + PHPUNIT_ARGS: '--no-coverage' From 9bf1e9ca1d184388b0bd8889d451d0c860f58cb1 Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Sat, 26 Aug 2023 00:54:50 +0200 Subject: [PATCH 15/18] Fix CS --- Inpsyde/PhpcsHelpers.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Inpsyde/PhpcsHelpers.php b/Inpsyde/PhpcsHelpers.php index 27c5403..f33f8f1 100644 --- a/Inpsyde/PhpcsHelpers.php +++ b/Inpsyde/PhpcsHelpers.php @@ -540,13 +540,8 @@ public static function returnsCountInfo(File $file, int $position): array /** @var array> $tokens */ $tokens = $file->getTokens(); - if (T_FN === $tokens[$position]['code'] ?? null) { - return [ - 'nonEmpty' => 1, - 'void' => 0, - 'null' => 0, - 'total' => 1, - ]; + if (T_FN === ($tokens[$position]['code'] ?? null)) { + return ['nonEmpty' => 1, 'void' => 0, 'null' => 0, 'total' => 1]; } $pos = $start + 1; From 2ec02f5ab7f13251ee7860ce78db5ee999ac7043 Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Sat, 26 Aug 2023 01:04:12 +0200 Subject: [PATCH 16/18] Fix tests --- .../CodeQuality/ArgumentTypeDeclarationSniff.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php b/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php index 34dc9fb..7afdbb0 100644 --- a/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php +++ b/Inpsyde/Sniffs/CodeQuality/ArgumentTypeDeclarationSniff.php @@ -52,12 +52,13 @@ public function process(File $phpcsFile, $stackPtr): void // phpcs:enable Inpsyde.CodeQuality.ArgumentTypeDeclaration // phpcs:enable Generic.Metrics.CyclomaticComplexity - if ($this->shouldIgnore($phpcsFile, $stackPtr)) { + /** @var array> $tokens */ + $tokens = $phpcsFile->getTokens(); + + if ($this->shouldIgnore($phpcsFile, $stackPtr, $tokens)) { return; } - /** @var array> $tokens */ - $tokens = $phpcsFile->getTokens(); $paramsStart = (int)($tokens[$stackPtr]['parenthesis_opener'] ?? 0); $paramsEnd = (int)($tokens[$stackPtr]['parenthesis_closer'] ?? 0); @@ -91,15 +92,17 @@ public function process(File $phpcsFile, $stackPtr): void /** * @param File $phpcsFile * @param int $stackPtr + * @param array> $tokens * @return bool * * phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration */ - private function shouldIgnore(File $phpcsFile, $stackPtr): bool + private function shouldIgnore(File $phpcsFile, $stackPtr, array $tokens): bool { // phpcs:enable Inpsyde.CodeQuality.ArgumentTypeDeclaration - $name = $phpcsFile->getDeclarationName($stackPtr); + $tokenCode = $tokens[$stackPtr]['code'] ?? ''; + $name = ($tokenCode !== T_FN) ? ($phpcsFile->getDeclarationName($stackPtr) ?: '') : ''; return PhpcsHelpers::functionIsArrayAccess($phpcsFile, $stackPtr) || PhpcsHelpers::isHookClosure($phpcsFile, $stackPtr) From 8c92e990403adc62aafa7c5ceb4e55ba6916cd62 Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Sat, 26 Aug 2023 01:15:23 +0200 Subject: [PATCH 17/18] Add support for static short closures --- Inpsyde/PhpcsHelpers.php | 38 ++++++++++--------- .../Sniffs/CodeQuality/StaticClosureSniff.php | 4 +- tests/fixtures/static-closure.php | 14 +++++++ 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Inpsyde/PhpcsHelpers.php b/Inpsyde/PhpcsHelpers.php index f33f8f1..64dde5a 100644 --- a/Inpsyde/PhpcsHelpers.php +++ b/Inpsyde/PhpcsHelpers.php @@ -342,7 +342,7 @@ public static function functionDocBlockTags( if ( !array_key_exists($position, $tokens) - || !in_array($tokens[$position]['code'], [T_FUNCTION, T_CLOSURE], true) + || !in_array($tokens[$position]['code'], [T_FUNCTION, T_CLOSURE, T_FN], true) ) { return []; } @@ -480,7 +480,7 @@ public static function functionBody(File $file, int $position): string /** * @param File $file * @param int $position - * @return array{int, int} + * @return list{int, int} */ public static function functionBoundaries(File $file, int $position): array { @@ -491,19 +491,13 @@ public static function functionBoundaries(File $file, int $position): array return [-1, -1]; } - $functionStart = (int)($tokens[$position]['scope_opener'] ?? 0); - $functionEnd = (int)($tokens[$position]['scope_closer'] ?? 0); - if ($functionStart <= 0 || $functionEnd <= 0 || $functionStart >= ($functionEnd - 1)) { - return [-1, -1]; - } - - return [$functionStart, $functionEnd]; + return static::boundaries($tokens, $position); } /** * @param File $file * @param int $position - * @return array{int, int} + * @return list{int, int} */ public static function classBoundaries(File $file, int $position): array { @@ -514,13 +508,7 @@ public static function classBoundaries(File $file, int $position): array return [-1, -1]; } - $start = (int)($tokens[$position]['scope_opener'] ?? 0); - $end = (int)($tokens[$position]['scope_closer'] ?? 0); - if ($start <= 0 || $end <= 0 || $start >= ($end - 1)) { - return [-1, -1]; - } - - return [$start, $end]; + return static::boundaries($tokens, $position); } /** @@ -728,4 +716,20 @@ public static function isUntypedPsrMethod(File $file, int $position): bool return false; } + + /** + * @param array> $tokens + * @param int $position + * @return list{int, int} + */ + private static function boundaries(array $tokens, int $position): array + { + $start = (int)($tokens[$position]['scope_opener'] ?? 0); + $end = (int)($tokens[$position]['scope_closer'] ?? 0); + if ($start <= 0 || $end <= 0 || $start >= ($end - 1)) { + return [-1, -1]; + } + + return [$start, $end]; + } } diff --git a/Inpsyde/Sniffs/CodeQuality/StaticClosureSniff.php b/Inpsyde/Sniffs/CodeQuality/StaticClosureSniff.php index fe31154..e9d62bd 100644 --- a/Inpsyde/Sniffs/CodeQuality/StaticClosureSniff.php +++ b/Inpsyde/Sniffs/CodeQuality/StaticClosureSniff.php @@ -11,11 +11,11 @@ class StaticClosureSniff implements Sniff { /** - * @return list + * @return list */ public function register(): array { - return [T_CLOSURE]; + return [T_CLOSURE, T_FN]; } /** diff --git a/tests/fixtures/static-closure.php b/tests/fixtures/static-closure.php index 9460d49..3aa137c 100644 --- a/tests/fixtures/static-closure.php +++ b/tests/fixtures/static-closure.php @@ -32,6 +32,10 @@ function () { return $foo; }; +fn() => $this; + +static fn() => 'Foo'; + static function () { return 'Foo'; }; @@ -60,6 +64,8 @@ public function b() return 'Foo'; }; + $a = fn () => 'x'; // @phpcsWarningOnThisLine + return $a; } @@ -80,5 +86,13 @@ function () { function () { return 'Foo'; }; + + /** @bound */ + fn() => 'Foo'; + + fn() => $this; } } + +add_filter('x', fn() => 'y'); // @phpcsWarningOnThisLine +add_filter('x', static fn() => 'y'); From efa0869589b2e72613c4fde94dbdc2ffa32a208f Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Sat, 26 Aug 2023 01:27:03 +0200 Subject: [PATCH 18/18] Update branch alias --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 909ae1b..ea4c1a9 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ }, "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-version/1": "1.x-dev", + "dev-version/2": "2.x-dev" } }, "scripts" : {