diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d33ede7..a3638c8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,15 +9,29 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.1', '8.2', '8.3'] + php-versions: ['8.2', '8.3'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-versions }} + php-version: ${{ matrix.version }} + coverage: xdebug + + - name: Install + run: make install + + - name: Run linter + run: make lint analyse - - run: make install - - run: make lint - run: make test + + # - name: Run test & publish code coverage + # uses: paambaati/codeclimate-action@v9.0.0 + # env: + # CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + # with: + # coverageCommand: make test-coverage + # coverageLocations: ${{github.workplace}}/build/logs/clover.xml:clover + # debug: true diff --git a/.gitignore b/.gitignore index e2a3893..9adc4b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /vendor/ composer.lock *.cache +build +html-coverage diff --git a/Makefile b/Makefile index 60879a0..386c471 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,19 @@ test: composer exec -v phpunit tests lint: - composer exec -v phpcs src tests + composer exec -v phpcs + +analyse: composer exec -v phpstan -- analyse -c phpstan.neon lint-fix: - composer exec phpcbf -- --standard=PSR12 -v src tests + composer exec -v phpcbf test-coverage: - composer exec --verbose phpunit tests -- --coverage-clover build/logs/clover.xml + XDEBUG_MODE=coverage composer exec --verbose phpunit tests -- --coverage-clover=build/logs/clover.xml + +test-coverage-text: + XDEBUG_MODE=coverage composer exec --verbose phpunit tests -- --coverage-text + +test-coverage-html: + XDEBUG_MODE=coverage composer exec --verbose phpunit tests -- --coverage-html=html-coverage diff --git a/composer.json b/composer.json index 8f3fd41..b4d5186 100644 --- a/composer.json +++ b/composer.json @@ -1,17 +1,18 @@ { "name": "hexlet/phpstan-fp", "description": "PHPStan rules for functional programming", + "version": "3.0.0", "type": "library", "require": { - "php": ">=8.1", - "phpstan/phpstan": "^1.10.24", - "illuminate/collections": "^10.14.1" + "php": ">=8.2", + "phpstan/phpstan": "^2.1", + "illuminate/collections": "^12" }, "license": "MIT", "autoload": { "psr-4": { - "Hexlet\\PHPStanFp\\": "src/", - "Hexlet\\PHPStanFp\\Tests\\": "tests/" + "Hexlet\\PHPStanFp\\": "src/", + "Hexlet\\PHPStanFp\\Tests\\": "tests/" } }, "extra": { @@ -22,7 +23,7 @@ } }, "require-dev": { - "phpunit/phpunit": "^9.5.18", - "squizlabs/php_codesniffer": "^3.6.2" + "phpunit/phpunit": "^11.5 | ^12.0", + "squizlabs/php_codesniffer": "^3.9" } } diff --git a/phpcs.xml b/phpcs.xml index 05b845c..a54e710 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,11 +1,14 @@ PSR12 - - + + + + src + tests vendor/* tests/*/data/* diff --git a/phpstan.neon b/phpstan.neon index c844d27..7cc36b6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,9 +3,5 @@ parameters: paths: - src - tests - excludePaths: - tests/*/data/* - - checkGenericClassInNonGenericObjectType: false - reportUnmatchedIgnoredErrors: false diff --git a/phpunit.xml b/phpunit.xml index 550fb89..c373a75 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,28 @@ - ./tests/ + + + + src + + diff --git a/src/Rules/Classes/DisallowClassesRule.php b/src/Rules/Classes/DisallowClassesRule.php index 4e5d59a..f2ba3da 100644 --- a/src/Rules/Classes/DisallowClassesRule.php +++ b/src/Rules/Classes/DisallowClassesRule.php @@ -2,10 +2,12 @@ namespace Hexlet\PHPStanFp\Rules\Classes; -use PHPStan\Rules\Rule; use PhpParser\Node; -use PHPStan\Analyser\Scope; use PhpParser\Node\Stmt\Class_; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\IdentifierRuleError; class DisallowClassesRule implements Rule { @@ -22,15 +24,22 @@ public function getNodeType(): string } /** - * @param \PhpParser\Node\Stmt\Class_ $node - * @param \PHPStan\Analyser\Scope $scope - * @return string[] + * @param Class_ $node + * @param Scope $scope + * @return IdentifierRuleError[] */ public function processNode(Node $node, Scope $scope): array { if (!$this->disallowClasses) { return []; } - return ['Should not use classes']; + + $errorMessage = 'Should not use classes'; + + return [ + RuleErrorBuilder::message($errorMessage) + ->identifier('phpstanFunctionalProgramming.disallowClasses') + ->build() + ]; } } diff --git a/src/Rules/Exceptions/DisallowThrowRule.php b/src/Rules/Exceptions/DisallowThrowRule.php index d37ace5..4ac36d2 100644 --- a/src/Rules/Exceptions/DisallowThrowRule.php +++ b/src/Rules/Exceptions/DisallowThrowRule.php @@ -2,10 +2,12 @@ namespace Hexlet\PHPStanFp\Rules\Exceptions; -use PHPStan\Rules\Rule; use PhpParser\Node; +use PhpParser\Node\Expr\Throw_; use PHPStan\Analyser\Scope; -use PhpParser\Node\Stmt\Throw_; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\IdentifierRuleError; class DisallowThrowRule implements Rule { @@ -22,15 +24,22 @@ public function getNodeType(): string } /** - * @param \PhpParser\Node\Stmt\Throw_ $node - * @param \PHPStan\Analyser\Scope $scope - * @return string[] + * @param Throw_ $node + * @param Scope $scope + * @return IdentifierRuleError[] */ public function processNode(Node $node, Scope $scope): array { if (!$this->disallowThrow) { return []; } - return ['Should not use throw']; + + $errorMessage = 'Should not use throw'; + + return [ + RuleErrorBuilder::message($errorMessage) + ->identifier('phpstanFunctionalProgramming.disallowThrow') + ->build() + ]; } } diff --git a/src/Rules/Expression/DisallowUnusedExpressionRule.php b/src/Rules/Expression/DisallowUnusedExpressionRule.php index 0c9a398..59b289a 100644 --- a/src/Rules/Expression/DisallowUnusedExpressionRule.php +++ b/src/Rules/Expression/DisallowUnusedExpressionRule.php @@ -2,10 +2,12 @@ namespace Hexlet\PHPStanFp\Rules\Expression; -use PHPStan\Rules\Rule; use PhpParser\Node; -use PHPStan\Analyser\Scope; use PhpParser\Node\Stmt\Expression; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\IdentifierRuleError; class DisallowUnusedExpressionRule implements Rule { @@ -22,9 +24,9 @@ public function getNodeType(): string } /** - * @param \PhpParser\Node\Stmt\Expression $node - * @param \PHPStan\Analyser\Scope $scope - * @return string[] + * @param Expression $node + * @param Scope $scope + * @return IdentifierRuleError[] */ public function processNode(Node $node, Scope $scope): array { @@ -36,6 +38,10 @@ public function processNode(Node $node, Scope $scope): array return []; } - return ['Enforce that an expression gets used']; + return [ + RuleErrorBuilder::message('Enforce that an expression gets used') + ->identifier('phpstanFunctionalProgramming.disallowUnusedExpression') + ->build() + ]; } } diff --git a/src/Rules/Functions/DisallowMutatingFunctionsRule.php b/src/Rules/Functions/DisallowMutatingFunctionsRule.php index e9e0483..29083c7 100644 --- a/src/Rules/Functions/DisallowMutatingFunctionsRule.php +++ b/src/Rules/Functions/DisallowMutatingFunctionsRule.php @@ -2,10 +2,12 @@ namespace Hexlet\PHPStanFp\Rules\Functions; -use PHPStan\Rules\Rule; use PhpParser\Node; -use PHPStan\Analyser\Scope; use PhpParser\Node\Expr\FuncCall; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\IdentifierRuleError; class DisallowMutatingFunctionsRule implements Rule { @@ -46,9 +48,9 @@ public function getNodeType(): string } /** - * @param \PhpParser\Node\Expr\FuncCall $node - * @param \PHPStan\Analyser\Scope $scope - * @return string[] + * @param FuncCall $node + * @param Scope $scope + * @return IdentifierRuleError[] */ public function processNode(Node $node, Scope $scope): array { @@ -61,10 +63,17 @@ public function processNode(Node $node, Scope $scope): array } $name = $node->name->getFirst(); + if (!in_array($name, $this->mutatingFunctionsNames)) { return []; } - return ["The use of function '{$name}' is not allowed as it might be a mutating function"]; + $errorMessage = "The use of function '{$name}' is not allowed as it might be a mutating function"; + + return [ + RuleErrorBuilder::message($errorMessage) + ->identifier('phpstanFunctionalProgramming.disallowMutatingFunctions') + ->build() + ]; } } diff --git a/src/Rules/Loops/DisallowLoopsRule.php b/src/Rules/Loops/DisallowLoopsRule.php index ec92e6a..c1c603f 100644 --- a/src/Rules/Loops/DisallowLoopsRule.php +++ b/src/Rules/Loops/DisallowLoopsRule.php @@ -2,9 +2,11 @@ namespace Hexlet\PHPStanFp\Rules\Loops; -use PHPStan\Rules\Rule; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\IdentifierRuleError; abstract class DisallowLoopsRule implements Rule { @@ -15,13 +17,22 @@ public function __construct(bool $disallowLoops) $this->disallowLoops = $disallowLoops; } + /** + * @param Node $node + * @param Scope $scope + * @return IdentifierRuleError[] + */ public function processNode(Node $node, Scope $scope): array { if (!$this->disallowLoops) { return []; } - return ["Should not use loop {$this->getLoopType()}"]; + return [ + RuleErrorBuilder::message("Should not use loop {$this->getLoopType()}") + ->identifier('phpstanFunctionalProgramming.disallowLoops') + ->build() + ]; } abstract protected function getLoopType(): string; diff --git a/src/Rules/Variables/DisallowMutationRule.php b/src/Rules/Variables/DisallowMutationRule.php index a7f6681..01cd0c9 100644 --- a/src/Rules/Variables/DisallowMutationRule.php +++ b/src/Rules/Variables/DisallowMutationRule.php @@ -2,9 +2,11 @@ namespace Hexlet\PHPStanFp\Rules\Variables; -use PHPStan\Rules\Rule; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\IdentifierRuleError; abstract class DisallowMutationRule implements Rule { @@ -16,9 +18,9 @@ public function __construct(bool $disallowVariablesMutation) } /** - * @param \PhpParser\Node\Expr $node - * @param \PHPStan\Analyser\Scope $scope - * @return string[] + * @param Node $node + * @param Scope $scope + * @return IdentifierRuleError[] */ public function processNode(Node $node, Scope $scope): array { @@ -30,24 +32,35 @@ public function processNode(Node $node, Scope $scope): array return []; } - $errorMessage = 'Should not use of mutating operators'; - switch ($node->var->getType()) { case 'Expr_ArrayDimFetch': - return [$errorMessage]; - - case 'Expr_Array': + return [ + $this->buildError() + ]; + case 'Expr_List': $containsTypedItems = collect($node->var->items) ->map(fn($item) => $item->value->name) ->filter(fn($name) => !is_null($name)) ->contains(fn($name) => $scope->hasVariableType($name)->yes()); - return $containsTypedItems ? [$errorMessage] : []; - case 'Expr_Variable': - return $scope->hasVariableType($node->var->name)->yes() ? [$errorMessage] : []; + return $containsTypedItems + ? [$this->buildError()] + : []; + case 'Expr_Variable': + return $scope->hasVariableType($node->var->name)->yes() + ? [$this->buildError()] + : []; default: return []; } } + private function buildError(): IdentifierRuleError + { + $errorMessage = 'Should not use of mutating operators'; + + return RuleErrorBuilder::message($errorMessage) + ->identifier('phpstanFunctionalProgramming.disallowVariablesMutation') + ->build(); + } } diff --git a/tests/Rules/Classes/DisallowClassesRuleTest.php b/tests/Rules/Classes/DisallowClassesRuleTest.php index bc02b6a..a94280d 100644 --- a/tests/Rules/Classes/DisallowClassesRuleTest.php +++ b/tests/Rules/Classes/DisallowClassesRuleTest.php @@ -25,15 +25,15 @@ public function testWithEnabledRule(): void ], [ 'Should not use classes', - 8, + 7, ], [ 'Should not use classes', - 13, + 11, ], [ 'Should not use classes', - 23, + 20, ], ]); } diff --git a/tests/Rules/Classes/data/classes.php b/tests/Rules/Classes/data/classes.php index 16c3132..6cc4804 100644 --- a/tests/Rules/Classes/data/classes.php +++ b/tests/Rules/Classes/data/classes.php @@ -2,17 +2,14 @@ class ExampleClass { - } abstract class ExampleAbstractClass { - } final class ExampleFinalClass { - } function example() @@ -20,7 +17,8 @@ function example() // do not cause an error $someVar = 'class'; - $anonymous = new class {}; + $anonymous = new class { + }; } // do not cause an error diff --git a/tests/Rules/Exceptions/data/exceptions.php b/tests/Rules/Exceptions/data/exceptions.php index bd3b4b1..bc17ff3 100644 --- a/tests/Rules/Exceptions/data/exceptions.php +++ b/tests/Rules/Exceptions/data/exceptions.php @@ -7,7 +7,8 @@ function example() // do not cause an error $someVar = 'throw'; - $anonymous = new class {}; + $anonymous = new class { + }; } // do not cause an error diff --git a/tests/Rules/Expression/DisallowUnusedExpressionRuleTest.php b/tests/Rules/Expression/DisallowUnusedExpressionRuleTest.php index f244504..d4572ea 100644 --- a/tests/Rules/Expression/DisallowUnusedExpressionRuleTest.php +++ b/tests/Rules/Expression/DisallowUnusedExpressionRuleTest.php @@ -33,19 +33,19 @@ public function testWithEnabledRule(): void ], [ 'Enforce that an expression gets used', - 21, + 22, ], [ 'Enforce that an expression gets used', - 23, + 24, ], [ 'Enforce that an expression gets used', - 27, + 28, ], [ 'Enforce that an expression gets used', - 30, + 31, ], ]); } diff --git a/tests/Rules/Expression/data/expressions.php b/tests/Rules/Expression/data/expressions.php index f001d9c..c3c632d 100644 --- a/tests/Rules/Expression/data/expressions.php +++ b/tests/Rules/Expression/data/expressions.php @@ -17,7 +17,8 @@ function ds() $a++; -function example() { +function example() +{ 9 + 7; print_r(); @@ -30,5 +31,4 @@ function example() { print_r('some text'); for ($i = 0; 2 + 5; $i++) { - } diff --git a/tests/Rules/Functions/data/functions.php b/tests/Rules/Functions/data/functions.php index 2ab498e..ffca6ab 100644 --- a/tests/Rules/Functions/data/functions.php +++ b/tests/Rules/Functions/data/functions.php @@ -32,8 +32,20 @@ sort($ar2); -uasort($ar2); +uasort($ar2, fn($a, $b) => $a <=> $b); -uksort($ar2); +uksort($ar2, fn($a, $b) => $a <=> $b); -usort($ar2); +usort($ar2, fn($a, $b) => $a <=> $b); + +// no error +array_filter($ar1, fn($a) => $a); + +// no error +// but maybe should +$fn = 'print_r'; +$fn('hello'); + +// no error but should +$fn2 = 'array_push'; +$fn2($ar2, 6); diff --git a/tests/Rules/Loops/ForeachRuleTest.php b/tests/Rules/Loops/ForeachRuleTest.php index e2ca9d6..bbbdb20 100644 --- a/tests/Rules/Loops/ForeachRuleTest.php +++ b/tests/Rules/Loops/ForeachRuleTest.php @@ -21,7 +21,7 @@ public function testWithEnabledRule(): void $this->analyse([__DIR__ . '/data/loops.php'], [ [ 'Should not use loop foreach', - 19, + 18, ], ]); } diff --git a/tests/Rules/Loops/data/loops.php b/tests/Rules/Loops/data/loops.php index 8c295f0..af39580 100644 --- a/tests/Rules/Loops/data/loops.php +++ b/tests/Rules/Loops/data/loops.php @@ -12,7 +12,6 @@ } while ($a < 3); for ($i = 0; $i < 2; $i++) { - } $arr = array(1, 2, 3, 4); diff --git a/tests/Rules/Variables/AssignRuleTest.php b/tests/Rules/Variables/AssignRuleTest.php index 6d0bc16..3ab356a 100644 --- a/tests/Rules/Variables/AssignRuleTest.php +++ b/tests/Rules/Variables/AssignRuleTest.php @@ -75,6 +75,10 @@ public function testWithEnabledRule(): void 'Should not use of mutating operators', 52, ], + [ + 'Should not use of mutating operators', + 54, + ], ]); } diff --git a/tests/Rules/Variables/data/assign.php b/tests/Rules/Variables/data/assign.php index 9dffd85..01a9a71 100644 --- a/tests/Rules/Variables/data/assign.php +++ b/tests/Rules/Variables/data/assign.php @@ -50,3 +50,5 @@ function ds($arg1, &$arg2) { [$a] = $arr; [$newVar, $a] = [5, 6, 7]; + +fn($a) => $a = 3;