diff --git a/WordPress/Sniffs/WP/I18nSniff.php b/WordPress/Sniffs/WP/I18nSniff.php index 33158ed08e..e0a96580bb 100644 --- a/WordPress/Sniffs/WP/I18nSniff.php +++ b/WordPress/Sniffs/WP/I18nSniff.php @@ -380,6 +380,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $has_content = $this->check_string_has_translatable_content( $matched_content, $param_name, $param_info ); if ( true === $has_content ) { $this->check_string_has_no_html_wrapper( $matched_content, $param_name, $param_info ); + $this->check_string_has_no_leading_trailing_spaces( $matched_content, $param_name, $param_info ); } } } @@ -804,6 +805,115 @@ private function check_string_has_no_html_wrapper( $matched_content, $param_name } } + /** + * Check if a translatable string has leading or trailing spaces. + * + * @since 3.2.0 + * + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param string $param_name The name of the parameter being examined. + * @param array|false $param_info Parameter info array for an individual parameter, + * as received from the PassedParameters class. + * + * @return void + */ + private function check_string_has_no_leading_trailing_spaces( $matched_content, $param_name, $param_info ) { + // Strip surrounding quotes. + $content_without_quotes = TextStrings::stripQuotes( $param_info['clean'] ); + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + // Define regex patterns. + $pattern_leading_spaces = '/^[\x20]+/u'; + $pattern_trailing_spaces = '/[\x20]+$/u'; + $pattern_leading_tabs = '/^\x09+/u'; + $pattern_trailing_tabs = '/\x09+$/u'; + $pattern_leading_vtabs = '/^\x0B+/u'; + $pattern_trailing_vtabs = '/\x0B+$/u'; + $pattern_leading_newlines = '/^\x0A+/u'; + $pattern_trailing_newlines = '/\x0A+$/u'; + + // Check for leading spaces. + if ( preg_match( $pattern_leading_spaces, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have leading spaces. Found: %s', + $first_non_empty, + 'LeadingSpaces', + array( $param_info['clean'] ) + ); + } + + // Check for trailing spaces. + if ( preg_match( $pattern_trailing_spaces, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have trailing spaces. Found: %s', + $first_non_empty, + 'TrailingSpaces', + array( $param_info['clean'] ) + ); + } + + // Check for leading tabs. + if ( preg_match( $pattern_leading_tabs, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have leading tabs. Found: %s', + $first_non_empty, + 'LeadingTabs', + array( $param_info['clean'] ) + ); + } + + // Check for trailing tabs. + if ( preg_match( $pattern_trailing_tabs, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have trailing tabs. Found: %s', + $first_non_empty, + 'TrailingTabs', + array( $param_info['clean'] ) + ); + } + + // Check for leading vertical tabs. + if ( preg_match( $pattern_leading_vtabs, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have leading vertical tabs. Found: %s', + $first_non_empty, + 'LeadingVTabs', + array( $param_info['clean'] ) + ); + } + + // Check for trailing vertical tabs. + if ( preg_match( $pattern_trailing_vtabs, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have trailing vertical tabs. Found: %s', + $first_non_empty, + 'TrailingVTabs', + array( $param_info['clean'] ) + ); + } + + // Check for leading new lines. + if ( preg_match( $pattern_leading_newlines, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have leading new lines. Found: %s', + $first_non_empty, + 'LeadingNewLines', + array( $param_info['clean'] ) + ); + } + + // Check for trailing new lines. + if ( preg_match( $pattern_trailing_newlines, $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have trailing new lines. Found: %s', + $first_non_empty, + 'TrailingNewLines', + array( $param_info['clean'] ) + ); + } + } + /** * Check for inconsistencies in the placeholders between single and plural form of the translatable text string. * diff --git a/WordPress/Tests/WP/I18nUnitTest.1.inc b/WordPress/Tests/WP/I18nUnitTest.1.inc index 6256e6d84a..3e10d4db49 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc @@ -317,4 +317,50 @@ esc_html_e( 'foo', '' ); // Bad: text-domain can not be empty. // PHP 8.0+: safeguard handling of newly introduced placeholders. __( 'There are %1$h monkeys in the %H', 'my-slug' ); // Bad: multiple arguments should be numbered. +__( ' string', 'my-slug' ); // Bad: leading space. +_e( 'string ', 'my-slug' ); // Bad: trailing space. +_x( ' string ', 'context', 'my-slug' ); // Bad: leading and trailing spaces. +_ex( ' string', 'context', 'my-slug' ); // Bad: leading spaces. +_n( ' There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys', $number, 'my-slug' ); // Bad: leading space. +_n( 'There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys ', $number, 'my-slug' ); // Bad: trailing space. +_n( ' There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys ', $number, 'my-slug' ); // Bad: leading and trailing spaces. +_nx( ' I have %d cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // Bad: leading spaces. +_nx( 'I have %d cat.', 'I have %d cats. ', $number, 'Not really.', 'my-slug' ); // Bad: trailing spaces. +_nx( ' I have %d cat.', 'I have %d cats. ', $number, 'Not really.', 'my-slug' ); // Bad: leading and trailing spaces. +_n_noop( ' I have %d cat.', 'I have %d cats.', 'my-slug' ); // Bad: leading space. +_n_noop( 'I have %d cat.', 'I have %d cats. ', 'my-slug' ); // Bad: trailing space. +_n_noop( ' I have %d cat.', 'I have %d cats. ', 'my-slug' ); // Bad: leading and trailing spaces. +_nx_noop( ' I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug' ); // Bad: leading spaces. +_nx_noop( 'I have %d cat.', 'I have %d cats. ', 'Not really.', 'my-slug' ); // Bad: trailing spaces. +_nx_noop( ' I have %d cat.', 'I have %d cats. ', 'Not really.', 'my-slug' ); // Bad: leading and trailing spaces. +esc_html__( ' string', 'my-slug' ); // Bad: leading space. +esc_html_e( 'string ', 'my-slug' ); // Bad: trailing space. +esc_html_x( ' string ', 'context', 'my-slug' ); // Bad: leading and trailing spaces. +esc_attr__( ' string', 'my-slug' ); // Bad: leading space. +esc_attr_e( 'string ', 'my-slug' ); // Bad: trailing space. +esc_attr_x( ' string ', 'context', 'my-slug' ); // Bad: leading and trailing spaces. +__( ' string', 'my-slug' ); // Bad: leading tab. +__( 'string ', 'my-slug' ); // Bad: trailing tab. +__( ' string ', 'my-slug' ); // Bad: leading and trailing tabs. +__( ' string', 'my-slug' ); // Bad: two leading tabs. +__( 'string ', 'my-slug' ); // Bad: two trailing tabs. +__( " String with leading vertical tab", 'my-slug' ); // Bad: leading vertical tab. +__( "String with trailing vertical tab ", 'my-slug' ); // Bad: trailing vertical tab. +__( " String with leading and trailing vertical tabs ", 'my-slug' ); // Bad: leading and trailing vertical tabs. +__( " String with 2 leading vertical tabs", 'my-slug' ); // Bad: 2 leading vertical tabs. +__( "String with 2 trailing vertical tabs ", 'my-slug' ); // Bad: 2 trailing vertical tabs. +__( ' +string', 'my-slug' ); // Bad: leading new line. +__( 'string +', 'my-slug' ); // Bad: trailing new line. +__( ' +string +', 'my-slug' ); // Bad: leading and trailing new line. +__( ' + +string', 'my-slug' ); // Bad: two leading new lines. +__( 'string + +', 'my-slug' ); // Bad: two trailing new lines. + // phpcs:enable WordPress.WP.I18n.MissingTranslatorsComment diff --git a/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed index b23e9b6192..17558228da 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed @@ -317,4 +317,50 @@ esc_html_e( 'foo', '' ); // Bad: text-domain can not be empty. // PHP 8.0+: safeguard handling of newly introduced placeholders. __( 'There are %1$h monkeys in the %H', 'my-slug' ); // Bad: multiple arguments should be numbered. +__( ' string', 'my-slug' ); // Bad: leading space. +_e( 'string ', 'my-slug' ); // Bad: trailing space. +_x( ' string ', 'context', 'my-slug' ); // Bad: leading and trailing spaces. +_ex( ' string', 'context', 'my-slug' ); // Bad: leading spaces. +_n( ' There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys', $number, 'my-slug' ); // Bad: leading space. +_n( 'There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys ', $number, 'my-slug' ); // Bad: trailing space. +_n( ' There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys ', $number, 'my-slug' ); // Bad: leading and trailing spaces. +_nx( ' I have %d cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // Bad: leading spaces. +_nx( 'I have %d cat.', 'I have %d cats. ', $number, 'Not really.', 'my-slug' ); // Bad: trailing spaces. +_nx( ' I have %d cat.', 'I have %d cats. ', $number, 'Not really.', 'my-slug' ); // Bad: leading and trailing spaces. +_n_noop( ' I have %d cat.', 'I have %d cats.', 'my-slug' ); // Bad: leading space. +_n_noop( 'I have %d cat.', 'I have %d cats. ', 'my-slug' ); // Bad: trailing space. +_n_noop( ' I have %d cat.', 'I have %d cats. ', 'my-slug' ); // Bad: leading and trailing spaces. +_nx_noop( ' I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug' ); // Bad: leading spaces. +_nx_noop( 'I have %d cat.', 'I have %d cats. ', 'Not really.', 'my-slug' ); // Bad: trailing spaces. +_nx_noop( ' I have %d cat.', 'I have %d cats. ', 'Not really.', 'my-slug' ); // Bad: leading and trailing spaces. +esc_html__( ' string', 'my-slug' ); // Bad: leading space. +esc_html_e( 'string ', 'my-slug' ); // Bad: trailing space. +esc_html_x( ' string ', 'context', 'my-slug' ); // Bad: leading and trailing spaces. +esc_attr__( ' string', 'my-slug' ); // Bad: leading space. +esc_attr_e( 'string ', 'my-slug' ); // Bad: trailing space. +esc_attr_x( ' string ', 'context', 'my-slug' ); // Bad: leading and trailing spaces. +__( ' string', 'my-slug' ); // Bad: leading tab. +__( 'string ', 'my-slug' ); // Bad: trailing tab. +__( ' string ', 'my-slug' ); // Bad: leading and trailing tabs. +__( ' string', 'my-slug' ); // Bad: two leading tabs. +__( 'string ', 'my-slug' ); // Bad: two trailing tabs. +__( " String with leading vertical tab", 'my-slug' ); // Bad: leading vertical tab. +__( "String with trailing vertical tab ", 'my-slug' ); // Bad: trailing vertical tab. +__( " String with leading and trailing vertical tabs ", 'my-slug' ); // Bad: leading and trailing vertical tabs. +__( " String with 2 leading vertical tabs", 'my-slug' ); // Bad: 2 leading vertical tabs. +__( "String with 2 trailing vertical tabs ", 'my-slug' ); // Bad: 2 trailing vertical tabs. +__( ' +string', 'my-slug' ); // Bad: leading new line. +__( 'string +', 'my-slug' ); // Bad: trailing new line. +__( ' +string +', 'my-slug' ); // Bad: leading and trailing new line. +__( ' + +string', 'my-slug' ); // Bad: two leading new lines. +__( 'string + +', 'my-slug' ); // Bad: two trailing new lines. + // phpcs:enable WordPress.WP.I18n.MissingTranslatorsComment diff --git a/WordPress/Tests/WP/I18nUnitTest.php b/WordPress/Tests/WP/I18nUnitTest.php index 3834eb189f..131acc2cb4 100644 --- a/WordPress/Tests/WP/I18nUnitTest.php +++ b/WordPress/Tests/WP/I18nUnitTest.php @@ -148,6 +148,43 @@ public function getErrorList( $testFile = '' ) { 311 => 1, 315 => 1, 318 => 1, + 320 => 1, + 321 => 1, + 322 => 2, + 323 => 1, + 324 => 1, + 325 => 1, + 326 => 2, + 327 => 1, + 328 => 1, + 329 => 2, + 330 => 1, + 331 => 1, + 332 => 2, + 333 => 1, + 334 => 1, + 335 => 2, + 336 => 1, + 337 => 1, + 338 => 2, + 339 => 1, + 340 => 1, + 341 => 2, + 342 => 1, + 343 => 1, + 344 => 2, + 345 => 1, + 346 => 1, + 347 => 1, + 348 => 1, + 349 => 2, + 350 => 1, + 351 => 1, + 352 => 1, + 354 => 1, + 356 => 2, + 359 => 1, + 362 => 1, ); case 'I18nUnitTest.2.inc':