From 52456f665d1a4bc6cf6c5bf21b9f2f8a73340468 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 26 Oct 2022 07:08:59 +0200 Subject: [PATCH] :sparkles: New `Universal.CodeAnalysis.ConstructorDestructorReturn` sniff New sniff to verify that class constructor/destructors: * [error] Do not have a return type declaration. This would result in a fatal error in PHP. * [warning] Do not return a value via a return statement. Includes unit tests. Includes documentation. Inspired by: * https://wiki.php.net/rfc/make_ctor_ret_void * https://twitter.com/derickr/status/1280996420305276934 --- .../ConstructorDestructorReturnStandard.xml | 64 ++++++++ .../ConstructorDestructorReturnSniff.php | 145 ++++++++++++++++++ .../ConstructorDestructorReturnUnitTest.inc | 125 +++++++++++++++ .../ConstructorDestructorReturnUnitTest.php | 58 +++++++ 4 files changed, 392 insertions(+) create mode 100644 Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml create mode 100644 Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php create mode 100644 Universal/Tests/CodeAnalysis/ConstructorDestructorReturnUnitTest.inc create mode 100644 Universal/Tests/CodeAnalysis/ConstructorDestructorReturnUnitTest.php diff --git a/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml b/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml new file mode 100644 index 00000000..b44b4777 --- /dev/null +++ b/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + : int {} +} + ]]> + + + + + + + + + + + + $this; + } + + public function __destruct() { + // Do something. + return false; + } +} + ]]> + + + diff --git a/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php b/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php new file mode 100644 index 00000000..a62830ed --- /dev/null +++ b/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php @@ -0,0 +1,145 @@ +addError( + '%s can not declare a return type. Found: %s', + $properties['return_type_token'], + 'ReturnTypeFound', + $data + ); + } + + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { + // Abstract/interface method, live coding or parse error. + return; + } + + // Check for a value being returned. + $current = $tokens[$stackPtr]['scope_opener']; + $end = $tokens[$stackPtr]['scope_closer']; + + do { + $current = $phpcsFile->findNext(\T_RETURN, ($current + 1), $end); + if ($current === false) { + break; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $end, true); + if ($next === false + || $tokens[$next]['code'] === \T_SEMICOLON + || $tokens[$next]['code'] === \T_CLOSE_TAG + ) { + // Return statement without value. + continue; + } + + $endOfStatement = BCFile::findEndOfStatement($phpcsFile, $next); + + $data = [ + $functionType, + GetTokensAsString::compact($phpcsFile, $current, $endOfStatement, true), + ]; + + $phpcsFile->addWarning( + '%s can not return a value. Found: "%s"', + $current, + 'ReturnValueFound', + $data + ); + } while ($current < $end); + } +} diff --git a/Universal/Tests/CodeAnalysis/ConstructorDestructorReturnUnitTest.inc b/Universal/Tests/CodeAnalysis/ConstructorDestructorReturnUnitTest.inc new file mode 100644 index 00000000..79bb4956 --- /dev/null +++ b/Universal/Tests/CodeAnalysis/ConstructorDestructorReturnUnitTest.inc @@ -0,0 +1,125 @@ + => + */ + public function getErrorList() + { + return [ + 85 => 1, + 89 => 1, + 101 => 1, + 116 => 1, + 118 => 1, + 122 => 1, + 124 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return [ + 86 => 1, + 90 => 1, + 95 => 1, + 103 => 1, + 107 => 1, + ]; + } +}