From 53b40c5b31c6a2265a8528bc148b886daa405020 Mon Sep 17 00:00:00 2001 From: Jay McPartland Date: Wed, 12 Mar 2025 18:43:16 +0000 Subject: [PATCH 1/2] Add Sniff to ensure usage of Stringable interface --- BigBite/Docs/Classes/StringableStandard.xml | 27 +++++ BigBite/Sniffs/Classes/StringableSniff.php | 107 ++++++++++++++++++ .../Tests/Classes/StringableUnitTest.1.inc | 29 +++++ .../Classes/StringableUnitTest.1.inc.fixed | 29 +++++ BigBite/Tests/Classes/StringableUnitTest.php | 61 ++++++++++ 5 files changed, 253 insertions(+) create mode 100644 BigBite/Docs/Classes/StringableStandard.xml create mode 100644 BigBite/Sniffs/Classes/StringableSniff.php create mode 100644 BigBite/Tests/Classes/StringableUnitTest.1.inc create mode 100644 BigBite/Tests/Classes/StringableUnitTest.1.inc.fixed create mode 100644 BigBite/Tests/Classes/StringableUnitTest.php diff --git a/BigBite/Docs/Classes/StringableStandard.xml b/BigBite/Docs/Classes/StringableStandard.xml new file mode 100644 index 0000000..5688832 --- /dev/null +++ b/BigBite/Docs/Classes/StringableStandard.xml @@ -0,0 +1,27 @@ + + + + + + + implements Stringable { + public function __toString(): string { + return __CLASS__; + } +} + ]]> + + + { + public function __toString(): string { + return __CLASS__; + } +} + ]]> + + + diff --git a/BigBite/Sniffs/Classes/StringableSniff.php b/BigBite/Sniffs/Classes/StringableSniff.php new file mode 100644 index 0000000..8888732 --- /dev/null +++ b/BigBite/Sniffs/Classes/StringableSniff.php @@ -0,0 +1,107 @@ + + */ + public $supportedTokenizers = array( + 'PHP', + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + if ( version_compare( phpversion(), '8.0.0', '<' ) ) { + return array(); + } + + return array( \T_FUNCTION ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + $fnName = $phpcsFile->findNext( T_STRING, $stackPtr, ( $stackPtr + 5 ), false, null, true ); + + if ( false === $fnName || '__toString' !== $tokens[ $fnName ]['content'] ) { + return; + } + + $classDecl = $phpcsFile->findPrevious( T_CLASS, $stackPtr, 0 ); + + // this __toString function isn't a class member? + if ( false === $classDecl ) { + return; + } + + $interfaces = ObjectDeclarations::findImplementedInterfaceNames( $phpcsFile, $classDecl ); + + // we're good - class containing __toString implements the interface. + if ( is_array( $interfaces ) && in_array( 'Stringable', $interfaces, true ) ) { + return; + } + + $message = 'Classes that declare "__toString" should implement the Stringable interface.'; + $doWeFix = $phpcsFile->addFixableError( $message, $classDecl, 'NotImplemented', array(), 0 ); + + if ( true !== $doWeFix ) { + return; + } + + $openingCurly = $tokens[ $classDecl ]['scope_opener']; + + if ( $tokens[ $openingCurly ]['line'] === $tokens[ $classDecl ]['line'] ) { + $prevToken = $phpcsFile->findPrevious( T_STRING, $openingCurly, $classDecl ); + + if ( false === $prevToken ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( $prevToken, ' implements Stringable' ); + $phpcsFile->fixer->endChangeset(); + + return; + } + + $endOfLine = $phpcsFile->findPrevious( T_WHITESPACE, $openingCurly, $classDecl ); + + if ( false === $endOfLine ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore( $endOfLine, ' implements Stringable' ); + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/BigBite/Tests/Classes/StringableUnitTest.1.inc b/BigBite/Tests/Classes/StringableUnitTest.1.inc new file mode 100644 index 0000000..b15eae2 --- /dev/null +++ b/BigBite/Tests/Classes/StringableUnitTest.1.inc @@ -0,0 +1,29 @@ + + */ + public function getErrorList( $testFile = '' ) { + if ( version_compare( phpversion(), '8.0.0', '<' ) ) { + return array(); + } + + switch ( $testFile ) { + case 'StringableUnitTest.1.inc': + return array( + 12 => 1, + 18 => 1, + 24 => 1, + ); + default: + return array(); + } + } + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @param string $testFile The name of the file being tested. + * + * @return array + */ + public function getWarningList( $testFile = '' ) { + return array(); + } +} From 00b93bf782ec7dad76d1a0e7115fe15c0c1232ad Mon Sep 17 00:00:00 2001 From: Jay McPartland Date: Mon, 17 Mar 2025 12:39:30 +0000 Subject: [PATCH 2/2] Fix typo in Stringable Sniff documentation --- BigBite/Docs/Classes/StringableStandard.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BigBite/Docs/Classes/StringableStandard.xml b/BigBite/Docs/Classes/StringableStandard.xml index 5688832..dc46959 100644 --- a/BigBite/Docs/Classes/StringableStandard.xml +++ b/BigBite/Docs/Classes/StringableStandard.xml @@ -7,7 +7,7 @@ implements Stringable { +class Foo implements Stringable { public function __toString(): string { return __CLASS__; }