From 78f54cfcbbb1c285fe364117bf141a91d508b445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ch=C3=A1bek?= Date: Wed, 15 Jan 2020 15:34:05 +0100 Subject: [PATCH] Add SingleLineArrayWhitespaceSniff --- README.md | 8 + .../Arrays/SingleLineArrayWhitespaceSniff.php | 189 ++++++++++++++++++ .../SingleLineArrayWhitespaceSniffTest.php | 52 +++++ .../singleLineArrayWhitespaceErrors.fixed.php | 8 + .../data/singleLineArrayWhitespaceErrors.php | 8 + .../singleLineArrayWhitespaceNoErrors.php | 24 +++ ...rayWhitespaceRequireSpacesErrors.fixed.php | 3 + ...LineArrayWhitespaceRequireSpacesErrors.php | 3 + 8 files changed, 295 insertions(+) create mode 100644 SlevomatCodingStandard/Sniffs/Arrays/SingleLineArrayWhitespaceSniff.php create mode 100644 tests/Sniffs/Arrays/SingleLineArrayWhitespaceSniffTest.php create mode 100644 tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.fixed.php create mode 100644 tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.php create mode 100644 tests/Sniffs/Arrays/data/singleLineArrayWhitespaceNoErrors.php create mode 100644 tests/Sniffs/Arrays/data/singleLineArrayWhitespaceRequireSpacesErrors.fixed.php create mode 100644 tests/Sniffs/Arrays/data/singleLineArrayWhitespaceRequireSpacesErrors.php diff --git a/README.md b/README.md index 7c605db9a..18ae685c2 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,14 @@ try { Enforces reasonable end bracket placement for multiline arrays. +#### SlevomatCodingStandard.Arrays.SingleLineArrayWhitespace 🔧 + +Checks whitespace in single line array declarations (whitespace between brackets, around commas, ...). + +Sniff provides the following settings: + +* `$spacesAroundBrackets`: number of spaces you require to have around array brackets + #### SlevomatCodingStandard.Arrays.TrailingArrayComma 🔧 Commas after last element in an array make adding a new element easier and result in a cleaner versioning diff. diff --git a/SlevomatCodingStandard/Sniffs/Arrays/SingleLineArrayWhitespaceSniff.php b/SlevomatCodingStandard/Sniffs/Arrays/SingleLineArrayWhitespaceSniff.php new file mode 100644 index 000000000..aa381f35f --- /dev/null +++ b/SlevomatCodingStandard/Sniffs/Arrays/SingleLineArrayWhitespaceSniff.php @@ -0,0 +1,189 @@ +getTokens(); + + $arrayStart = $stackPointer; + $arrayEnd = $tokens[$stackPointer]['bracket_closer']; + + // Check only single-line arrays. + if ($tokens[$arrayStart]['line'] !== $tokens[$arrayEnd]['line']) { + return $arrayEnd; + } + + $this->checkWhitespaceAfterOpeningBracket($phpcsFile, $arrayStart); + $this->checkWhitespaceBeforeClosingBracket($phpcsFile, $arrayEnd); + + for ($i = $arrayStart + 1; $i < $arrayEnd; $i++) { + // Skip bracketed statements, like function calls. + if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) { + $i = $tokens[$i]['parenthesis_closer']; + + continue; + } + + // Skip nested arrays as they will be processed separately + if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { + $i = $tokens[$i]['bracket_closer']; + + continue; + } + + if ($tokens[$i]['code'] !== T_COMMA) { + continue; + } + + // Before checking this comma, make sure we are not at the end of the array. + $next = $phpcsFile->findNext(T_WHITESPACE, $i + 1, $arrayEnd, true); + if ($next === false) { + return $arrayStart + 1; + } + + $this->checkWhitespaceBeforeComma($phpcsFile, $i); + $this->checkWhitespaceAfterComma($phpcsFile, $i); + } + + return $arrayStart + 1; + } + + private function checkWhitespaceAfterOpeningBracket(File $phpcsFile, int $arrayStart): void + { + $tokens = $phpcsFile->getTokens(); + + $whitespacePointer = $arrayStart + 1; + + $spaceLength = 0; + if ($tokens[$whitespacePointer]['code'] === T_WHITESPACE) { + $spaceLength = $tokens[$whitespacePointer]['length']; + } + + if ($spaceLength === $this->spacesAroundBrackets) { + return; + } + + $error = 'Expected %d spaces after array opening bracket; %d found'; + $data = [$this->spacesAroundBrackets, $spaceLength]; + $fix = $phpcsFile->addFixableError($error, $arrayStart, self::CODE_SPACE_AFTER_ARRAY_OPEN, $data); + if (! $fix) { + return; + } + + if ($spaceLength === 0) { + $phpcsFile->fixer->addContent($arrayStart, str_repeat(' ', $this->spacesAroundBrackets)); + } else { + $phpcsFile->fixer->replaceToken($whitespacePointer, str_repeat(' ', $this->spacesAroundBrackets)); + } + } + + private function checkWhitespaceBeforeClosingBracket(File $phpcsFile, int $arrayEnd): void + { + $tokens = $phpcsFile->getTokens(); + + $whitespacePointer = $arrayEnd - 1; + + $spaceLength = 0; + if ($tokens[$whitespacePointer]['code'] === T_WHITESPACE) { + $spaceLength = $tokens[$whitespacePointer]['length']; + } + + if ($spaceLength === $this->spacesAroundBrackets) { + return; + } + + $error = 'Expected %d spaces before array closing bracket; %d found'; + $data = [$this->spacesAroundBrackets, $spaceLength]; + $fix = $phpcsFile->addFixableError($error, $arrayEnd, self::CODE_SPACE_BEFORE_ARRAY_CLOSE, $data); + if (! $fix) { + return; + } + + if ($spaceLength === 0) { + $phpcsFile->fixer->addContentBefore($arrayEnd, str_repeat(' ', $this->spacesAroundBrackets)); + } else { + $phpcsFile->fixer->replaceToken($whitespacePointer, str_repeat(' ', $this->spacesAroundBrackets)); + } + } + + private function checkWhitespaceBeforeComma(File $phpcsFile, int $comma): void + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$comma - 1]['code'] !== T_WHITESPACE) { + return; + } + + $error = 'Expected 0 spaces between "%s" and comma; %s found'; + $content = $tokens[$comma - 2]['content']; + $spaceLength = $tokens[$comma - 1]['length']; + $fix = $phpcsFile->addFixableError($error, $comma, self::CODE_SPACE_BEFORE_COMMA, [$content, $spaceLength]); + if (! $fix) { + return; + } + + $phpcsFile->fixer->replaceToken($comma - 1, ''); + } + + private function checkWhitespaceAfterComma(File $phpcsFile, int $comma): void + { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$comma + 1]['code'] !== T_WHITESPACE) { + $error = 'Expected 1 space between comma and "%s"; 0 found'; + $content = $tokens[$comma + 1]['content']; + $fix = $phpcsFile->addFixableError($error, $comma, self::CODE_SPACE_AFTER_COMMA, [$content]); + if ($fix) { + $phpcsFile->fixer->addContent($comma, ' '); + } + + return; + } + + $spaceLength = $tokens[$comma + 1]['length']; + if ($spaceLength === 1) { + return; + } + + $error = 'Expected 1 space between comma and "%s"; %s found'; + $content = $tokens[$comma + 2]['content']; + $fix = $phpcsFile->addFixableError($error, $comma, self::CODE_SPACE_AFTER_COMMA, [$content, $spaceLength]); + if (! $fix) { + return; + } + + $phpcsFile->fixer->replaceToken($comma + 1, ' '); + } + +} diff --git a/tests/Sniffs/Arrays/SingleLineArrayWhitespaceSniffTest.php b/tests/Sniffs/Arrays/SingleLineArrayWhitespaceSniffTest.php new file mode 100644 index 000000000..7048406f0 --- /dev/null +++ b/tests/Sniffs/Arrays/SingleLineArrayWhitespaceSniffTest.php @@ -0,0 +1,52 @@ +getErrorCount()); + + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_COMMA, '1 found'); + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_COMMA, '2 found'); + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_COMMA, '0 found'); + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_COMMA, '5 found'); + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_COMMA, '5 found'); + + self::assertSniffError($file, 5, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_ARRAY_OPEN, '1 found'); + self::assertSniffError($file, 5, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_ARRAY_CLOSE, '1 found'); + self::assertSniffError($file, 6, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_ARRAY_OPEN, '1 found'); + self::assertSniffError($file, 6, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_ARRAY_CLOSE, '1 found'); + self::assertSniffError($file, 7, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_ARRAY_OPEN, '1 found'); + self::assertSniffError($file, 7, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_ARRAY_CLOSE, '1 found'); + self::assertSniffError($file, 8, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_ARRAY_OPEN, '1 found'); + self::assertSniffError($file, 8, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_ARRAY_CLOSE, '1 found'); + self::assertCount(4, $file->getErrors()[8]); + + self::assertAllFixedInFile($file); + } + + public function testRequireSpacesErrors(): void + { + $file = self::checkFile(__DIR__ . '/data/singleLineArrayWhitespaceRequireSpacesErrors.php', ['spacesAroundBrackets' => 1]); + + self::assertSame(4, $file->getErrorCount()); + + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_AFTER_ARRAY_OPEN, '0 found'); + self::assertSniffError($file, 3, SingleLineArrayWhitespaceSniff::CODE_SPACE_BEFORE_ARRAY_CLOSE, '0 found'); + + self::assertAllFixedInFile($file); + } + +} diff --git a/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.fixed.php b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.fixed.php new file mode 100644 index 000000000..37c10b52d --- /dev/null +++ b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.fixed.php @@ -0,0 +1,8 @@ + '1', 2 => 3, 'e']]; + +$array = [1 => 2]; +$array = [[1 => 2]]; +$array = [[1 => 2]]; +$array = [[1 => 2]]; diff --git a/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.php b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.php new file mode 100644 index 000000000..aff6f4ab6 --- /dev/null +++ b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceErrors.php @@ -0,0 +1,8 @@ + '1' , 2 => 3, 'e']]; + +$array = [ 1 => 2 ]; +$array = [ [1 => 2] ]; +$array = [[ 1 => 2 ]]; +$array = [ [ 1 => 2 ] ]; diff --git a/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceNoErrors.php b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceNoErrors.php new file mode 100644 index 000000000..2eac12e0b --- /dev/null +++ b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceNoErrors.php @@ -0,0 +1,24 @@ + '1', 2 => 3, 'e']]; + +$multiLine = [ + 'a', + 'b', + 1, + 'c', + [ + 'd' => '1', + 2 => 3, + 'e', + ], +]; + +$array = [1 => 2]; +$array = [[1 => 2]]; +$array = [ + [1 => 2], +]; + +$array = [1, run([1, 2], 3)]; +$array = [1,]; diff --git a/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceRequireSpacesErrors.fixed.php b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceRequireSpacesErrors.fixed.php new file mode 100644 index 000000000..481317a76 --- /dev/null +++ b/tests/Sniffs/Arrays/data/singleLineArrayWhitespaceRequireSpacesErrors.fixed.php @@ -0,0 +1,3 @@ +