From 3cc8003ef0f3d00743e954a450c5175639ac0dea Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Mon, 25 May 2020 22:08:42 +0100 Subject: [PATCH] Split the logic that determines which lines should be ignored because of a user supplied annotation, from the code that determines which lines are code/non-code inside uncovered files --- src/CodeCoverage.php | 108 ++-------------- src/RawCodeCoverageData.php | 120 ++++++++++++++++++ tests/TestCase.php | 1 - .../dashboard.html | 8 +- .../index.html | 16 +-- ...with_class_and_anonymous_function.php.html | 26 ++-- .../index.xml | 4 +- ..._with_class_and_anonymous_function.php.xml | 9 +- .../class-with-anonymous-function-clover.xml | 7 +- .../class-with-anonymous-function-crap4j.xml | 2 +- .../class-with-anonymous-function-text.txt | 4 +- tests/_files/source_with_empty_class.php | 14 ++ tests/_files/source_with_interface.php | 23 ++++ tests/tests/CodeCoverageTest.php | 111 +--------------- tests/tests/FilterTest.php | 2 + tests/tests/RawCodeCoverageDataTest.php | 82 ++++++++++++ 16 files changed, 293 insertions(+), 244 deletions(-) create mode 100644 tests/_files/source_with_empty_class.php create mode 100644 tests/_files/source_with_interface.php diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index b732521f0..628593f5f 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -496,27 +496,22 @@ private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void */ private function addUncoveredFilesFromWhitelist(): void { - $data = []; $uncoveredFiles = \array_diff( $this->filter->getWhitelist(), \array_keys($this->data->getLineCoverage()) ); foreach ($uncoveredFiles as $uncoveredFile) { - if (!\file_exists($uncoveredFile)) { - continue; - } - - $data[$uncoveredFile] = []; - - $lines = \count(\file($uncoveredFile)); + if (\file_exists($uncoveredFile)) { + if ($this->cacheTokens) { + $tokens = \PHP_Token_Stream_CachingFactory::get($uncoveredFile); + } else { + $tokens = new \PHP_Token_Stream($uncoveredFile); + } - for ($i = 1; $i <= $lines; $i++) { - $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED; + $this->append(RawCodeCoverageData::fromUncoveredFile($uncoveredFile, $tokens), self::UNCOVERED_FILES_FROM_WHITELIST); } } - - $this->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage($data), self::UNCOVERED_FILES_FROM_WHITELIST); } private function getLinesToBeIgnored(string $fileName): array @@ -540,60 +535,12 @@ private function getLinesToBeIgnoredInner(string $fileName): array $lines = \file($fileName); - foreach ($lines as $index => $line) { - if (!\trim($line)) { - $this->ignoredLines[$fileName][] = $index + 1; - } - } - if ($this->cacheTokens) { $tokens = \PHP_Token_Stream_CachingFactory::get($fileName); } else { $tokens = new \PHP_Token_Stream($fileName); } - foreach ($tokens->getInterfaces() as $interface) { - $interfaceStartLine = $interface['startLine']; - $interfaceEndLine = $interface['endLine']; - - foreach (\range($interfaceStartLine, $interfaceEndLine) as $line) { - $this->ignoredLines[$fileName][] = $line; - } - } - - foreach (\array_merge($tokens->getClasses(), $tokens->getTraits()) as $classOrTrait) { - $classOrTraitStartLine = $classOrTrait['startLine']; - $classOrTraitEndLine = $classOrTrait['endLine']; - - if (empty($classOrTrait['methods'])) { - foreach (\range($classOrTraitStartLine, $classOrTraitEndLine) as $line) { - $this->ignoredLines[$fileName][] = $line; - } - - continue; - } - - $firstMethod = \array_shift($classOrTrait['methods']); - $firstMethodStartLine = $firstMethod['startLine']; - $lastMethodEndLine = $firstMethod['endLine']; - - do { - $lastMethod = \array_pop($classOrTrait['methods']); - } while ($lastMethod !== null && 0 === \strpos($lastMethod['signature'], 'anonymousFunction')); - - if ($lastMethod !== null) { - $lastMethodEndLine = $lastMethod['endLine']; - } - - foreach (\range($classOrTraitStartLine, $firstMethodStartLine) as $line) { - $this->ignoredLines[$fileName][] = $line; - } - - foreach (\range($lastMethodEndLine + 1, $classOrTraitEndLine) as $line) { - $this->ignoredLines[$fileName][] = $line; - } - } - if ($this->disableIgnoredLines) { $this->ignoredLines[$fileName] = \array_unique($this->ignoredLines[$fileName]); \sort($this->ignoredLines[$fileName]); @@ -623,29 +570,10 @@ private function getLinesToBeIgnoredInner(string $fileName): array $stop = true; } - if (!$ignore) { - $start = $token->getLine(); - $end = $start + \substr_count((string) $token, "\n"); - - // Do not ignore the first line when there is a token - // before the comment - if (0 !== \strpos($_token, $_line)) { - $start++; - } - - for ($i = $start; $i < $end; $i++) { - $this->ignoredLines[$fileName][] = $i; - } - - // A DOC_COMMENT token or a COMMENT token starting with "/*" - // does not contain the final \n character in its text - if (isset($lines[$i - 1]) && 0 === \strpos($_token, '/*') && '*/' === \substr(\trim($lines[$i - 1]), -2)) { - $this->ignoredLines[$fileName][] = $i; - } - } - break; + // Intentional fallthrough + case \PHP_Token_INTERFACE::class: case \PHP_Token_TRAIT::class: case \PHP_Token_CLASS::class: @@ -654,8 +582,6 @@ private function getLinesToBeIgnoredInner(string $fileName): array $docblock = (string) $token->getDocblock(); - $this->ignoredLines[$fileName][] = $token->getLine(); - if (\strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && \strpos($docblock, '@deprecated'))) { $endLine = $token->getEndLine(); @@ -664,20 +590,6 @@ private function getLinesToBeIgnoredInner(string $fileName): array } } - break; - - /* @noinspection PhpMissingBreakStatementInspection */ - case \PHP_Token_NAMESPACE::class: - $this->ignoredLines[$fileName][] = $token->getEndLine(); - - // Intentional fallthrough - case \PHP_Token_DECLARE::class: - case \PHP_Token_OPEN_TAG::class: - case \PHP_Token_CLOSE_TAG::class: - case \PHP_Token_USE::class: - case \PHP_Token_USE_FUNCTION::class: - $this->ignoredLines[$fileName][] = $token->getLine(); - break; } @@ -691,8 +603,6 @@ private function getLinesToBeIgnoredInner(string $fileName): array } } - $this->ignoredLines[$fileName][] = \count($lines) + 1; - $this->ignoredLines[$fileName] = \array_unique( $this->ignoredLines[$fileName] ); diff --git a/src/RawCodeCoverageData.php b/src/RawCodeCoverageData.php index ea6a6313c..dc67eb3cf 100644 --- a/src/RawCodeCoverageData.php +++ b/src/RawCodeCoverageData.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage; +use PHP_Token_Stream; +use SebastianBergmann\CodeCoverage\Driver\Driver; + /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ @@ -54,6 +57,123 @@ public static function fromXdebugWithPathCoverage(array $rawCoverage): self return new self($lineCoverage, $functionCoverage); } + public static function fromUncoveredFile(string $filename, PHP_Token_Stream $tokens): self + { + $lineCoverage = []; + + $lines = \file($filename); + $lineCount = \count($lines); + + for ($i = 1; $i <= $lineCount; $i++) { + $lineCoverage[$i] = Driver::LINE_NOT_EXECUTED; + } + + //remove empty lines + foreach ($lines as $index => $line) { + if (!\trim($line)) { + unset($lineCoverage[$index + 1]); + } + } + + //not all lines are actually executable though, remove these + try { + foreach ($tokens->getInterfaces() as $interface) { + $interfaceStartLine = $interface['startLine']; + $interfaceEndLine = $interface['endLine']; + + foreach (\range($interfaceStartLine, $interfaceEndLine) as $line) { + unset($lineCoverage[$line]); + } + } + + foreach (\array_merge($tokens->getClasses(), $tokens->getTraits()) as $classOrTrait) { + $classOrTraitStartLine = $classOrTrait['startLine']; + $classOrTraitEndLine = $classOrTrait['endLine']; + + if (empty($classOrTrait['methods'])) { + foreach (\range($classOrTraitStartLine, $classOrTraitEndLine) as $line) { + unset($lineCoverage[$line]); + } + + continue; + } + + $firstMethod = \array_shift($classOrTrait['methods']); + $firstMethodStartLine = $firstMethod['startLine']; + $lastMethodEndLine = $firstMethod['endLine']; + + do { + $lastMethod = \array_pop($classOrTrait['methods']); + } while ($lastMethod !== null && 0 === \strpos($lastMethod['signature'], 'anonymousFunction')); + + if ($lastMethod !== null) { + $lastMethodEndLine = $lastMethod['endLine']; + } + + foreach (\range($classOrTraitStartLine, $firstMethodStartLine) as $line) { + unset($lineCoverage[$line]); + } + + foreach (\range($lastMethodEndLine + 1, $classOrTraitEndLine) as $line) { + unset($lineCoverage[$line]); + } + } + + foreach ($tokens->tokens() as $token) { + switch (\get_class($token)) { + case \PHP_Token_COMMENT::class: + case \PHP_Token_DOC_COMMENT::class: + $_token = \trim((string) $token); + $_line = \trim($lines[$token->getLine() - 1]); + + $start = $token->getLine(); + $end = $start + \substr_count((string) $token, "\n"); + + // Do not ignore the first line when there is a token + // before the comment + if (0 !== \strpos($_token, $_line)) { + $start++; + } + + for ($i = $start; $i < $end; $i++) { + unset($lineCoverage[$i]); + } + + // A DOC_COMMENT token or a COMMENT token starting with "/*" + // does not contain the final \n character in its text + if (isset($lines[$i - 1]) && 0 === \strpos($_token, '/*') && '*/' === \substr(\trim($lines[$i - 1]), -2)) { + unset($lineCoverage[$i]); + } + + break; + + /* @noinspection PhpMissingBreakStatementInspection */ + case \PHP_Token_NAMESPACE::class: + unset($lineCoverage[$token->getEndLine()]); + + // Intentional fallthrough + + case \PHP_Token_INTERFACE::class: + case \PHP_Token_TRAIT::class: + case \PHP_Token_CLASS::class: + case \PHP_Token_FUNCTION::class: + case \PHP_Token_DECLARE::class: + case \PHP_Token_OPEN_TAG::class: + case \PHP_Token_CLOSE_TAG::class: + case \PHP_Token_USE::class: + case \PHP_Token_USE_FUNCTION::class: + unset($lineCoverage[$token->getLine()]); + + break; + } + } + } catch (\Exception $e) { // This can happen with PHP_Token_Stream if the file is syntactically invalid + // do nothing + } + + return new self([$filename => $lineCoverage], []); + } + private function __construct(array $lineCoverage, array $functionCoverage) { $this->lineCoverage = $lineCoverage; diff --git a/tests/TestCase.php b/tests/TestCase.php index b5266f3b9..571d89ba5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1530,7 +1530,6 @@ protected function setUpXdebugStubForFileWithIgnoredLines(): Driver 2 => 1, 4 => -1, 6 => -1, - 7 => 1, ], ] ) diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/dashboard.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/dashboard.html index 8b27809c7..c1246a182 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/dashboard.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/dashboard.html @@ -57,7 +57,7 @@

Insufficient Coverage

- CoveredClassWithAnonymousFunctionInStaticMethod87% + CoveredClassWithAnonymousFunctionInStaticMethod88% @@ -111,7 +111,7 @@

Insufficient Coverage

- runAnonymous87% + runAnonymous88% @@ -224,7 +224,7 @@

Project Risks

chart.yAxis.axisLabel('Cyclomatic Complexity'); d3.select('#classComplexity svg') - .datum(getComplexityData([[87.5,1,"CoveredClassWithAnonymousFunctionInStaticMethod<\/a>"]], 'Class Complexity')) + .datum(getComplexityData([[88.888888888889,1,"CoveredClassWithAnonymousFunctionInStaticMethod<\/a>"]], 'Class Complexity')) .transition() .duration(500) .call(chart); @@ -248,7 +248,7 @@

Project Risks

chart.yAxis.axisLabel('Method Complexity'); d3.select('#methodComplexity svg') - .datum(getComplexityData([[87.5,1,"
CoveredClassWithAnonymousFunctionInStaticMethod::runAnonymous<\/a>"]], 'Method Complexity')) + .datum(getComplexityData([[88.888888888889,1,"CoveredClassWithAnonymousFunctionInStaticMethod::runAnonymous<\/a>"]], 'Method Complexity')) .transition() .duration(500) .call(chart); diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html index 68318d055..222cec8c4 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html @@ -44,13 +44,13 @@ Total
-
- 87.50% covered (warning) +
+ 88.89% covered (warning)
-
87.50%
-
7 / 8
+
88.89%
+
8 / 9
0.00% covered (danger) @@ -72,13 +72,13 @@ source_with_class_and_anonymous_function.php
-
- 87.50% covered (warning) +
+ 88.89% covered (warning)
-
87.50%
-
7 / 8
+
88.89%
+
8 / 9
0.00% covered (danger) diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html index c261c6e1e..7c0525431 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html @@ -61,13 +61,13 @@
0 / 1
CRAP
-
- 87.50% covered (warning) +
+ 88.89% covered (warning)
-
87.50%
-
7 / 8
+
88.89%
+
8 / 9
@@ -90,13 +90,13 @@
0 / 1
1.00
-
- 87.50% covered (warning) +
+ 88.89% covered (warning)
-
87.50%
-
7 / 8
+
88.89%
+
8 / 9
@@ -111,13 +111,13 @@
0 / 1
1.00
-
- 87.50% covered (warning) +
+ 88.89% covered (warning)
-
87.50%
-
7 / 8
+
88.89%
+
8 / 9
@@ -136,7 +136,7 @@         array_walk(             $filter, -             function (&$val, $key) { +             function (&$val, $key) {                 $val = preg_replace('|[^0-9]|', '', $val);             }         ); diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml index c8d90baa9..4d5dc7efc 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml @@ -10,7 +10,7 @@ - + @@ -18,7 +18,7 @@ - + diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml index a4131745d..c70af8adf 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml @@ -2,16 +2,16 @@ - + - + - + @@ -20,6 +20,9 @@ + + + diff --git a/tests/_files/class-with-anonymous-function-clover.xml b/tests/_files/class-with-anonymous-function-clover.xml index 008db55f1..246101d56 100644 --- a/tests/_files/class-with-anonymous-function-clover.xml +++ b/tests/_files/class-with-anonymous-function-clover.xml @@ -3,19 +3,20 @@ - + + - + - + diff --git a/tests/_files/class-with-anonymous-function-crap4j.xml b/tests/_files/class-with-anonymous-function-crap4j.xml index 5bd2535e1..2d60e6215 100644 --- a/tests/_files/class-with-anonymous-function-crap4j.xml +++ b/tests/_files/class-with-anonymous-function-crap4j.xml @@ -19,7 +19,7 @@ runAnonymous() 1 1 - 87.5 + 88.89 0 diff --git a/tests/_files/class-with-anonymous-function-text.txt b/tests/_files/class-with-anonymous-function-text.txt index e4204cc64..fc46ef929 100644 --- a/tests/_files/class-with-anonymous-function-text.txt +++ b/tests/_files/class-with-anonymous-function-text.txt @@ -6,7 +6,7 @@ Code Coverage Report: Summary: Classes: 0.00% (0/1) Methods: 0.00% (0/1) - Lines: 87.50% (7/8) + Lines: 88.89% (8/9) CoveredClassWithAnonymousFunctionInStaticMethod - Methods: 0.00% ( 0/ 1) Lines: 87.50% ( 7/ 8) + Methods: 0.00% ( 0/ 1) Lines: 88.89% ( 8/ 9) diff --git a/tests/_files/source_with_empty_class.php b/tests/_files/source_with_empty_class.php new file mode 100644 index 000000000..f088db8c1 --- /dev/null +++ b/tests/_files/source_with_empty_class.php @@ -0,0 +1,14 @@ + 1, 'b' => 2, 'c' => 3, 'd' => 4], + static function ($v, $k) + { + return $k === 'b' || $v === 4; + }, + ARRAY_FILTER_USE_BOTH + ); + } +} + +interface AnInterface +{ + public function m(): void; +} diff --git a/tests/tests/CodeCoverageTest.php b/tests/tests/CodeCoverageTest.php index 6e37224de..9c5fccd09 100644 --- a/tests/tests/CodeCoverageTest.php +++ b/tests/tests/CodeCoverageTest.php @@ -128,40 +128,19 @@ public function testGetLinesToBeIgnored(): void { $this->assertEquals( [ - 1, 3, 4, 5, - 7, - 8, - 9, - 10, 11, 12, 13, 14, 15, 16, - 17, - 18, - 19, - 20, - 21, - 22, 23, 24, 25, - 26, - 27, - 28, 30, - 32, - 33, - 34, - 35, - 36, - 37, - 38, ], $this->getLinesToBeIgnored()->invoke( $this->coverage, @@ -173,7 +152,7 @@ public function testGetLinesToBeIgnored(): void public function testGetLinesToBeIgnored2(): void { $this->assertEquals( - [1, 5], + [], $this->getLinesToBeIgnored()->invoke( $this->coverage, TEST_FILES_PATH . 'source_without_ignore.php' @@ -184,19 +163,7 @@ public function testGetLinesToBeIgnored2(): void public function testGetLinesToBeIgnored3(): void { $this->assertEquals( - [ - 1, - 2, - 3, - 4, - 5, - 8, - 11, - 15, - 16, - 19, - 20, - ], + [], $this->getLinesToBeIgnored()->invoke( $this->coverage, TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php' @@ -208,35 +175,10 @@ public function testGetLinesToBeIgnoredOneLineAnnotations(): void { $this->assertEquals( [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 14, - 15, - 16, - 18, - 20, - 21, - 23, - 24, - 25, - 27, - 28, 29, - 30, 31, 32, 33, - 34, - 37, ], $this->getLinesToBeIgnored()->invoke( $this->coverage, @@ -250,28 +192,7 @@ public function testGetLinesToBeIgnoredWhenIgnoreIsDisabled(): void $this->coverage->setDisableIgnoredLines(true); $this->assertEquals( - [ - 7, - 11, - 12, - 13, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 26, - 27, - 32, - 33, - 34, - 35, - 36, - 37, - ], + [], $this->getLinesToBeIgnored()->invoke( $this->coverage, TEST_FILES_PATH . 'source_with_ignore.php' @@ -279,32 +200,6 @@ public function testGetLinesToBeIgnoredWhenIgnoreIsDisabled(): void ); } - public function testUseStatementsAreIgnored(): void - { - $this->assertEquals( - [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 13, - 16, - 23, - 24, - ], - $this->getLinesToBeIgnored()->invoke( - $this->coverage, - TEST_FILES_PATH . 'source_with_use_statements.php' - ) - ); - } - public function testAppendThrowsExceptionIfCoveredCodeWasNotExecuted(): void { $this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH); diff --git a/tests/tests/FilterTest.php b/tests/tests/FilterTest.php index e19f719e7..8286b3222 100644 --- a/tests/tests/FilterTest.php +++ b/tests/tests/FilterTest.php @@ -70,7 +70,9 @@ protected function setUp(): void TEST_FILES_PATH . 'NamespacedBankAccount.php', TEST_FILES_PATH . 'NotExistingCoveredElementTest.php', TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php', + TEST_FILES_PATH . 'source_with_empty_class.php', TEST_FILES_PATH . 'source_with_ignore.php', + TEST_FILES_PATH . 'source_with_interface.php', TEST_FILES_PATH . 'source_with_namespace.php', TEST_FILES_PATH . 'source_with_oneline_annotations.php', TEST_FILES_PATH . 'source_with_use_statements.php', diff --git a/tests/tests/RawCodeCoverageDataTest.php b/tests/tests/RawCodeCoverageDataTest.php index e4e318096..f61d15ad7 100644 --- a/tests/tests/RawCodeCoverageDataTest.php +++ b/tests/tests/RawCodeCoverageDataTest.php @@ -212,4 +212,86 @@ public function testRemoveCoverageDataForLines(): void $this->assertEquals($expectedFilterResult, $dataObject->getLineCoverage()); } + + public function testUseStatementsAreUncovered(): void + { + $file = TEST_FILES_PATH . 'source_with_use_statements.php'; + $tokens = new \PHP_Token_Stream($file); + + $this->assertEquals( + [ + 11, + 12, + 14, + 15, + 17, + 18, + 19, + 20, + 21, + 22, + ], + \array_keys(RawCodeCoverageData::fromUncoveredFile($file, $tokens)->getLineCoverage()[$file]) + ); + } + + public function testEmptyClassesAreUncovered(): void + { + $file = TEST_FILES_PATH . 'source_with_empty_class.php'; + $tokens = new \PHP_Token_Stream($file); + + $this->assertEquals( + [ + 12, + 14, + ], + \array_keys(RawCodeCoverageData::fromUncoveredFile($file, $tokens)->getLineCoverage()[$file]) + ); + } + + public function testInterfacesAreUncovered(): void + { + $file = TEST_FILES_PATH . 'source_with_interface.php'; + $tokens = new \PHP_Token_Stream($file); + + $this->assertEquals( + [ + 6, + 7, + 9, + 10, + 12, + 13, + 14, + 15, + 16, + 17, + ], + \array_keys(RawCodeCoverageData::fromUncoveredFile($file, $tokens)->getLineCoverage()[$file]) + ); + } + + public function testInlineCommentsKeepTheLine(): void + { + $file = TEST_FILES_PATH . 'source_with_oneline_annotations.php'; + $tokens = new \PHP_Token_Stream($file); + + $this->assertEquals( + [ + 12, + 13, + 17, + 19, + 22, + 26, + 29, + 31, + 32, + 33, + 35, + 36, + ], + \array_keys(RawCodeCoverageData::fromUncoveredFile($file, $tokens)->getLineCoverage()[$file]) + ); + } }