From 7c60c78e24cfeed4553ce6b5ac6a950dca2282f0 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Tue, 9 Jan 2024 10:35:21 +0100 Subject: [PATCH] Revert "fix: do not enable AlphabeticallyOrderedConstantsSniff by default (tmp sniff removal) (#124)" (#130) This reverts commit 74ad7eaacfe00d4e74e1ef1e7863731c031a9048. --- .../AlphabeticallyOrderedConstantsSniff.php | 249 ++++++++++++++++++ ...lphabeticallyOrderedConstantsSniffTest.php | 44 ++++ ...ticallyOrderedConstantsSniffTest.fixed.inc | 29 ++ ...lphabeticallyOrderedConstantsSniffTest.inc | 29 ++ 4 files changed, 351 insertions(+) create mode 100644 src/Cdn77/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniff.php create mode 100644 tests/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniffTest.php create mode 100644 tests/Sniffs/Ordering/data/AlphabeticallyOrderedConstantsSniffTest.fixed.inc create mode 100644 tests/Sniffs/Ordering/data/AlphabeticallyOrderedConstantsSniffTest.inc diff --git a/src/Cdn77/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniff.php b/src/Cdn77/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniff.php new file mode 100644 index 0000000..3699618 --- /dev/null +++ b/src/Cdn77/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniff.php @@ -0,0 +1,249 @@ +findConstantNamesWithValuesByVisibility($phpcsFile); + + if ($namesWithValuesByVisibility === []) { + return; + } + + foreach ($namesWithValuesByVisibility as $visibility => $namesWithValues) { + $constantNames = array_map( + static fn (array $nameWithValue): string => $nameWithValue['name']['lowercaseContent'], + $namesWithValues, + ); + $sortedConstantNames = $constantNames; + sort($sortedConstantNames); + + if ($sortedConstantNames === $constantNames) { + continue; + } + + $firstNameWithValue = $namesWithValues[array_key_first($namesWithValues)]; + $fix = $phpcsFile->addFixableError( + sprintf('%s constant names are not alphabetically ordered.', ucfirst($visibility)), + $firstNameWithValue['name']['ptr'], + self::CodeIncorrectConstantOrder, + ); + + if (! $fix) { + continue; + } + + $this->fix($phpcsFile, $namesWithValues); + } + } + + /** @param list $namesWithValues */ + private function fix(File $file, array $namesWithValues): void + { + $fixer = $file->fixer; + $sortedNameAndValueTokens = $namesWithValues; + usort( + $sortedNameAndValueTokens, + static fn (array $a, array $b): int => $a['name']['lowercaseContent'] <=> $b['name']['lowercaseContent'], + ); + + $fixer->beginChangeset(); + + foreach ($namesWithValues as $key => $nameWithValue) { + $sortedNameAndValueToken = $sortedNameAndValueTokens[$key]; + + $namePointer = $nameWithValue['name']['ptr']; + FixerHelper::removeBetweenIncluding($file, $namePointer, $namePointer); + $fixer->addContent($namePointer, $sortedNameAndValueToken['name']['content']); + + $value = $nameWithValue['value']; + FixerHelper::removeBetweenIncluding($file, $value['startPtr'], $value['endPtr']); + $fixer->addContent($value['startPtr'], $sortedNameAndValueToken['value']['content']); + } + + $fixer->endChangeset(); + } + + /** @return array> */ + private function findConstantNamesWithValuesByVisibility(File $phpcsFile): array + { + $constantNamesWithValues = []; + $tokens = $phpcsFile->getTokens(); + + foreach ($tokens as $stackPtr => $token) { + if ($token['code'] !== T_CONST) { + continue; + } + + $visibility = $this->getVisibility($phpcsFile, $stackPtr); + $constantName = $this->findConstantName($phpcsFile, $stackPtr); + + if ($constantName === null) { + continue; + } + + $equalsTokenPointer = $this->findEqualsPointer($phpcsFile, $constantName['ptr']); + + if ($equalsTokenPointer === null) { + continue; + } + + $value = $this->findValue($phpcsFile, $equalsTokenPointer); + + if ($value === null) { + continue; + } + + $constantNamesWithValues[$visibility][] = [ + 'name' => $constantName, + 'value' => $value, + ]; + } + + return $constantNamesWithValues; + } + + private function getVisibility(File $phpcsFile, int $constStackPtr): string + { + $tokens = $phpcsFile->getTokens(); + $visibilityTokenPointer = $phpcsFile->findPrevious( + types: Tokens::$emptyTokens, + start: $constStackPtr - 1, + exclude: true, + local: true, + ); + + return in_array($tokens[$visibilityTokenPointer]['code'], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true) + ? (string) $tokens[$visibilityTokenPointer]['content'] + : 'public'; + } + + /** @phpstan-return NameShape|null */ + private function findConstantName(File $phpcsFile, int $constStackPtr): array|null + { + $tokens = $phpcsFile->getTokens(); + $constantNameTokenPointer = $phpcsFile->findNext( + types: Tokens::$emptyTokens, + start: $constStackPtr + 1, + exclude: true, + local: true, + ); + + if ($constantNameTokenPointer === false || $tokens[$constantNameTokenPointer]['code'] !== T_STRING) { + return null; + } + + return [ + 'content' => $tokens[$constantNameTokenPointer]['content'], + 'lowercaseContent' => strtolower($tokens[$constantNameTokenPointer]['content']), + 'ptr' => $constantNameTokenPointer, + ]; + } + + private function findEqualsPointer(File $phpcsFile, int $constNameStackPtr): int|null + { + $tokens = $phpcsFile->getTokens(); + $equalsTokenPointer = $phpcsFile->findNext( + types: Tokens::$emptyTokens, + start: $constNameStackPtr + 1, + exclude: true, + local: true, + ); + + if ($equalsTokenPointer === false || $tokens[$equalsTokenPointer]['code'] !== T_EQUAL) { + return null; + } + + return $equalsTokenPointer; + } + + /** @phpstan-return ValueShape|null */ + private function findValue(File $phpcsFile, int $equalsTokenPointer): array|null + { + $tokens = $phpcsFile->getTokens(); + $startValueTokenPointer = $phpcsFile->findNext( + types: Tokens::$emptyTokens, + start: $equalsTokenPointer + 1, + exclude: true, + local: true, + ); + + if ($startValueTokenPointer === false) { + return null; + } + + $endValueTokenPointer = $startValueTokenPointer; + $valueToken = $tokens[$endValueTokenPointer]; + $values = []; + + while ($valueToken['code'] !== T_SEMICOLON) { + if ( + $valueToken['code'] === T_WHITESPACE + && in_array($valueToken['content'], ["\n", "\r\n", "\r"], true) + ) { + return null; + } + + $values[] = $valueToken['content']; + $valueToken = $tokens[++$endValueTokenPointer]; + } + + return [ + 'content' => implode('', $values), + 'startPtr' => $startValueTokenPointer, + 'endPtr' => $endValueTokenPointer - 1, + ]; + } +} diff --git a/tests/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniffTest.php b/tests/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniffTest.php new file mode 100644 index 0000000..ec1d05d --- /dev/null +++ b/tests/Sniffs/Ordering/AlphabeticallyOrderedConstantsSniffTest.php @@ -0,0 +1,44 @@ + AlphabeticallyOrderedConstantsSniff::CodeIncorrectConstantOrder, + 19 => AlphabeticallyOrderedConstantsSniff::CodeIncorrectConstantOrder, + 25 => AlphabeticallyOrderedConstantsSniff::CodeIncorrectConstantOrder, + ]; + $possibleLines = array_keys($expectedErrors); + $errors = $file->getErrors(); + + foreach ($errors as $line => $error) { + self::assertContains($line, $possibleLines, json_encode($error, JSON_THROW_ON_ERROR)); + + $expectedError = $expectedErrors[$line]; + self::assertSniffError($file, $line, $expectedError); + } + + self::assertSame(3, $file->getErrorCount()); + + $file->disableCaching(); + $file->fixer->fixFile(); + + self::assertStringEqualsFile( + __DIR__ . '/data/AlphabeticallyOrderedConstantsSniffTest.fixed.inc', + $file->fixer->getContents(), + ); + } +} diff --git a/tests/Sniffs/Ordering/data/AlphabeticallyOrderedConstantsSniffTest.fixed.inc b/tests/Sniffs/Ordering/data/AlphabeticallyOrderedConstantsSniffTest.fixed.inc new file mode 100644 index 0000000..7210d77 --- /dev/null +++ b/tests/Sniffs/Ordering/data/AlphabeticallyOrderedConstantsSniffTest.fixed.inc @@ -0,0 +1,29 @@ +