diff --git a/WordPress/Sniffs/WP/I18nSniff.php b/WordPress/Sniffs/WP/I18nSniff.php index 33158ed08e..6463b7179e 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_whitespace( $matched_content, $param_name, $param_info ); } } } @@ -804,6 +805,42 @@ private function check_string_has_no_html_wrapper( $matched_content, $param_name } } + /** + * Check if a translatable string has leading or trailing whitespace. + * + * @since 3.4.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_whitespace( $matched_content, $param_name, $param_info ) { + $content_without_quotes = TextStrings::stripQuotes( $param_info['clean'] ); + $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_info['start'], ( $param_info['end'] + 1 ), true ); + + if ( preg_match( '/^\s+/u', $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have leading whitespace. Found: %s', + $first_non_empty, + 'LeadingWhiteSpace', + array( $param_info['clean'] ) + ); + } + + if ( preg_match( '/\s+$/u', $content_without_quotes ) ) { + $this->phpcsFile->addError( + 'Translatable string should not have trailing whitespace. Found: %s', + $first_non_empty, + 'TrailingWhiteSpace', + 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 3abf8ec3ba..0b6a694da4 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc @@ -328,4 +328,56 @@ MyNamespace\__( 'foo', 'my-slug' ); // Ok. namespace\esc_html_e( 'foo', '' ); // The sniff should start flagging this once it can resolve relative namespaces. namespace\Sub\translate( 'foo', 'my-slug' ); // Ok. +__( ' 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. +__( ' string', 'my-slug' ); // Bad: leading tab + space (1 leading whitespace error). +__( 'string ', 'my-slug' ); // Bad: trailing space + tab (1 trailing whitespace error). +__( ' string ', 'my-slug' ); // Bad: mixed leading and trailing whitespace (2 errors). +__( ' + string +', 'my-slug' ); // Bad: leading newline+tab and trailing tab+newline (2 errors). + // 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 ae6358abbf..faecf34381 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed @@ -328,4 +328,56 @@ MyNamespace\__( 'foo', 'my-slug' ); // Ok. namespace\esc_html_e( 'foo', '' ); // The sniff should start flagging this once it can resolve relative namespaces. namespace\Sub\translate( 'foo', 'my-slug' ); // Ok. +__( ' 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. +__( ' string', 'my-slug' ); // Bad: leading tab + space (1 leading whitespace error). +__( 'string ', 'my-slug' ); // Bad: trailing space + tab (1 trailing whitespace error). +__( ' string ', 'my-slug' ); // Bad: mixed leading and trailing whitespace (2 errors). +__( ' + string +', 'my-slug' ); // Bad: leading newline+tab and trailing tab+newline (2 errors). + // phpcs:enable WordPress.WP.I18n.MissingTranslatorsComment diff --git a/WordPress/Tests/WP/I18nUnitTest.php b/WordPress/Tests/WP/I18nUnitTest.php index 20e7accaab..caa037ecdf 100644 --- a/WordPress/Tests/WP/I18nUnitTest.php +++ b/WordPress/Tests/WP/I18nUnitTest.php @@ -149,6 +149,47 @@ public function getErrorList( $testFile = '' ) { 315 => 1, 318 => 1, 323 => 1, + 331 => 1, + 332 => 1, + 333 => 2, + 334 => 1, + 335 => 1, + 336 => 1, + 337 => 2, + 338 => 1, + 339 => 1, + 340 => 2, + 341 => 1, + 342 => 1, + 343 => 2, + 344 => 1, + 345 => 1, + 346 => 2, + 347 => 1, + 348 => 1, + 349 => 2, + 350 => 1, + 351 => 1, + 352 => 2, + 353 => 1, + 354 => 1, + 355 => 2, + 356 => 1, + 357 => 1, + 358 => 1, + 359 => 1, + 360 => 2, + 361 => 1, + 362 => 1, + 363 => 1, + 365 => 1, + 367 => 2, + 370 => 1, + 373 => 1, + 376 => 1, + 377 => 1, + 378 => 2, + 379 => 2, ); case 'I18nUnitTest.2.inc':