From 20135c078f4fd5575e930a7ac636a246c27ef93d Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 28 Feb 2022 08:02:44 +0100 Subject: [PATCH 1/3] Executable lines: consider empty constructor with promoted properties --- .../ExecutableLinesFindingVisitor.php | 27 ++++++++++++++++ .../_files/source_with_empty_constructor.php | 32 +++++++++++++++++++ tests/tests/Data/RawCodeCoverageDataTest.php | 15 +++++++++ 3 files changed, 74 insertions(+) create mode 100644 tests/_files/source_with_empty_constructor.php diff --git a/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/src/StaticAnalysis/ExecutableLinesFindingVisitor.php index eb76f9178..fedb7a34e 100644 --- a/src/StaticAnalysis/ExecutableLinesFindingVisitor.php +++ b/src/StaticAnalysis/ExecutableLinesFindingVisitor.php @@ -25,6 +25,8 @@ use PhpParser\Node\Stmt\Break_; use PhpParser\Node\Stmt\Case_; use PhpParser\Node\Stmt\Catch_; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Continue_; use PhpParser\Node\Stmt\Do_; use PhpParser\Node\Stmt\Echo_; @@ -118,6 +120,30 @@ private function getLines(Node $node): array return [$node->dim->getStartLine()]; } + if ($node instanceof ClassMethod) { + if ($node->name->name !== '__construct') { + return []; + } + + $existsAPromotedProperty = false; + + foreach ($node->getParams() as $param) { + if (0 !== ($param->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + $existsAPromotedProperty = true; + + break; + } + } + + if ($existsAPromotedProperty) { + // Only the line with `function` keyword should be listed here + // but `nikic/php-parser` doesn't provide a way to fetch it + return range($node->getStartLine(), $node->name->getEndLine()); + } + + return []; + } + if ($node instanceof MethodCall) { return [$node->name->getStartLine()]; } @@ -147,6 +173,7 @@ private function isExecutable(Node $node): bool $node instanceof Case_ || $node instanceof Cast || $node instanceof Catch_ || + $node instanceof ClassMethod || $node instanceof Closure || $node instanceof Continue_ || $node instanceof Do_ || diff --git a/tests/_files/source_with_empty_constructor.php b/tests/_files/source_with_empty_constructor.php new file mode 100644 index 000000000..0d596c3ec --- /dev/null +++ b/tests/_files/source_with_empty_constructor.php @@ -0,0 +1,32 @@ +assertEquals( + [ + 5, + 6, + 7, + 30, + ], + array_keys(RawCodeCoverageData::fromUncoveredFile($file, new ParsingFileAnalyser(true, true))->lineCoverage()[$file]) + ); + } + public function testCoverageForFileWithInlineAnnotations(): void { $filename = TEST_FILES_PATH . 'source_with_oneline_annotations.php'; From 65969ce23413fc9d277a99117e8d1e18e87d3bae Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 28 Feb 2022 08:39:33 +0100 Subject: [PATCH 2/3] Executable lines: mark each match condition --- .../ExecutableLinesFindingVisitor.php | 20 +++++++++++++ .../_files/source_with_heavy_indentation.php | 3 -- tests/_files/source_with_match_expression.php | 30 +++++++++++++++++++ tests/tests/Data/RawCodeCoverageDataTest.php | 21 +++++++++---- 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 tests/_files/source_with_match_expression.php diff --git a/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/src/StaticAnalysis/ExecutableLinesFindingVisitor.php index fedb7a34e..d8950333e 100644 --- a/src/StaticAnalysis/ExecutableLinesFindingVisitor.php +++ b/src/StaticAnalysis/ExecutableLinesFindingVisitor.php @@ -16,11 +16,13 @@ use PhpParser\Node\Expr\CallLike; use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\Closure; +use PhpParser\Node\Expr\Match_; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\NullsafePropertyFetch; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Expr\Ternary; +use PhpParser\Node\MatchArm; use PhpParser\Node\Scalar\Encapsed; use PhpParser\Node\Stmt\Break_; use PhpParser\Node\Stmt\Case_; @@ -160,6 +162,22 @@ private function getLines(Node $node): array return $lines; } + if ($node instanceof Match_) { + return [$node->cond->getStartLine()]; + } + + if ($node instanceof MatchArm) { + return [$node->body->getStartLine()]; + } + + if ($node instanceof Expression && ( + $node->expr instanceof Cast || + $node->expr instanceof Match_ || + $node->expr instanceof MethodCall + )) { + return []; + } + return [$node->getStartLine()]; } @@ -187,6 +205,8 @@ private function isExecutable(Node $node): bool $node instanceof Foreach_ || $node instanceof Goto_ || $node instanceof If_ || + $node instanceof Match_ || + $node instanceof MatchArm || $node instanceof MethodCall || $node instanceof NullsafePropertyFetch || $node instanceof PropertyFetch || diff --git a/tests/_files/source_with_heavy_indentation.php b/tests/_files/source_with_heavy_indentation.php index a0829d197..64cea4a10 100644 --- a/tests/_files/source_with_heavy_indentation.php +++ b/tests/_files/source_with_heavy_indentation.php @@ -155,9 +155,6 @@ function ) ; - $var[] - ; - (string) $var ; diff --git a/tests/_files/source_with_match_expression.php b/tests/_files/source_with_match_expression.php new file mode 100644 index 000000000..3a09d542a --- /dev/null +++ b/tests/_files/source_with_match_expression.php @@ -0,0 +1,30 @@ + + 1 + , + // Some comments + default + => + throw new Exception() + , + } + ; + } +} diff --git a/tests/tests/Data/RawCodeCoverageDataTest.php b/tests/tests/Data/RawCodeCoverageDataTest.php index 3db45cc60..faf954f95 100644 --- a/tests/tests/Data/RawCodeCoverageDataTest.php +++ b/tests/tests/Data/RawCodeCoverageDataTest.php @@ -366,18 +366,15 @@ public function testHeavyIndentationIsHandledCorrectly(): void 135, 139, 143, - 147, 149, 153, - 158, - 161, - 162, + 159, ], array_keys(RawCodeCoverageData::fromUncoveredFile($file, new ParsingFileAnalyser(true, true))->lineCoverage()[$file]) ); } - public function testEmtpyConstructorIsMarkedAsExevutable(): void + public function testEmtpyConstructorIsMarkedAsExecutable(): void { $file = TEST_FILES_PATH . 'source_with_empty_constructor.php'; @@ -392,6 +389,20 @@ public function testEmtpyConstructorIsMarkedAsExevutable(): void ); } + public function testEachCaseInMatchExpressionIsMarkedAsExecutable(): void + { + $file = TEST_FILES_PATH . 'source_with_match_expression.php'; + + $this->assertEquals( + [ + 14, + 20, + 25, + ], + array_keys(RawCodeCoverageData::fromUncoveredFile($file, new ParsingFileAnalyser(true, true))->lineCoverage()[$file]) + ); + } + public function testCoverageForFileWithInlineAnnotations(): void { $filename = TEST_FILES_PATH . 'source_with_oneline_annotations.php'; From 0cfebfe2582dd4fa51a8a3d7d1594ae2054b22df Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 28 Feb 2022 09:30:33 +0100 Subject: [PATCH 3/3] Test on match expr needs PHP 8.0 --- tests/tests/Data/RawCodeCoverageDataTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/tests/Data/RawCodeCoverageDataTest.php b/tests/tests/Data/RawCodeCoverageDataTest.php index faf954f95..a87324e3b 100644 --- a/tests/tests/Data/RawCodeCoverageDataTest.php +++ b/tests/tests/Data/RawCodeCoverageDataTest.php @@ -389,6 +389,9 @@ public function testEmtpyConstructorIsMarkedAsExecutable(): void ); } + /** + * @requires PHP 8.0 + */ public function testEachCaseInMatchExpressionIsMarkedAsExecutable(): void { $file = TEST_FILES_PATH . 'source_with_match_expression.php';