diff --git a/.github/release-checklist.md b/.github/release-checklist.md
index 108bea3aa..92f93e024 100644
--- a/.github/release-checklist.md
+++ b/.github/release-checklist.md
@@ -28,7 +28,8 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH
- [ ] `$pluggable_functions` in `WordPress.NamingConventions.PrefixAllGlobals` - PR #xxx
- [ ] `$pluggable_classes` in `WordPress.NamingConventions.PrefixAllGlobals` - PR #xxx
- [ ] `$target_functions` in `WordPress.Security.PluginMenuSlug` - PR #xxx
- - [ ] `$reserved_names` in `WordPress.NamingConventions.ValidPostTypeSlug` - PR #xxx
+ - [ ] `$post_types` in `WPReservedNamesHelper` - PR #xxx
+ - [ ] `$terms` in `WPReservedNamesHelper` - PR #xxx
- [ ] `$wp_time_constants` in `WordPress.WP.CronInterval` - PR #xxx
- [ ] `$known_test_classes` in `IsUnitTestTrait` - PR #xxx
- [ ] ...etc...
diff --git a/WordPress-Extra/ruleset.xml b/WordPress-Extra/ruleset.xml
index 53da8be01..2f10ba81d 100644
--- a/WordPress-Extra/ruleset.xml
+++ b/WordPress-Extra/ruleset.xml
@@ -129,9 +129,12 @@
-
+
+
+
+
diff --git a/WordPress/AbstractValidSlugSniff.php b/WordPress/AbstractValidSlugSniff.php
new file mode 100644
index 000000000..5061b1ee3
--- /dev/null
+++ b/WordPress/AbstractValidSlugSniff.php
@@ -0,0 +1,300 @@
+ Key is reserved name, value irrelevant.
+ */
+ private $reserved_names;
+
+ /**
+ * All valid tokens for the slug parameter.
+ *
+ * Set in `register()`.
+ *
+ * @since 3.2.0
+ *
+ * @var array
+ */
+ private $valid_tokens = array();
+
+ /**
+ * Retrieve function and parameter(s) pairs this sniff is looking for.
+ *
+ * The parameter or an array of parameters keyed by target function.
+ * An array of names is supported to allow for functions for which the
+ * parameter names have undergone name changes over time.
+ *
+ * @since 3.2.0
+ *
+ * @return array> Function parameter(s) pairs.
+ */
+ abstract protected function get_target_functions();
+
+ /**
+ * Retrieve the slug type.
+ *
+ * @since 3.2.0
+ *
+ * @return string The slug type.
+ */
+ abstract protected function get_slug_type();
+
+ /**
+ * Retrieve the plural slug type.
+ *
+ * @since 3.2.0
+ *
+ * @return string The plural slug type.
+ */
+ abstract protected function get_slug_type_plural();
+
+ /**
+ * Retrieve the regex to validate the characters that can be used as
+ * the slug.
+ *
+ * @since 3.2.0
+ *
+ * @return string Regular expression.
+ */
+ abstract protected function get_valid_characters();
+
+ /**
+ * Retrieve the max length of a slug.
+ *
+ * @since 3.2.0
+ *
+ * @return int The maximum length of a slug.
+ */
+ abstract protected function get_max_length();
+
+ /**
+ * Retrieve the reserved names which can not be used by themes and plugins.
+ *
+ * @since 3.2.0
+ *
+ * @return array Key is reserved name, value irrelevant.
+ */
+ abstract protected function get_reserved_names();
+
+ /**
+ * Returns an array of tokens this test wants to listen for.
+ *
+ * @since 2.2.0
+ * @since 3.2.0 - Moved from the ValidPostTypeSlug Sniff class to this class.
+ * - Added setting up properties for target functions and slug details.
+ *
+ * @return array
+ */
+ public function register() {
+ $this->valid_tokens = Tokens::$textStringTokens + Tokens::$heredocTokens + Tokens::$emptyTokens;
+ $this->target_functions = $this->get_target_functions();
+ $this->slug_type = $this->get_slug_type();
+ $this->slug_type_plural = $this->get_slug_type_plural();
+ $this->valid_characters = $this->get_valid_characters();
+ $this->max_length = $this->get_max_length();
+ $this->reserved_names = $this->get_reserved_names();
+ return parent::register();
+ }
+
+ /**
+ * Process the parameter of a matched function.
+ *
+ * Errors on invalid names when reserved names are used,
+ * the name is too long, or contains invalid characters.
+ *
+ * @since 2.2.0
+ * @since 3.2.0 Moved from the ValidPostTypeSlug Sniff class to this class and
+ * modfied for variable target functions and slug restrictions.
+ *
+ * @param int $stackPtr The position of the current token in the stack.
+ * @param string $group_name The name of the group which was matched.
+ * @param string $matched_content The token content (function name) which was matched
+ * in lowercase.
+ * @param array $parameters Array with information about the parameters.
+ *
+ * @return void
+ */
+ public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
+ $slug_param = PassedParameters::getParameterFromStack(
+ $parameters,
+ 1,
+ $this->target_functions[ $matched_content ]
+ );
+ if ( false === $slug_param || '' === $slug_param['clean'] ) {
+ // Error for using empty slug.
+ $this->phpcsFile->addError(
+ '%s() called without a %s slug. The slug must be a non-empty string.',
+ false === $slug_param ? $stackPtr : $slug_param['start'],
+ 'Empty',
+ array(
+ $matched_content,
+ $this->slug_type,
+ )
+ );
+ return;
+ }
+
+ $string_start = $this->phpcsFile->findNext( Collections::textStringStartTokens(), $slug_param['start'], ( $slug_param['end'] + 1 ) );
+ $string_pos = $this->phpcsFile->findNext( Tokens::$textStringTokens, $slug_param['start'], ( $slug_param['end'] + 1 ) );
+
+ $has_invalid_tokens = $this->phpcsFile->findNext( $this->valid_tokens, $slug_param['start'], ( $slug_param['end'] + 1 ), true );
+ if ( false !== $has_invalid_tokens || false === $string_pos ) {
+ // Check for non string based slug parameter (we cannot determine if this is valid).
+ $this->phpcsFile->addWarning(
+ 'The %s slug is not a string literal. It is not possible to automatically determine the validity of this slug. Found: %s.',
+ $stackPtr,
+ 'NotStringLiteral',
+ array(
+ $this->slug_type,
+ $slug_param['clean'],
+ ),
+ 3
+ );
+ return;
+ }
+
+ $slug = TextStrings::getCompleteTextString( $this->phpcsFile, $string_start );
+ if ( isset( Tokens::$heredocTokens[ $this->tokens[ $string_start ]['code'] ] ) ) {
+ // Trim off potential indentation from PHP 7.3 flexible heredoc/nowdoc content.
+ $slug = ltrim( $slug );
+ }
+
+ // Warn for dynamic parts in the slug parameter.
+ if ( 'T_DOUBLE_QUOTED_STRING' === $this->tokens[ $string_pos ]['type']
+ || ( 'T_HEREDOC' === $this->tokens[ $string_pos ]['type']
+ && strpos( $this->tokens[ $string_pos ]['content'], '$' ) !== false )
+ ) {
+ $this->phpcsFile->addWarning(
+ 'The %s slug may, or may not, get too long with dynamic contents and could contain invalid characters. Found: "%s".',
+ $string_pos,
+ 'PartiallyDynamic',
+ array(
+ $this->slug_type,
+ $slug,
+ )
+ );
+ $slug = TextStrings::stripEmbeds( $slug );
+ }
+
+ if ( preg_match( $this->valid_characters, $slug ) === 0 ) {
+ // Error for invalid characters.
+ $this->phpcsFile->addError(
+ '%s() called with invalid %s "%s". %s contains invalid characters. Only lowercase alphanumeric characters, dashes, and underscores are allowed.',
+ $string_pos,
+ 'InvalidCharacters',
+ array(
+ $matched_content,
+ $this->slug_type,
+ $slug,
+ ucfirst( $this->slug_type ),
+ )
+ );
+ }
+
+ if ( isset( $this->reserved_names[ $slug ] ) ) {
+ // Error for using reserved slug names.
+ $this->phpcsFile->addError(
+ '%s() called with reserved %s "%s". Reserved %s should not be used as they interfere with the functioning of WordPress itself.',
+ $string_pos,
+ 'Reserved',
+ array(
+ $matched_content,
+ $this->slug_type,
+ $slug,
+ $this->slug_type_plural,
+ )
+ );
+ } elseif ( stripos( $slug, 'wp_' ) === 0 ) {
+ // Error for using reserved slug prefix.
+ $this->phpcsFile->addError(
+ 'The %s passed to %s() uses a prefix reserved for WordPress itself. Found: "%s".',
+ $string_pos,
+ 'ReservedPrefix',
+ array(
+ $this->slug_type,
+ $matched_content,
+ $slug,
+ )
+ );
+ }
+
+ // Error for slugs that are too long.
+ if ( strlen( $slug ) > $this->max_length ) {
+ $this->phpcsFile->addError(
+ 'A %s slug must not exceed %d characters. Found: "%s" (%d characters).',
+ $string_pos,
+ 'TooLong',
+ array(
+ $this->slug_type,
+ $this->max_length,
+ $slug,
+ strlen( $slug ),
+ )
+ );
+ }
+ }
+}
diff --git a/WordPress/Docs/NamingConventions/ValidPostTypeSlugStandard.xml b/WordPress/Docs/NamingConventions/ValidPostTypeSlugStandard.xml
index 639dda54e..f770b6870 100644
--- a/WordPress/Docs/NamingConventions/ValidPostTypeSlugStandard.xml
+++ b/WordPress/Docs/NamingConventions/ValidPostTypeSlugStandard.xml
@@ -11,17 +11,17 @@
'my_short_slug',
- array()
+register_post_type(
+ 'my_short_slug',
+ array()
);
]]>
'my_own_post_type_too_long',
- array()
+register_post_type(
+ 'my_own_post_type_too_long',
+ array()
);
]]>
@@ -34,17 +34,17 @@ register_post_type(
'my_post_type_slug',
- array()
+register_post_type(
+ 'my_post_type_slug',
+ array()
);
]]>
'my/post/type/slug',
- array()
+register_post_type(
+ 'my/post/type/slug',
+ array()
);
]]>
@@ -57,40 +57,40 @@ register_post_type(
'my_post_active',
- array()
+register_post_type(
+ 'my_post_active',
+ array()
);
]]>
"my_post_{$status}",
- array()
+register_post_type(
+ "my_post_{$status}",
+ array()
);
]]>
'prefixed_author',
- array()
+register_post_type(
+ 'prefixed_author',
+ array()
);
]]>
-
+
'author',
- array()
+register_post_type(
+ 'author',
+ array()
);
]]>
@@ -103,17 +103,17 @@ register_post_type(
'prefixed_author',
- array()
+register_post_type(
+ 'prefixed_author',
+ array()
);
]]>
'wp_author',
- array()
+register_post_type(
+ 'wp_author',
+ array()
);
]]>
diff --git a/WordPress/Docs/NamingConventions/ValidTaxonomySlugStandard.xml b/WordPress/Docs/NamingConventions/ValidTaxonomySlugStandard.xml
new file mode 100644
index 000000000..25f525b2f
--- /dev/null
+++ b/WordPress/Docs/NamingConventions/ValidTaxonomySlugStandard.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+ 'my_short_slug',
+ array()
+);
+ ]]>
+
+
+ 'my_very_own_taxonomy_is_much_too_long',
+ array()
+);
+ ]]>
+
+
+
+
+
+
+
+ 'my_taxonomy_slug',
+ array()
+);
+ ]]>
+
+
+ 'my/taxonomy/slug',
+ array()
+);
+ ]]>
+
+
+
+
+
+
+
+ 'my_taxonomy_active',
+ array()
+);
+ ]]>
+
+
+ "my_taxonomy_{$status}",
+ array()
+);
+ ]]>
+
+
+
+
+
+
+
+ 'prefixed_post_tag',
+ array()
+);
+ ]]>
+
+
+ 'post_tag',
+ array()
+);
+ ]]>
+
+
+
+
+
+
+
+ 'prefixed_post_tag',
+ array()
+);
+ ]]>
+
+
+ 'wp_post_tag',
+ array()
+);
+ ]]>
+
+
+
diff --git a/WordPress/Helpers/WPReservedNamesHelper.php b/WordPress/Helpers/WPReservedNamesHelper.php
new file mode 100644
index 000000000..5ced2f3ae
--- /dev/null
+++ b/WordPress/Helpers/WPReservedNamesHelper.php
@@ -0,0 +1,203 @@
+ Key is reserved post type name, value irrelevant.
+ */
+ private static $post_types = array(
+ 'action' => true, // Not a WP post type, but prevents other problems.
+ 'attachment' => true,
+ 'author' => true, // Not a WP post type, but prevents other problems.
+ 'custom_css' => true,
+ 'customize_changeset' => true,
+ 'nav_menu_item' => true,
+ 'oembed_cache' => true,
+ 'order' => true, // Not a WP post type, but prevents other problems.
+ 'page' => true,
+ 'post' => true,
+ 'revision' => true,
+ 'theme' => true, // Not a WP post type, but prevents other problems.
+ 'user_request' => true,
+ 'wp_block' => true,
+ 'wp_font_face' => true,
+ 'wp_font_family' => true,
+ 'wp_global_styles' => true,
+ 'wp_navigation' => true,
+ 'wp_template' => true,
+ 'wp_template_part' => true,
+ );
+
+ /**
+ * Array of reserved taxonomy names which can not be used by themes and plugins.
+ *
+ * Source: {@link https://developer.wordpress.org/reference/functions/register_taxonomy/#reserved-terms}
+ *
+ * {@internal To be updated after every major release. Last updated for WordPress 6.6.1.}
+ *
+ * @since 3.2.0
+ *
+ * @var array Key is reserved taxonomy name, value irrelevant.
+ */
+ private static $terms = array(
+ 'attachment' => true,
+ 'attachment_id' => true,
+ 'author' => true,
+ 'author_name' => true,
+ 'calendar' => true,
+ 'cat' => true,
+ 'category' => true,
+ 'category__and' => true,
+ 'category__in' => true,
+ 'category__not_in' => true,
+ 'category_name' => true,
+ 'comments_per_page' => true,
+ 'comments_popup' => true,
+ 'custom' => true,
+ 'customize_messenger_channel' => true,
+ 'customized' => true,
+ 'cpage' => true,
+ 'day' => true,
+ 'debug' => true,
+ 'embed' => true,
+ 'error' => true,
+ 'exact' => true,
+ 'feed' => true,
+ 'fields' => true,
+ 'hour' => true,
+ 'link_category' => true,
+ 'm' => true,
+ 'minute' => true,
+ 'monthnum' => true,
+ 'more' => true,
+ 'name' => true,
+ 'nav_menu' => true,
+ 'nonce' => true,
+ 'nopaging' => true,
+ 'offset' => true,
+ 'order' => true,
+ 'orderby' => true,
+ 'p' => true,
+ 'page' => true,
+ 'page_id' => true,
+ 'paged' => true,
+ 'pagename' => true,
+ 'pb' => true,
+ 'perm' => true,
+ 'post' => true,
+ 'post__in' => true,
+ 'post__not_in' => true,
+ 'post_format' => true,
+ 'post_mime_type' => true,
+ 'post_status' => true,
+ 'post_tag' => true,
+ 'post_type' => true,
+ 'posts' => true,
+ 'posts_per_archive_page' => true,
+ 'posts_per_page' => true,
+ 'preview' => true,
+ 'robots' => true,
+ 's' => true,
+ 'search' => true,
+ 'second' => true,
+ 'sentence' => true,
+ 'showposts' => true,
+ 'static' => true,
+ 'status' => true,
+ 'subpost' => true,
+ 'subpost_id' => true,
+ 'tag' => true,
+ 'tag__and' => true,
+ 'tag__in' => true,
+ 'tag__not_in' => true,
+ 'tag_id' => true,
+ 'tag_slug__and' => true,
+ 'tag_slug__in' => true,
+ 'taxonomy' => true,
+ 'tb' => true,
+ 'term' => true,
+ 'terms' => true,
+ 'theme' => true,
+ 'title' => true,
+ 'type' => true,
+ 'types' => true,
+ 'w' => true,
+ 'withcomments' => true,
+ 'withoutcomments' => true,
+ 'year' => true,
+ );
+
+ /**
+ * Verify if a given name is a reserved post type name.
+ *
+ * @since 3.2.0
+ *
+ * @param string $name The name to be checked.
+ *
+ * @return bool
+ */
+ public static function is_reserved_post_type( $name ) {
+ return isset( self::$post_types[ $name ] );
+ }
+
+ /**
+ * Verify if a given name is a reserved taxonomy name.
+ *
+ * @since 3.2.0
+ *
+ * @param string $name The name to be checked.
+ *
+ * @return bool
+ */
+ public static function is_reserved_term( $name ) {
+ return isset( self::$terms[ $name ] )
+ || isset( self::$post_types[ $name ] );
+ }
+
+ /**
+ * Retrieve an array with the reserved post type names.
+ *
+ * @since 3.2.0
+ *
+ * @return array Array with the post type names as keys. The value is irrelevant.
+ */
+ public static function get_post_types() {
+ return self::$post_types;
+ }
+
+ /**
+ * Retrieve an array with the reserved taxonomy names.
+ *
+ * @since 3.2.0
+ *
+ * @return array Array with the taxonomy names as keys. The value is irrelevant.
+ */
+ public static function get_terms() {
+ return array_merge( self::$post_types, self::$terms );
+ }
+}
diff --git a/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php b/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php
index 9b3d3db4b..c38da3158 100644
--- a/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php
+++ b/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php
@@ -9,222 +9,92 @@
namespace WordPressCS\WordPress\Sniffs\NamingConventions;
-use PHP_CodeSniffer\Util\Tokens;
-use PHPCSUtils\Tokens\Collections;
-use PHPCSUtils\Utils\PassedParameters;
-use PHPCSUtils\Utils\TextStrings;
-use WordPressCS\WordPress\AbstractFunctionParameterSniff;
+use WordPressCS\WordPress\AbstractValidSlugSniff;
+use WordPressCS\WordPress\Helpers\WPReservedNamesHelper;
/**
* Validates post type names.
*
- * Checks the post type slug for invalid characters, long function names
- * and reserved names.
+ * Checks post type slugs for the presence of invalid characters, excessive
+ * length, and reserved names.
*
* @link https://developer.wordpress.org/reference/functions/register_post_type/
*
* @since 2.2.0
*/
-final class ValidPostTypeSlugSniff extends AbstractFunctionParameterSniff {
+final class ValidPostTypeSlugSniff extends AbstractValidSlugSniff {
/**
- * Max length of a post type name is limited by the SQL field.
+ * Retrieve function and parameter(s) pairs this sniff is looking for.
*
- * @since 2.2.0
+ * @since 3.2.0
*
- * @var int
+ * @return array> Function parameter(s) pairs.
*/
- const POST_TYPE_MAX_LENGTH = 20;
+ protected function get_target_functions() {
+ return array(
+ 'register_post_type' => array( 'post_type' ),
+ );
+ }
/**
- * Regex to validate the characters that can be used as the post type slug.
+ * Retrieve the slug type.
*
- * @link https://developer.wordpress.org/reference/functions/register_post_type/
- * @since 2.2.0
- * @since 3.0.0 Renamed from `POST_TYPE_CHARACTER_WHITELIST` to `VALID_POST_TYPE_CHARACTERS`.
+ * @since 3.2.0
*
- * @var string
+ * @return string The slug type.
*/
- const VALID_POST_TYPE_CHARACTERS = '/^[a-z0-9_-]+$/';
+ protected function get_slug_type() {
+ return 'post type';
+ }
/**
- * Array of functions that must be checked.
+ * Retrieve the plural slug type.
*
- * @since 2.2.0
+ * @since 3.2.0
*
- * @var array Key is function name, value irrelevant.
+ * @return string The plural slug type.
*/
- protected $target_functions = array(
- 'register_post_type' => true,
- );
+ protected function get_slug_type_plural() {
+ return 'post types';
+ }
/**
- * Array of reserved post type names which can not be used by themes and plugins.
- *
- * Source: {@link https://developer.wordpress.org/reference/functions/register_post_type/#reserved-post-types}
+ * Retrieve regex to validate the characters that can be used as the
+ * post type slug.
*
- * {@internal To be updated after every major release. Last updated for WordPress 6.5-RC3.}
+ * @since 3.2.0
*
- * @since 2.2.0
+ * @link https://developer.wordpress.org/reference/functions/register_post_type/
*
- * @var array Key is reserved post type name, value irrelevant.
+ * @return string
*/
- protected $reserved_names = array(
- 'action' => true, // Not a WP post type, but prevents other problems.
- 'attachment' => true,
- 'author' => true, // Not a WP post type, but prevents other problems.
- 'custom_css' => true,
- 'customize_changeset' => true,
- 'nav_menu_item' => true,
- 'oembed_cache' => true,
- 'order' => true, // Not a WP post type, but prevents other problems.
- 'page' => true,
- 'post' => true,
- 'revision' => true,
- 'theme' => true, // Not a WP post type, but prevents other problems.
- 'user_request' => true,
- 'wp_block' => true,
- 'wp_font_face' => true,
- 'wp_font_family' => true,
- 'wp_global_styles' => true,
- 'wp_navigation' => true,
- 'wp_template' => true,
- 'wp_template_part' => true,
- );
+ protected function get_valid_characters() {
+ return '/^[a-z0-9_-]+$/';
+ }
/**
- * All valid tokens for in the first parameter of register_post_type().
- *
- * Set in `register()`.
+ * Retrieve max length of a post type name.
*
- * @since 2.2.0
- *
- * @var array
- */
- private $valid_tokens = array();
-
- /**
- * Returns an array of tokens this test wants to listen for.
+ * The length is limited by the SQL field.
*
- * @since 2.2.0
+ * @since 3.2.0
*
- * @return array
+ * @return int
*/
- public function register() {
- $this->valid_tokens = Tokens::$textStringTokens + Tokens::$heredocTokens + Tokens::$emptyTokens;
- return parent::register();
+ protected function get_max_length() {
+ return 20;
}
/**
- * Process the parameter of a matched function.
- *
- * Errors on invalid post type names when reserved keywords are used,
- * the post type is too long, or contains invalid characters.
- *
- * @since 2.2.0
+ * Retrieve the reserved post type names which can not be used
+ * by themes and plugins.
*
- * @param int $stackPtr The position of the current token in the stack.
- * @param string $group_name The name of the group which was matched.
- * @param string $matched_content The token content (function name) which was matched
- * in lowercase.
- * @param array $parameters Array with information about the parameters.
+ * @since 3.2.0
*
- * @return void
+ * @return array
*/
- public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
- $post_type_param = PassedParameters::getParameterFromStack( $parameters, 1, 'post_type' );
- if ( false === $post_type_param || '' === $post_type_param['clean'] ) {
- // Error for using empty slug.
- $this->phpcsFile->addError(
- 'register_post_type() called without a post type slug. The slug must be a non-empty string.',
- false === $post_type_param ? $stackPtr : $post_type_param['start'],
- 'Empty'
- );
- return;
- }
-
- $string_start = $this->phpcsFile->findNext( Collections::textStringStartTokens(), $post_type_param['start'], ( $post_type_param['end'] + 1 ) );
- $string_pos = $this->phpcsFile->findNext( Tokens::$textStringTokens, $post_type_param['start'], ( $post_type_param['end'] + 1 ) );
-
- $has_invalid_tokens = $this->phpcsFile->findNext( $this->valid_tokens, $post_type_param['start'], ( $post_type_param['end'] + 1 ), true );
- if ( false !== $has_invalid_tokens || false === $string_pos ) {
- // Check for non string based slug parameter (we cannot determine if this is valid).
- $this->phpcsFile->addWarning(
- 'The post type slug is not a string literal. It is not possible to automatically determine the validity of this slug. Found: %s.',
- $stackPtr,
- 'NotStringLiteral',
- array(
- $post_type_param['clean'],
- ),
- 3
- );
- return;
- }
-
- $post_type = TextStrings::getCompleteTextString( $this->phpcsFile, $string_start );
- if ( isset( Tokens::$heredocTokens[ $this->tokens[ $string_start ]['code'] ] ) ) {
- // Trim off potential indentation from PHP 7.3 flexible heredoc/nowdoc content.
- $post_type = ltrim( $post_type );
- }
-
- $data = array(
- $post_type,
- );
-
- // Warn for dynamic parts in the slug parameter.
- if ( 'T_DOUBLE_QUOTED_STRING' === $this->tokens[ $string_pos ]['type']
- || ( 'T_HEREDOC' === $this->tokens[ $string_pos ]['type']
- && strpos( $this->tokens[ $string_pos ]['content'], '$' ) !== false )
- ) {
- $this->phpcsFile->addWarning(
- 'The post type slug may, or may not, get too long with dynamic contents and could contain invalid characters. Found: "%s".',
- $string_pos,
- 'PartiallyDynamic',
- $data
- );
- $post_type = TextStrings::stripEmbeds( $post_type );
- }
-
- if ( preg_match( self::VALID_POST_TYPE_CHARACTERS, $post_type ) === 0 ) {
- // Error for invalid characters.
- $this->phpcsFile->addError(
- 'register_post_type() called with invalid post type "%s". Post type contains invalid characters. Only lowercase alphanumeric characters, dashes, and underscores are allowed.',
- $string_pos,
- 'InvalidCharacters',
- $data
- );
- }
-
- if ( isset( $this->reserved_names[ $post_type ] ) ) {
- // Error for using reserved slug names.
- $this->phpcsFile->addError(
- 'register_post_type() called with reserved post type "%s". Reserved post types should not be used as they interfere with the functioning of WordPress itself.',
- $string_pos,
- 'Reserved',
- $data
- );
- } elseif ( stripos( $post_type, 'wp_' ) === 0 ) {
- // Error for using reserved slug prefix.
- $this->phpcsFile->addError(
- 'The post type passed to register_post_type() uses a prefix reserved for WordPress itself. Found: "%s".',
- $string_pos,
- 'ReservedPrefix',
- $data
- );
- }
-
- // Error for slugs that are too long.
- if ( strlen( $post_type ) > self::POST_TYPE_MAX_LENGTH ) {
- $this->phpcsFile->addError(
- 'A post type slug must not exceed %d characters. Found: "%s" (%d characters).',
- $string_pos,
- 'TooLong',
- array(
- self::POST_TYPE_MAX_LENGTH,
- $post_type,
- strlen( $post_type ),
- )
- );
- }
+ protected function get_reserved_names() {
+ return WPReservedNamesHelper::get_post_types();
}
}
diff --git a/WordPress/Sniffs/NamingConventions/ValidTaxonomySlugSniff.php b/WordPress/Sniffs/NamingConventions/ValidTaxonomySlugSniff.php
new file mode 100644
index 000000000..8b6819d87
--- /dev/null
+++ b/WordPress/Sniffs/NamingConventions/ValidTaxonomySlugSniff.php
@@ -0,0 +1,100 @@
+> Function parameter(s) pairs.
+ */
+ protected function get_target_functions() {
+ return array(
+ 'register_taxonomy' => array( 'taxonomy' ),
+ );
+ }
+
+ /**
+ * Retrieve the slug type.
+ *
+ * @since 3.2.0
+ *
+ * @return string The slug type.
+ */
+ protected function get_slug_type() {
+ return 'taxonomy';
+ }
+
+ /**
+ * Retrieve the plural slug type.
+ *
+ * @since 3.2.0
+ *
+ * @return string The plural slug type.
+ */
+ protected function get_slug_type_plural() {
+ return 'taxonomies';
+ }
+
+ /**
+ * Retrieve regex to validate the characters that can be used as the
+ * taxonomy slug.
+ *
+ * @since 3.2.0
+ *
+ * @link https://developer.wordpress.org/reference/functions/register_taxonomy/
+ *
+ * @return string
+ */
+ protected function get_valid_characters() {
+ return '/^[a-z0-9_-]+$/';
+ }
+
+ /**
+ * Retrieve max length of a taxonomy name.
+ *
+ * The length is limited by the SQL field.
+ *
+ * @since 3.2.0
+ *
+ * @return int
+ */
+ protected function get_max_length() {
+ return 32;
+ }
+
+ /**
+ * Retrieve the reserved taxonomy names which can not be used
+ * by themes and plugins.
+ *
+ * @since 3.2.0
+ *
+ * @return array
+ */
+ protected function get_reserved_names() {
+ return WPReservedNamesHelper::get_terms();
+ }
+}
diff --git a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc
index 5c0ed5729..c994383ea 100644
--- a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc
+++ b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc
@@ -49,7 +49,7 @@ EOD
register_post_type( "my-own-post-type-too-long-{$i}" ); // 1x Error, Too long. 1x Warning, post type may or may not get too long with dynamic contents in the id.
register_post_type( 'my/own/post/type/too/long', array() ); // Bad. Invalid chars: "/" and too long.
-register_post_type( 'wp_block', array() ); // Bad. Must only error on reserved keyword, not invalid prefix.
+register_post_type( 'wp_block', array() ); // Bad. Must only error on reserved name, not invalid prefix.
// Test handling of more complex embedded variables and expressions.
register_post_type("testing123-${(foo)}-test");
diff --git a/WordPress/Tests/NamingConventions/ValidTaxonomySlugUnitTest.1.inc b/WordPress/Tests/NamingConventions/ValidTaxonomySlugUnitTest.1.inc
new file mode 100644
index 000000000..f647cfb63
--- /dev/null
+++ b/WordPress/Tests/NamingConventions/ValidTaxonomySlugUnitTest.1.inc
@@ -0,0 +1,70 @@
+get_taxonomy_id() ); // Non string literal. Warning with severity: 3
+
+register_taxonomy( null, array() ); // Non string literal. Warning with severity: 3
+register_taxonomy( 1000, array() ); // Non string literal. Warning with severity: 3
+
+register_taxonomy( 'wp_', array() ); // Bad. Reserved prefix.
+register_taxonomy( 'wp_taxonomy', array() ); // Bad. Reserved prefix.
+
+register_taxonomy( '', array() ); // Bad. Empty taxonomy slug.
+register_taxonomy( /*comment*/, array() ); // Bad. No taxonomy slug.
+
+register_taxonomy( 'taxonomy_1', array() ); // OK.
+
+register_taxonomy( <<warningSeverity = 3;
+ }
+
+ /**
+ * Returns the lines where errors should occur.
+ *
+ * @param string $testFile The name of the file being tested.
+ *
+ * @return array Key is the line number, value is the number of expected errors.
+ */
+ public function getErrorList( $testFile = '' ) {
+ switch ( $testFile ) {
+ case 'ValidTaxonomySlugUnitTest.1.inc':
+ return array(
+ 5 => 1,
+ 6 => 1,
+ 7 => 1,
+ 8 => 1,
+ 20 => 1,
+ 36 => 1,
+ 37 => 1,
+ 39 => 1,
+ 40 => 1,
+ 49 => 1,
+ 50 => 2,
+ 52 => 1,
+ 62 => 1,
+ 64 => 1,
+ );
+
+ case 'ValidTaxonomySlugUnitTest.2.inc':
+ // These tests will only yield reliable results when PHPCS is run on PHP 7.3 or higher.
+ if ( \PHP_VERSION_ID < 70300 ) {
+ return array();
+ }
+
+ return array(
+ 17 => 1,
+ );
+
+ default:
+ return array();
+ }
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @param string $testFile The name of the file being tested.
+ *
+ * @return array Key is the line number, value is the number of expected warnings.
+ */
+ public function getWarningList( $testFile = '' ) {
+ switch ( $testFile ) {
+ case 'ValidTaxonomySlugUnitTest.1.inc':
+ return array(
+ 24 => 1,
+ 27 => 1,
+ 28 => 1,
+ 29 => 1,
+ 30 => 1,
+ 31 => 1,
+ 33 => 1,
+ 34 => 1,
+ 45 => 1,
+ 49 => 1,
+ 55 => 1,
+ 56 => 1,
+ 67 => 1,
+ );
+
+ case 'ValidTaxonomySlugUnitTest.2.inc':
+ // These tests will only yield reliable results when PHPCS is run on PHP 7.3 or higher.
+ if ( \PHP_VERSION_ID < 70300 ) {
+ return array();
+ }
+
+ return array(
+ 7 => 1,
+ );
+
+ default:
+ return array();
+ }
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index a5f12e738..b50909b31 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -25,6 +25,7 @@
./WordPress/AbstractClassRestrictionsSniff.php
./WordPress/AbstractFunctionParameterSniff.php
./WordPress/AbstractFunctionRestrictionsSniff.php
+ ./WordPress/AbstractValidSlugSniff.php
./WordPress/Sniffs/
./WordPress/Helpers/