diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index cf03cc18..bc350b79 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -68,6 +68,7 @@ + diff --git a/WordPress/Sniffs/Security/EscapeOutputSniff.php b/WordPress/Sniffs/Security/EscapeOutputSniff.php index 5d5a8272..7d2d4727 100644 --- a/WordPress/Sniffs/Security/EscapeOutputSniff.php +++ b/WordPress/Sniffs/Security/EscapeOutputSniff.php @@ -216,22 +216,35 @@ class EscapeOutputSniff extends AbstractFunctionRestrictionsSniff { case \T_THROW: // Find the open parentheses, while stepping over the exception creation tokens. - $ignore = Tokens::$emptyTokens; - $ignore += Collections::namespacedNameTokens(); - $ignore += Collections::functionCallTokens(); - $ignore += Collections::objectOperators(); + $ignore = Tokens::$emptyTokens; + $ignore += Collections::namespacedNameTokens(); + $ignore += Collections::functionCallTokens(); + $ignore += Collections::objectOperators(); + $ignore[ \T_READONLY ] = \T_READONLY; - $next_relevant = $this->phpcsFile->findNext( $ignore, ( $stackPtr + 1 ), null, true ); - if ( false === $next_relevant ) { - return; - } - - if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) { + $next_relevant = $stackPtr; + do { $next_relevant = $this->phpcsFile->findNext( $ignore, ( $next_relevant + 1 ), null, true ); if ( false === $next_relevant ) { return; } - } + + if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) { + continue; + } + + // Skip over attribute declarations when searching for the open parenthesis. + if ( \T_ATTRIBUTE === $this->tokens[ $next_relevant ]['code'] ) { + if ( isset( $this->tokens[ $next_relevant ]['attribute_closer'] ) === false ) { + return; + } + + $next_relevant = $this->tokens[ $next_relevant ]['attribute_closer']; + continue; + } + + break; + } while ( $next_relevant < ( $this->phpcsFile->numTokens - 1 ) ); if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $next_relevant ]['code'] || isset( $this->tokens[ $next_relevant ]['parenthesis_closer'] ) === false diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc b/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc index eaf96f5b..e1766c7d 100644 --- a/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc @@ -662,3 +662,17 @@ die( status: esc_html( $foo ) ); // Ok. exit( status: $foo ); // Bad. die( status: $foo ); // Bad. + +/* + * Issue https://github.com/WordPress/WordPress-Coding-Standards/issues/2552 + * Ensure that readonly anonymous classes and anonymous classes with attributes are handled + * correctly when part of a throw statement. + */ +throw new #[MyAttribute] readonly class( esc_html( $message ) ) extends Exception {}; // Good. +throw new readonly class( $unescaped ) {}; // Bad. +throw new #[MyAttribute] class( $unescaped ) extends Exception {}; // Bad. +throw new +#[Attribute1] +/* some comment */ +#[Attribute2('text', 10)] +readonly class( $unescaped ) {}; // Bad. diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.21.inc b/WordPress/Tests/Security/EscapeOutputUnitTest.21.inc new file mode 100644 index 00000000..249b6091 --- /dev/null +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.21.inc @@ -0,0 +1,8 @@ + 1, 663 => 1, 664 => 1, + 672 => 1, + 673 => 1, + 678 => 1, ); case 'EscapeOutputUnitTest.6.inc':