From c7d2ac10946fc552cc5b8bdafae9bc8cf59c4f14 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 18:33:09 +0200 Subject: [PATCH 1/2] :sparkles: New `Universal.Classes.DisallowAnonClassParentheses` sniff Sniff to disallow the use of parentheses when declaring an anonymous class without passing parameters. PSR12 has no opinion on whether or not anonymous class declarations without parameters should have parentheses, so parentheses for an anonymous class are not enforced, nor forbidden by the `PSR12.Classes.ClassInstantiation` sniff. This sniff and its sister-sniff `Universal.Classes.RequireAnonClassParentheses`, allow for a consistent codebase regarding anonymous class parentheses, whether you choose to enforce them or forbid them. Includes fixer. Includes unit tests. Includes documentation. Includes metrics. --- .../DisallowAnonClassParenthesesStandard.xml | 20 ++++ .../DisallowAnonClassParenthesesSniff.php | 98 +++++++++++++++++++ .../DisallowAnonClassParenthesesUnitTest.inc | 31 ++++++ ...llowAnonClassParenthesesUnitTest.inc.fixed | 31 ++++++ .../DisallowAnonClassParenthesesUnitTest.php | 49 ++++++++++ 5 files changed, 229 insertions(+) create mode 100644 Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml create mode 100644 Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php create mode 100644 Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc create mode 100644 Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc.fixed create mode 100644 Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.php diff --git a/Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml b/Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml new file mode 100644 index 00000000..d6854dd9 --- /dev/null +++ b/Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml @@ -0,0 +1,20 @@ + + + + + + + {}; +$anon = new class($param) {}; + ]]> + + + () {}; + ]]> + + + diff --git a/Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php b/Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php new file mode 100644 index 00000000..2f57fbe1 --- /dev/null +++ b/Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php @@ -0,0 +1,98 @@ +getTokens(); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false + || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS + ) { + // No parentheses found. + $phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'no'); + return; + } + + if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) { + // Incomplete set of parentheses. Ignore. + $phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes'); + return; + } + + $closer = $tokens[$nextNonEmpty]['parenthesis_closer']; + $hasParams = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $closer, true); + if ($hasParams !== false) { + // There is something between the parentheses. Ignore. + $phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes, with parameter'); + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes'); + + $fix = $phpcsFile->addFixableError( + 'Parenthesis not allowed when creating a new anonymous class without passing parameters', + $stackPtr, + 'Found' + ); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($i = $nextNonEmpty; $i <= $closer; $i++) { + if (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { + continue; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc b/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc new file mode 100644 index 00000000..35494db5 --- /dev/null +++ b/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc @@ -0,0 +1,31 @@ +doSomething( new class {} ); // OK. +$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK, has parameter. +$anon = new class // Comment + ($param) {}; // OK, has parameter. + +/* + * Bad. + */ +$util->doSomething( new class() {} ); +$b = new class ( ) extends SomeClass implements SomeInterface {}; +$anon = new class // Comment + () {}; + +$b = new class ( /*comment*/ ) {}; + +// Live coding. +// Intentional parse error. This has to be the last test in the file. +$anon = new class() diff --git a/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc.fixed b/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc.fixed new file mode 100644 index 00000000..b11518f9 --- /dev/null +++ b/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc.fixed @@ -0,0 +1,31 @@ +doSomething( new class {} ); // OK. +$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK, has parameter. +$anon = new class // Comment + ($param) {}; // OK, has parameter. + +/* + * Bad. + */ +$util->doSomething( new class {} ); +$b = new class extends SomeClass implements SomeInterface {}; +$anon = new class // Comment + {}; + +$b = new class /*comment*/ {}; + +// Live coding. +// Intentional parse error. This has to be the last test in the file. +$anon = new class() diff --git a/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.php b/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.php new file mode 100644 index 00000000..2f207b96 --- /dev/null +++ b/Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.php @@ -0,0 +1,49 @@ + => + */ + public function getErrorList() + { + return [ + 22 => 1, + 23 => 1, + 24 => 1, + 27 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} From 36ad68c2c422f46ce887a9cd5869f3904fe8cf1a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 18:34:11 +0200 Subject: [PATCH 2/2] :sparkles: New `Universal.Classes.RequireAnonClassParentheses` sniff Sniff to require the use of parentheses when declaring an anonymous class whether parameters are passed or not. PSR12 has no opinion on whether or not anonymous class declarations without parameters should have parentheses, so parentheses for an anonymous class are not enforced, nor forbidden by the `PSR12.Classes.ClassInstantiation` sniff. This sniff and its sister-sniff `Universal.Classes.DisallowAnonClassParentheses`, allow for a consistent codebase regarding anonymous class parentheses, whether you choose to enforce them or forbid them. Includes fixer. Includes unit tests. Includes documentation. Includes metrics. --- .../RequireAnonClassParenthesesStandard.xml | 19 +++++ .../RequireAnonClassParenthesesSniff.php | 75 +++++++++++++++++++ .../RequireAnonClassParenthesesUnitTest.inc | 27 +++++++ ...uireAnonClassParenthesesUnitTest.inc.fixed | 27 +++++++ .../RequireAnonClassParenthesesUnitTest.php | 47 ++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml create mode 100644 Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php create mode 100644 Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc create mode 100644 Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc.fixed create mode 100644 Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.php diff --git a/Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml b/Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml new file mode 100644 index 00000000..308f2b88 --- /dev/null +++ b/Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml @@ -0,0 +1,19 @@ + + + + + + + () {}; + ]]> + + + {}; + ]]> + + + diff --git a/Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php b/Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php new file mode 100644 index 00000000..7e947078 --- /dev/null +++ b/Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php @@ -0,0 +1,75 @@ +getTokens(); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty !== false + && $tokens[$nextNonEmpty]['code'] === \T_OPEN_PARENTHESIS + ) { + // Parentheses found. + $phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'no'); + + $fix = $phpcsFile->addFixableError( + 'Parenthesis required when creating a new anonymous class.', + $stackPtr, + 'Missing' + ); + + if ($fix === true) { + $phpcsFile->fixer->addContent($stackPtr, '()'); + } + } +} diff --git a/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc b/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc new file mode 100644 index 00000000..d329060b --- /dev/null +++ b/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc @@ -0,0 +1,27 @@ +doSomething( new class() {} ); // OK. +$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK. +$anon = new class // Comment + () {}; // OK. + +/* + * Bad. + */ +$util->doSomething( new class {} ); +$b = new class extends SomeClass implements SomeInterface {}; + +// Live coding. +// Intentional parse error. This has to be the last test in the file. +$anon = new class diff --git a/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc.fixed b/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc.fixed new file mode 100644 index 00000000..0768668a --- /dev/null +++ b/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc.fixed @@ -0,0 +1,27 @@ +doSomething( new class() {} ); // OK. +$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK. +$anon = new class // Comment + () {}; // OK. + +/* + * Bad. + */ +$util->doSomething( new class() {} ); +$b = new class() extends SomeClass implements SomeInterface {}; + +// Live coding. +// Intentional parse error. This has to be the last test in the file. +$anon = new class diff --git a/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.php b/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.php new file mode 100644 index 00000000..3d96c09c --- /dev/null +++ b/Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.php @@ -0,0 +1,47 @@ + => + */ + public function getErrorList() + { + return [ + 22 => 1, + 23 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +}