* Dariusz RumiƄski * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace PhpCsFixer\Tests\Fixer\Whitespace; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; use PhpCsFixer\WhitespacesFixerConfig; /** * @internal * * @covers \PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer * * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer> * * @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer */ final class NoExtraBlankLinesFixerTest extends AbstractFixerTestCase { private string $template = <<<'EOF' format('Y')); throw $e; } public function testBreak($a) { switch($a) { case 1: echo $a; break; case 2: echo 'test'; break; } } protected static function testContinueAndReturn($a, $b) { while($a < 100) { if ($b < time()) { continue; } return $b; } return $a; } private function test(){ // comment } private function test123(){ // comment } } EOF; /** * @param list $lineNumberRemoved Line numbers expected to be removed after fixing * @param _AutogeneratedInputConfiguration['tokens'] $configTokens * * @dataProvider provideWithConfigCases */ public function testWithConfig(array $lineNumberRemoved, array $configTokens): void { $this->fixer->configure(['tokens' => $configTokens]); $this->doTest($this->removeLinesFromString($this->template, $lineNumberRemoved), $this->template); } public static function provideWithConfigCases(): iterable { $tests = [ [ [9, 14, 21, 43, 45, 49, 53, 57], ['curly_brace_block'], ], [ [3, 5], ['use'], ], [ [23, 24], ['extra'], ], [ [49, 53], ['return'], ], [ [45], ['continue'], ], [ [32], ['break'], ], [ [14, 21], ['throw'], ], ]; yield from $tests; $all = [[], []]; foreach ($tests as $test) { $all[0] = array_merge($test[0], $all[0]); $all[1] = array_merge($test[1], $all[1]); } yield $all; } public function testFix(): void { $expected = <<<'EOF' doTest($expected, $input); } public function testFixWithManyEmptyLines(): void { $expected = <<<'EOF' doTest($expected, $input); } public function testFixWithHeredoc(): void { $expected = ' doTest($expected); } public function testFixWithNowdoc(): void { $expected = ' doTest($expected); } public function testFixWithEncapsulatedNowdoc(): void { $expected = ' doTest($expected); } public function testFixWithMultilineString(): void { $expected = <<<'EOF' doTest($expected); } public function testFixWithTrickyMultilineStrings(): void { $expected = <<<'EOF' doTest($expected, $input); } public function testFixWithCommentWithQuote(): void { $expected = <<<'EOF' doTest($expected, $input); } public function testFixWithTrailingInlineBlock(): void { $expected = " \$a = 0; //a "; $this->doTest($expected); } /** * @dataProvider provideFixWithCommentsCases */ public function testFixWithComments(string $expected, string $input): void { $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixWithCommentsCases(): iterable { yield [ <<<'EOF' doTest($expected, $input); } /** * @return iterable */ public static function provideFixWithLineBreaksCases(): iterable { $input = 'expectException(InvalidFixerConfigurationException::class); $this->expectExceptionMessageMatches('/^\[no_extra_blank_lines\] Invalid configuration: The option "tokens" .*\.$/'); $this->fixer->configure(['tokens' => ['__TEST__']]); // @phpstan-ignore-line } /** * @dataProvider provideBetweenUseCases */ public function testBetweenUse(string $expected, ?string $input = null): void { $this->fixer->configure(['tokens' => ['use']]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideBetweenUseCases(): iterable { yield ['']; yield ['\n\n"]; yield ['fixer->configure(['tokens' => ['use']]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideRemoveLinesBetweenUseStatementsCases(): iterable { yield [ <<<'EOF' fixer->configure(['tokens' => ['use']]); $this->doTest($expected); } /** * @return iterable */ public static function provideWithoutUsesCases(): iterable { yield [ '', ]; yield [ '', ]; } /** * @dataProvider provideRemoveBetweenUseTraitsCases * * @group legacy */ public function testRemoveBetweenUseTraits(string $expected, string $input): void { $this->expectDeprecation('Option "tokens: use_trait" used in `no_extra_blank_lines` rule is deprecated, use the rule `class_attributes_separation` with `elements: trait_import` instead.'); $this->fixer->configure(['tokens' => ['use_trait']]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideRemoveBetweenUseTraitsCases(): iterable { yield [ 'fixer->configure(['tokens' => [ 'break', 'continue', 'return', 'throw', 'curly_brace_block', 'square_brace_block', 'parenthesis_brace_block', ]]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideOneOrInLineCases(): iterable { yield [ "{'Test'};\nfunction test(){}\n", ]; yield [ "fixer->configure($config); $this->doTest($expected, $input); } public static function provideBracesCases(): iterable { yield [ ['tokens' => ['curly_brace_block']], " ['curly_brace_block']], " ['parenthesis_brace_block']], ' ['parenthesis_brace_block']], " ['parenthesis_brace_block']], 'd(e( )); foreach ($a as $x) { } }', ]; yield [ ['tokens' => ['return']], ' ['square_brace_block']], "doTest($expected, $input); } /** * @return iterable */ public static function provideFixPre80Cases(): iterable { yield [ "fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t", "\r\n")); $this->fixer->configure($config); $this->doTest($expected, $input); } public static function provideMessyWhitespacesCases(): iterable { yield [ [], " ['parenthesis_brace_block']], " ['square_brace_block']], " ['square_brace_block']], "fixer->configure(['tokens' => $configTokens]); $this->doTest($expected, $input); } public static function provideInSwitchStatementCases(): iterable { yield [ [ 'break', 'continue', 'extra', 'return', 'throw', ], 'getValue()); case 7: return new ConstantNode($token->getValue()); case 8: return 8; default: echo 1; }', 'getValue()); case 7: return new ConstantNode($token->getValue()); '.' case 8: return 8; '.' default: echo 1; }', ]; yield [ [ 'switch', 'case', 'default', ], 'doTest( 'fixer->configure($config); $this->doTest($expected, $input); } public static function provideFix80Cases(): iterable { yield [ ['tokens' => ['throw']], ' ['throw']], ' ['attribute']], ' ['attribute']], ' ['attribute']], 'fixer->configure(['tokens' => ['case']]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFix81Cases(): iterable { yield [ ' $lineNumbers */ private function removeLinesFromString(string $input, array $lineNumbers): string { sort($lineNumbers); $lines = explode("\n", $input); foreach ($lineNumbers as $lineNumber) { --$lineNumber; unset($lines[$lineNumber]); } return implode("\n", $lines); } }