diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php
index 913a9ae2d430e..85c7b58737a1c 100644
--- a/packages/block-library/src/latest-posts/index.php
+++ b/packages/block-library/src/latest-posts/index.php
@@ -152,6 +152,7 @@ function render_block_core_latest_posts( $attributes ) {
* […] is the default excerpt ending from wp_trim_excerpt() in Core.
*/
if ( str_ends_with( $trimmed_excerpt, ' […]' ) ) {
+ /** This filter is documented in wp-includes/formatting.php */
$excerpt_length = (int) apply_filters( 'excerpt_length', $block_core_latest_posts_excerpt_length );
if ( $excerpt_length <= $block_core_latest_posts_excerpt_length ) {
$trimmed_excerpt = substr( $trimmed_excerpt, 0, -11 );
diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php
index ddb4e14606924..c5ef8e9204b32 100644
--- a/packages/block-library/src/navigation/index.php
+++ b/packages/block-library/src/navigation/index.php
@@ -7,17 +7,23 @@
/**
* Helper functions used to render the navigation block.
+ *
+ * @since 6.5.0
*/
class WP_Navigation_Block_Renderer {
/**
* Used to determine whether or not a navigation has submenus.
+ *
+ * @since 6.5.0
*/
private static $has_submenus = false;
/**
* Used to determine which blocks need an
wrapper.
*
+ * @since 6.5.0
+ *
* @var array
*/
private static $needs_list_item_wrapper = array(
@@ -29,6 +35,8 @@ class WP_Navigation_Block_Renderer {
/**
* Keeps track of all the navigation names that have been seen.
*
+ * @since 6.5.0
+ *
* @var array
*/
private static $seen_menu_names = array();
@@ -36,6 +44,8 @@ class WP_Navigation_Block_Renderer {
/**
* Returns whether or not this is responsive navigation.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return bool Returns whether or not this is responsive navigation.
*/
@@ -51,6 +61,8 @@ private static function is_responsive( $attributes ) {
/**
* Returns whether or not a navigation has a submenu.
*
+ * @since 6.5.0
+ *
* @param WP_Block_List $inner_blocks The list of inner blocks.
* @return bool Returns whether or not a navigation has a submenu and also sets the member variable.
*/
@@ -88,6 +100,8 @@ private static function has_submenus( $inner_blocks ) {
/**
* Determine whether the navigation blocks is interactive.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block_List $inner_blocks The list of inner blocks.
* @return bool Returns whether or not to load the view script.
@@ -101,6 +115,8 @@ private static function is_interactive( $attributes, $inner_blocks ) {
/**
* Returns whether or not a block needs a list item wrapper.
*
+ * @since 6.5.0
+ *
* @param WP_Block $block The block.
* @return bool Returns whether or not a block needs a list item wrapper.
*/
@@ -127,6 +143,8 @@ private static function does_block_need_a_list_item_wrapper( $block ) {
/**
* Returns the markup for a single inner block.
*
+ * @since 6.5.0
+ *
* @param WP_Block $inner_block The inner block.
* @return string Returns the markup for a single inner block.
*/
@@ -144,6 +162,8 @@ private static function get_markup_for_inner_block( $inner_block ) {
/**
* Returns the html for the inner blocks of the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block_List $inner_blocks The list of inner blocks.
* @return string Returns the html for the inner blocks of the navigation block.
@@ -201,6 +221,8 @@ private static function get_inner_blocks_html( $attributes, $inner_blocks ) {
/**
* Gets the inner blocks for the navigation block from the navigation post.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return WP_Block_List Returns the inner blocks for the navigation block.
*/
@@ -236,6 +258,8 @@ private static function get_inner_blocks_from_navigation_post( $attributes ) {
/**
* Gets the inner blocks for the navigation block from the fallback.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return WP_Block_List Returns the inner blocks for the navigation block.
*/
@@ -253,6 +277,8 @@ private static function get_inner_blocks_from_fallback( $attributes ) {
/**
* Gets the inner blocks for the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block $block The parsed block.
* @return WP_Block_List Returns the inner blocks for the navigation block.
@@ -311,6 +337,8 @@ private static function get_inner_blocks( $attributes, $block ) {
/**
* Gets the name of the current navigation, if it has one.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return string Returns the name of the navigation.
*/
@@ -346,6 +374,8 @@ private static function get_navigation_name( $attributes ) {
/**
* Returns the layout class for the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return string Returns the layout class for the navigation block.
*/
@@ -377,6 +407,8 @@ private static function get_layout_class( $attributes ) {
/**
* Return classes for the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return string Returns the classes for the navigation block.
*/
@@ -404,6 +436,8 @@ private static function get_classes( $attributes ) {
/**
* Get styles for the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return string Returns the styles for the navigation block.
*/
@@ -417,6 +451,8 @@ private static function get_styles( $attributes ) {
/**
* Get the responsive container markup
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block_List $inner_blocks The list of inner blocks.
* @param string $inner_blocks_html The markup for the inner blocks.
@@ -515,6 +551,8 @@ private static function get_responsive_container_markup( $attributes, $inner_blo
/**
* Get the wrapper attributes
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block_List $inner_blocks A list of inner blocks.
* @return string Returns the navigation block markup.
@@ -544,6 +582,8 @@ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks )
/**
* Gets the nav element directives.
*
+ * @since 6.5.0
+ *
* @param bool $is_interactive Whether the block is interactive.
* @return string the directives for the navigation element.
*/
@@ -574,6 +614,8 @@ private static function get_nav_element_directives( $is_interactive ) {
/**
* Handle view script module loading.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block $block The parsed block.
* @param WP_Block_List $inner_blocks The list of inner blocks.
@@ -598,6 +640,8 @@ private static function handle_view_script_module_loading( $attributes, $block,
/**
* Returns the markup for the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param WP_Block_List $inner_blocks The list of inner blocks.
* @return string Returns the navigation wrapper markup.
@@ -613,6 +657,8 @@ private static function get_wrapper_markup( $attributes, $inner_blocks ) {
/**
* Returns a unique name for the navigation.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @return string Returns a unique name for the navigation.
*/
@@ -632,6 +678,8 @@ private static function get_unique_navigation_name( $attributes ) {
/**
* Renders the navigation block.
*
+ * @since 6.5.0
+ *
* @param array $attributes The block attributes.
* @param string $content The saved content.
* @param WP_Block $block The parsed block.
@@ -1604,7 +1652,9 @@ function block_core_navigation_insert_hooked_blocks_into_rest_response( $respons
// Remove mock Navigation block wrapper.
$content = block_core_navigation_remove_serialized_parent_block( $content );
- $response->data['content']['raw'] = $content;
+ $response->data['content']['raw'] = $content;
+
+ /** This filter is documented in wp-includes/post-template.php */
$response->data['content']['rendered'] = apply_filters( 'the_content', $content );
return $response;
diff --git a/packages/widgets/src/blocks/legacy-widget/index.php b/packages/widgets/src/blocks/legacy-widget/index.php
index 24ea288be3875..ee13ae9a1c612 100644
--- a/packages/widgets/src/blocks/legacy-widget/index.php
+++ b/packages/widgets/src/blocks/legacy-widget/index.php
@@ -8,6 +8,8 @@
/**
* Renders the 'core/legacy-widget' block.
*
+ * @since 5.8.0
+ *
* @global int $wp_widget_factory.
*
* @param array $attributes The block attributes.
@@ -56,6 +58,8 @@ function render_block_core_legacy_widget( $attributes ) {
/**
* Registers the 'core/legacy-widget' block.
+ *
+ * @since 5.8.0
*/
function register_block_core_legacy_widget() {
register_block_type_from_metadata(
@@ -72,6 +76,8 @@ function register_block_core_legacy_widget() {
* Intercepts any request with legacy-widget-preview in the query param and, if
* set, renders a page containing a preview of the requested Legacy Widget
* block.
+ *
+ * @since 5.8.0
*/
function handle_legacy_widget_preview_iframe() {
if ( empty( $_GET['legacy-widget-preview'] ) ) {
diff --git a/packages/widgets/src/blocks/widget-group/index.php b/packages/widgets/src/blocks/widget-group/index.php
index 284ca66a85ce7..e8769612a2f17 100644
--- a/packages/widgets/src/blocks/widget-group/index.php
+++ b/packages/widgets/src/blocks/widget-group/index.php
@@ -8,6 +8,8 @@
/**
* Renders the 'core/widget-group' block.
*
+ * @since 5.9.0
+ *
* @global array $wp_registered_sidebars
* @global int|string $_sidebar_being_rendered
*
@@ -45,6 +47,8 @@ function render_block_core_widget_group( $attributes, $content, $block ) {
/**
* Registers the 'core/widget-group' block.
+ *
+ * @since 5.9.0
*/
function register_block_core_widget_group() {
register_block_type_from_metadata(
@@ -62,6 +66,8 @@ function register_block_core_widget_group() {
* it. This lets us get to the current sidebar in
* render_block_core_widget_group().
*
+ * @since 5.9.0
+ *
* @global int|string $_sidebar_being_rendered
*
* @param int|string $index Index, name, or ID of the dynamic sidebar.
@@ -76,6 +82,8 @@ function note_sidebar_being_rendered( $index ) {
* Clear whatever we set in note_sidebar_being_rendered() after WordPress
* finishes rendering a sidebar.
*
+ * @since 5.9.0
+ *
* @global int|string $_sidebar_being_rendered
*/
function discard_sidebar_being_rendered() {
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 400b4d68ece44..45d742a498c65 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -172,8 +172,11 @@
-
+
/packages/block-library/src/.+/*\.php$
+ /packages/block-serialization-default-parser/.+/*\.php$
+ /packages/widgets/src/blocks/legacy-widget/index\.php$
+ /packages/widgets/src/blocks/widget-group/index\.php$
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/FunctionCommentSinceTagSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/FunctionCommentSinceTagSniff.php
deleted file mode 100644
index 54b8b367560fc..0000000000000
--- a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/FunctionCommentSinceTagSniff.php
+++ /dev/null
@@ -1,162 +0,0 @@
-
- */
- public function register() {
- return array( T_FUNCTION );
- }
-
- /**
- * Processes the tokens that this sniff is interested in.
- *
- * @param File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the current token in the stack passed in $tokens.
- */
- public function process( File $phpcsFile, $stackPtr ) {
- if ( static::is_experimental_block( $phpcsFile ) ) {
- // The "@since" tag is not required for experimental blocks since they are not yet included in WordPress Core.
- return;
- }
-
- $tokens = $phpcsFile->getTokens();
- $function_name = $phpcsFile->getDeclarationName( $stackPtr );
-
- $wrapping_tokens_to_check = array(
- T_CLASS,
- T_INTERFACE,
- T_TRAIT,
- );
-
- foreach ( $wrapping_tokens_to_check as $wrapping_token_to_check ) {
- if ( false !== $phpcsFile->getCondition( $stackPtr, $wrapping_token_to_check, false ) ) {
- // This sniff only processes functions, not class methods.
- return;
- }
- }
-
- $missing_since_tag_error_message = sprintf( '@since tag is missing for the "%s()" function.', $function_name );
-
- // All these tokens could be present before the docblock.
- $tokens_before_the_docblock = array(
- T_PUBLIC,
- T_PROTECTED,
- T_PRIVATE,
- T_STATIC,
- T_FINAL,
- T_ABSTRACT,
- T_WHITESPACE,
- );
-
- $doc_block_end_token = $phpcsFile->findPrevious( $tokens_before_the_docblock, ( $stackPtr - 1 ), null, true, null, true );
- if ( ( false === $doc_block_end_token ) || ( T_DOC_COMMENT_CLOSE_TAG !== $tokens[ $doc_block_end_token ]['code'] ) ) {
- $phpcsFile->addError( $missing_since_tag_error_message, $stackPtr, 'MissingSinceTag' );
- return;
- }
-
- // The sniff intentionally doesn't check if the docblock has a valid open tag.
- // Its only job is to make sure that the @since tag is present and has a valid version value.
- $doc_block_start_token = $phpcsFile->findPrevious( Tokens::$commentTokens, ( $doc_block_end_token - 1 ), null, true, null, true );
- if ( false === $doc_block_start_token ) {
- $phpcsFile->addError( $missing_since_tag_error_message, $stackPtr, 'MissingSinceTag' );
- return;
- }
-
- // This is the first non-docblock token, so the next token should be used.
- ++$doc_block_start_token;
-
- $since_tag_token = $phpcsFile->findNext( T_DOC_COMMENT_TAG, $doc_block_start_token, $doc_block_end_token, false, '@since', true );
- if ( false === $since_tag_token ) {
- $phpcsFile->addError( $missing_since_tag_error_message, $stackPtr, 'MissingSinceTag' );
- return;
- }
-
- $version_token = $phpcsFile->findNext( T_DOC_COMMENT_WHITESPACE, $since_tag_token + 1, null, true, null, true );
- if ( ( false === $version_token ) || ( T_DOC_COMMENT_STRING !== $tokens[ $version_token ]['code'] ) ) {
- $phpcsFile->addError( $missing_since_tag_error_message, $since_tag_token, 'MissingSinceTag' );
- return;
- }
-
- $version_value = $tokens[ $version_token ]['content'];
-
- if ( version_compare( $version_value, '0.0.1', '>=' ) ) {
- // Validate the version value.
- return;
- }
-
- $phpcsFile->addError(
- 'Invalid @since version value for the "%s()" function: "%s". Version value must be greater than or equal to 0.0.1.',
- $version_token,
- 'InvalidSinceTagVersionValue',
- array(
- $function_name,
- $version_value,
- )
- );
- }
-
- /**
- * Checks if the current block is experimental.
- *
- * @param File $phpcsFile The file being scanned.
- * @return bool Returns true if the current block is experimental.
- */
- private static function is_experimental_block( File $phpcsFile ) {
- $block_json_filepath = dirname( $phpcsFile->getFilename() ) . DIRECTORY_SEPARATOR . 'block.json';
-
- if ( isset( static::$cache[ $block_json_filepath ] ) ) {
- return static::$cache[ $block_json_filepath ];
- }
-
- if ( ! is_file( $block_json_filepath ) || ! is_readable( $block_json_filepath ) ) {
- static::$cache[ $block_json_filepath ] = false;
- return static::$cache[ $block_json_filepath ];
- }
-
- // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- this Composer package doesn't depend on WordPress.
- $block_metadata = file_get_contents( $block_json_filepath );
- if ( false === $block_metadata ) {
- static::$cache[ $block_json_filepath ] = false;
- return static::$cache[ $block_json_filepath ];
- }
-
- $block_metadata = json_decode( $block_metadata, true );
- if ( ! is_array( $block_metadata ) ) {
- static::$cache[ $block_json_filepath ] = false;
- return static::$cache[ $block_json_filepath ];
- }
-
- $experimental_flag = '__experimental';
- static::$cache[ $block_json_filepath ] = array_key_exists( $experimental_flag, $block_metadata ) && ( false !== $block_metadata[ $experimental_flag ] );
- return static::$cache[ $block_json_filepath ];
- }
-}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php
new file mode 100644
index 0000000000000..f216f4f681f0e
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php
@@ -0,0 +1,617 @@
+ array(
+ 'name' => 'class',
+ ),
+ T_INTERFACE => array(
+ 'name' => 'interface',
+ ),
+ T_TRAIT => array(
+ 'name' => 'trait',
+ ),
+ T_ENUM => array(
+ 'name' => 'enum',
+ ),
+ );
+
+ /**
+ * This property is used to store results returned
+ * by the static::is_experimental_block() method.
+ *
+ * @var array
+ */
+ protected static $cache = array();
+
+ /**
+ * Returns an array of tokens this test wants to listen for.
+ *
+ * @return array
+ */
+ public function register() {
+ return array_merge(
+ array(
+ T_FUNCTION,
+ T_VARIABLE,
+ T_STRING,
+ ),
+ array_keys( static::$oo_tokens )
+ );
+ }
+
+ /**
+ * Processes the tokens that this sniff is interested in.
+ *
+ * @param File $phpcsFile The file being scanned.
+ * @param int $stackPtr The position of the current token in the stack passed in $tokens.
+ */
+ public function process( File $phpcsFile, $stackPtr ) {
+ if ( static::is_experimental_block( $phpcsFile ) ) {
+ // The "@since" tag is not required for experimental blocks since they are not yet included in WordPress Core.
+ return;
+ }
+
+ $tokens = $phpcsFile->getTokens();
+ $token = $tokens[ $stackPtr ];
+
+ if ( 'T_FUNCTION' === $token['type'] ) {
+ $this->process_function_token( $phpcsFile, $stackPtr );
+ return;
+ }
+
+ if ( isset( static::$oo_tokens[ $token['code'] ] ) ) {
+ $this->process_oo_token( $phpcsFile, $stackPtr );
+ return;
+ }
+
+ if ( 'T_STRING' === $token['type'] && static::is_function_call( $phpcsFile, $stackPtr ) ) {
+ $this->process_hook( $phpcsFile, $stackPtr );
+ return;
+ }
+
+ if ( 'T_VARIABLE' === $token['type'] && Scopes::isOOProperty( $phpcsFile, $stackPtr ) ) {
+ $this->process_property_token( $phpcsFile, $stackPtr );
+ }
+ }
+
+ /**
+ * Processes a token representing a function call that invokes a WordPress hook,
+ * checking for a missing `@since` tag in its docblock.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position of the hook token in the stack.
+ */
+ protected function process_hook( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+
+ // The content of the current token.
+ $hook_function = $tokens[ $stack_pointer ]['content'];
+
+ $hook_invocation_functions = array(
+ 'do_action',
+ 'do_action_ref_array',
+ 'do_action_deprecated',
+ 'apply_filters',
+ 'apply_filters_ref_array',
+ 'apply_filters_deprecated',
+ );
+
+ // Check if the current token content is one of the filter functions.
+ if ( ! in_array( $hook_function, $hook_invocation_functions, true ) ) {
+ // Not a hook.
+ return;
+ }
+
+ $error_message_data = array( $hook_function );
+
+ $violation_codes = static::get_violation_codes( 'Hook' );
+
+ $docblock = static::find_hook_docblock( $phpcs_file, $stack_pointer );
+
+ $version_tags = static::parse_since_tags( $phpcs_file, $docblock );
+ if ( empty( $version_tags ) ) {
+ if ( false !== $docblock ) {
+ $docblock_content = GetTokensAsString::compact( $phpcs_file, $docblock['start_token'], $docblock['end_token'], false );
+ if ( false !== stripos( $docblock_content, 'This filter is documented in ' ) ) {
+ $hook_documented_elsewhere = true;
+ }
+ }
+
+ if ( empty( $hook_documented_elsewhere ) ) {
+ $phpcs_file->addError(
+ 'Missing @since tag for the "%s()" hook function.',
+ $stack_pointer,
+ $violation_codes['missing_since_tag'],
+ $error_message_data
+ );
+ }
+
+ return;
+ }
+
+ foreach ( $version_tags as $since_tag_token => $version_value_token ) {
+ if ( null === $version_value_token ) {
+ $phpcs_file->addError(
+ 'Missing @since tag version value for the "%s()" hook function.',
+ $since_tag_token,
+ $violation_codes['missing_version_value'],
+ $error_message_data
+ );
+ continue;
+ }
+
+ $version_value = $tokens[ $version_value_token ]['content'];
+
+ if ( static::validate_version( $version_value ) ) {
+ continue;
+ }
+
+ $phpcs_file->addError(
+ 'Invalid @since version value for the "%s()" hook function: "%s". Version value must be greater than or equal to 0.0.1.',
+ $version_value_token,
+ $violation_codes['invalid_version_value'],
+ array_merge( $error_message_data, array( $version_value ) )
+ );
+ }
+ }
+
+ /**
+ * Processes a token representing an object-oriented programming structure
+ * like a class, interface, trait, or enum to check for a missing `@since` tag in its docblock.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position of the OO token in the stack.
+ */
+ protected function process_oo_token( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+ $token_type = static::$oo_tokens[ $tokens[ $stack_pointer ]['code'] ]['name'];
+
+ $token_name = ObjectDeclarations::getName( $phpcs_file, $stack_pointer );
+ $error_message_data = array(
+ $token_name,
+ $token_type,
+ );
+
+ $violation_codes = static::get_violation_codes( ucfirst( $token_type ) );
+
+ $docblock = static::find_docblock( $phpcs_file, $stack_pointer );
+
+ $version_tags = static::parse_since_tags( $phpcs_file, $docblock );
+ if ( empty( $version_tags ) ) {
+ $phpcs_file->addError(
+ 'Missing @since tag for the "%s" %s.',
+ $stack_pointer,
+ $violation_codes['missing_since_tag'],
+ $error_message_data
+ );
+ return;
+ }
+
+ foreach ( $version_tags as $since_tag_token => $version_value_token ) {
+ if ( null === $version_value_token ) {
+ $phpcs_file->addError(
+ 'Missing @since tag version value for the "%s" %s.',
+ $since_tag_token,
+ $violation_codes['missing_version_value'],
+ $error_message_data
+ );
+ continue;
+ }
+
+ $version_value = $tokens[ $version_value_token ]['content'];
+
+ if ( static::validate_version( $version_value ) ) {
+ continue;
+ }
+
+ $phpcs_file->addError(
+ 'Invalid @since version value for the "%s" %s: "%s". Version value must be greater than or equal to 0.0.1.',
+ $version_value_token,
+ $violation_codes['invalid_version_value'],
+ array_merge( $error_message_data, array( $version_value ) )
+ );
+ }
+ }
+
+ /**
+ * Processes a token representing an object-oriented property to check for a missing @since tag in its docblock.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position of the object-oriented property token in the stack.
+ */
+ protected function process_property_token( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+
+ $property_name = $tokens[ $stack_pointer ]['content'];
+ $oo_token = Scopes::validDirectScope( $phpcs_file, $stack_pointer, Collections::ooPropertyScopes() );
+ $class_name = ObjectDeclarations::getName( $phpcs_file, $oo_token );
+
+ $visibility = Variables::getMemberProperties( $phpcs_file, $stack_pointer )['scope'];
+ if ( $this->check_below_minimum_visibility( $visibility ) ) {
+ return;
+ }
+
+ $violation_codes = static::get_violation_codes( 'Property' );
+
+ $error_message_data = array(
+ $class_name,
+ $property_name,
+ );
+
+ $docblock = static::find_docblock( $phpcs_file, $stack_pointer );
+
+ $version_tags = static::parse_since_tags( $phpcs_file, $docblock );
+ if ( empty( $version_tags ) ) {
+ $phpcs_file->addError(
+ 'Missing @since tag for the "%s::%s" property.',
+ $stack_pointer,
+ $violation_codes['missing_since_tag'],
+ $error_message_data
+ );
+ return;
+ }
+
+ foreach ( $version_tags as $since_tag_token => $version_value_token ) {
+ if ( null === $version_value_token ) {
+ $phpcs_file->addError(
+ 'Missing @since tag version value for the "%s::%s" property.',
+ $since_tag_token,
+ $violation_codes['missing_version_value'],
+ $error_message_data
+ );
+ continue;
+ }
+
+ $version_value = $tokens[ $version_value_token ]['content'];
+
+ if ( static::validate_version( $version_value ) ) {
+ continue;
+ }
+
+ $phpcs_file->addError(
+ 'Invalid @since version value for the "%s::%s" property: "%s". Version value must be greater than or equal to 0.0.1.',
+ $version_value_token,
+ $violation_codes['invalid_version_value'],
+ array_merge( $error_message_data, array( $version_value ) )
+ );
+ }
+ }
+
+ /**
+ * Processes a T_FUNCTION token to check for a missing @since tag in its docblock.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position of the T_FUNCTION token in the stack.
+ */
+ protected function process_function_token( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+
+ $oo_token = Scopes::validDirectScope( $phpcs_file, $stack_pointer, Tokens::$ooScopeTokens );
+ $function_name = ObjectDeclarations::getName( $phpcs_file, $stack_pointer );
+
+ $token_type = 'function';
+ if ( Scopes::isOOMethod( $phpcs_file, $stack_pointer ) ) {
+ $visibility = FunctionDeclarations::getProperties( $phpcs_file, $stack_pointer )['scope'];
+ if ( $this->check_below_minimum_visibility( $visibility ) ) {
+ return;
+ }
+
+ $function_name = ObjectDeclarations::getName( $phpcs_file, $oo_token ) . '::' . $function_name;
+ $token_type = 'method';
+ }
+
+ $violation_codes = static::get_violation_codes( ucfirst( $token_type ) );
+
+ $error_message_data = array(
+ $function_name,
+ $token_type,
+ );
+
+ $docblock = static::find_docblock( $phpcs_file, $stack_pointer );
+
+ $version_tags = static::parse_since_tags( $phpcs_file, $docblock );
+ if ( empty( $version_tags ) ) {
+ $phpcs_file->addError(
+ 'Missing @since tag for the "%s()" %s.',
+ $stack_pointer,
+ $violation_codes['missing_since_tag'],
+ $error_message_data
+ );
+ return;
+ }
+
+ foreach ( $version_tags as $since_tag_token => $version_value_token ) {
+ if ( null === $version_value_token ) {
+ $phpcs_file->addError(
+ 'Missing @since tag version value for the "%s()" %s.',
+ $since_tag_token,
+ $violation_codes['missing_version_value'],
+ $error_message_data
+ );
+ continue;
+ }
+
+ $version_value = $tokens[ $version_value_token ]['content'];
+
+ if ( static::validate_version( $version_value ) ) {
+ continue;
+ }
+
+ $phpcs_file->addError(
+ 'Invalid @since version value for the "%s()" %s: "%s". Version value must be greater than or equal to 0.0.1.',
+ $version_value_token,
+ $violation_codes['invalid_version_value'],
+ array_merge( $error_message_data, array( $version_value ) )
+ );
+ }
+ }
+
+ /**
+ * Validates the version value.
+ *
+ * @param string $version The version value being checked.
+ * @return bool True if the version value is valid.
+ */
+ protected static function validate_version( $version ) {
+ $matches = array();
+ if ( 1 === preg_match( '/^MU \((?.+)\)/', $version, $matches ) ) {
+ $version = $matches['version'];
+ }
+
+ return version_compare( $version, '0.0.1', '>=' );
+ }
+
+
+ /**
+ * Returns violation codes for a specific token type.
+ *
+ * @param string $token_type The type of token (e.g., Function, Property) to retrieve violation codes for.
+ * @return array An array containing violation codes for missing since tag, missing version value, and invalid version value.
+ */
+ protected static function get_violation_codes( $token_type ) {
+ return array(
+ 'missing_since_tag' => 'Missing' . $token_type . 'SinceTag',
+ 'missing_version_value' => 'Missing' . $token_type . 'VersionValue',
+ 'invalid_version_value' => 'Invalid' . $token_type . 'VersionValue',
+ );
+ }
+
+ /**
+ * Checks if the provided visibility level is below the set minimum visibility level.
+ *
+ * @param string $visibility The visibility level to check.
+ * @return bool Returns true if the provided visibility level is below the minimum visibility level, false otherwise.
+ */
+ protected function check_below_minimum_visibility( $visibility ) {
+ if ( 'public' === $this->minimumVisibility && in_array( $visibility, array( 'protected', 'private' ), true ) ) {
+ return true;
+ }
+
+ if ( 'protected' === $this->minimumVisibility && 'private' === $visibility ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds the docblock associated with a hook, starting from a specified position in the token stack.
+ * Since a line containing a hook can include any type of tokens, this method backtracks through the tokens
+ * to locate the first token on the current line. This token is then used as the starting point for searching the docblock.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position to start looking for the docblock.
+ * @return array|false An associative array containing the start and end tokens of the docblock, or false if not found.
+ */
+ protected static function find_hook_docblock( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+ $current_line = $tokens[ $stack_pointer ]['line'];
+
+ for ( $i = $stack_pointer; $i >= 0; $i-- ) {
+ if ( $tokens[ $i ]['line'] < $current_line ) {
+ // The previous token is on the previous line, so the current token is the first on the line.
+ return static::find_docblock( $phpcs_file, $i + 1 );
+ }
+ }
+
+ return static::find_docblock( $phpcs_file, 0 );
+ }
+
+ /**
+ * Determines if a T_STRING token represents a function call.
+ * The implementation was copied from PHPCompatibility\Sniffs\Extensions\RemovedExtensionsSniff::process().
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position of the T_STRING token in question.
+ * @return bool True if the token represents a function call, false otherwise.
+ */
+ protected static function is_function_call( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+
+ // Find the next non-empty token.
+ $open_bracket = $phpcs_file->findNext( Tokens::$emptyTokens, ( $stack_pointer + 1 ), null, true );
+
+ if ( T_OPEN_PARENTHESIS !== $tokens[ $open_bracket ]['code'] ) {
+ // Not a function call.
+ return false;
+ }
+
+ if ( false === isset( $tokens[ $open_bracket ]['parenthesis_closer'] ) ) {
+ // Not a function call.
+ return false;
+ }
+
+ // Find the previous non-empty token.
+ $search = Tokens::$emptyTokens;
+ $search[] = T_BITWISE_AND;
+ $previous = $phpcs_file->findPrevious( $search, ( $stack_pointer - 1 ), null, true );
+
+ $previous_tokens_to_ignore = array(
+ T_FUNCTION, // Function declaration.
+ T_NEW, // Creating an object.
+ T_OBJECT_OPERATOR, // Calling an object.
+ );
+
+ return ! in_array( $tokens[ $previous ]['code'], $previous_tokens_to_ignore, true );
+ }
+
+ /**
+ * Finds the docblock preceding a specified position (stack pointer) in a given PHP file.
+ * The implementation was copied from PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff::process().
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_pointer The position (stack pointer) in the token stack from which to start searching backwards.
+ * @return array|false An associative array containing the start and end tokens of the docblock, or false if not found.
+ */
+ protected static function find_docblock( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
+ $ignore = Tokens::$methodPrefixes;
+ $ignore[ T_WHITESPACE ] = T_WHITESPACE;
+
+ for ( $comment_end = ( $stack_pointer - 1 ); $comment_end >= 0; $comment_end-- ) {
+ if ( isset( $ignore[ $tokens[ $comment_end ]['code'] ] ) ) {
+ continue;
+ }
+
+ if ( T_ATTRIBUTE_END === $tokens[ $comment_end ]['code']
+ && isset( $tokens[ $comment_end ]['attribute_opener'] )
+ ) {
+ $comment_end = $tokens[ $comment_end ]['attribute_opener'];
+ continue;
+ }
+
+ break;
+ }
+
+ if ( $tokens[ $comment_end ]['code'] === T_COMMENT ) {
+ // Inline comments might just be closing comments for
+ // control structures or functions instead of function comments
+ // using the wrong comment type. If there is other code on the line,
+ // assume they relate to that code.
+ $previous = $phpcs_file->findPrevious( $ignore, ( $comment_end - 1 ), null, true );
+ if ( false !== $previous && $tokens[ $previous ]['line'] === $tokens[ $comment_end ]['line'] ) {
+ $comment_end = $previous;
+ }
+ }
+
+ if ( T_DOC_COMMENT_CLOSE_TAG !== $tokens[ $comment_end ]['code'] ) {
+ // Only "/**" style comments are supported.
+ return false;
+ }
+
+ return array(
+ 'start_token' => $tokens[ $comment_end ]['comment_opener'],
+ 'end_token' => $comment_end,
+ );
+ }
+
+ /**
+ * Searches for @since values within a docblock.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param array|false $docblock An associative array containing the start and end tokens of the docblock, or false if not exists.
+ * @return array Returns an array of "@since" tokens and their corresponding value tokens.
+ */
+ protected static function parse_since_tags( File $phpcs_file, $docblock ) {
+ $version_tags = array();
+
+ if ( false === $docblock ) {
+ return $version_tags;
+ }
+
+ $tokens = $phpcs_file->getTokens();
+
+ for ( $i = $docblock['start_token'] + 1; $i < $docblock['end_token']; $i++ ) {
+ if ( ! ( T_DOC_COMMENT_TAG === $tokens[ $i ]['code'] && '@since' === $tokens[ $i ]['content'] ) ) {
+ continue;
+ }
+
+ $version_token = $phpcs_file->findNext( T_DOC_COMMENT_WHITESPACE, $i + 1, $docblock['end_token'], true, null, true );
+ if ( ( false === $version_token ) || ( T_DOC_COMMENT_STRING !== $tokens[ $version_token ]['code'] ) ) {
+ $version_tags[ $i ] = null;
+ continue;
+ }
+
+ $version_tags[ $i ] = $version_token;
+ }
+
+ return $version_tags;
+ }
+
+ /**
+ * Checks if the current block is experimental.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @return bool Returns true if the current block is experimental.
+ */
+ protected static function is_experimental_block( File $phpcs_file ) {
+ $block_json_filepath = dirname( $phpcs_file->getFilename() ) . DIRECTORY_SEPARATOR . 'block.json';
+
+ if ( isset( static::$cache[ $block_json_filepath ] ) ) {
+ return static::$cache[ $block_json_filepath ];
+ }
+
+ if ( ! is_file( $block_json_filepath ) || ! is_readable( $block_json_filepath ) ) {
+ static::$cache[ $block_json_filepath ] = false;
+ return static::$cache[ $block_json_filepath ];
+ }
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- this Composer package doesn't depend on WordPress.
+ $block_metadata = file_get_contents( $block_json_filepath );
+ if ( false === $block_metadata ) {
+ static::$cache[ $block_json_filepath ] = false;
+ return static::$cache[ $block_json_filepath ];
+ }
+
+ $block_metadata = json_decode( $block_metadata, true );
+ if ( ! is_array( $block_metadata ) ) {
+ static::$cache[ $block_json_filepath ] = false;
+ return static::$cache[ $block_json_filepath ];
+ }
+
+ $experimental_flag = '__experimental';
+ static::$cache[ $block_json_filepath ] = array_key_exists( $experimental_flag, $block_metadata ) && ( false !== $block_metadata[ $experimental_flag ] );
+ return static::$cache[ $block_json_filepath ];
+ }
+}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php
new file mode 100644
index 0000000000000..08838ce412fc3
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php
@@ -0,0 +1,85 @@
+get_sniff_fqcn();
+ if ( ! isset( $current_ruleset->sniffs[ $sniff_fqcn ] ) ) {
+ throw new \RuntimeException( $error_message );
+ }
+
+ $sniff = $current_ruleset->sniffs[ $sniff_fqcn ];
+ $this->set_sniff_parameters( $sniff );
+ }
+}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php
index 5073cea9ecf06..8026e88f1d945 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php
@@ -10,22 +10,14 @@
namespace GutenbergCS\Gutenberg\Tests\CodeAnalysis;
use GutenbergCS\Gutenberg\Sniffs\CodeAnalysis\ForbiddenFunctionsAndClassesSniff;
-use PHP_CodeSniffer\Config;
-use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
-use PHP_CodeSniffer\Ruleset;
+use GutenbergCS\Gutenberg\Tests\AbstractSniffUnitTest;
+use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Unit test class for the ForbiddenFunctionsAndClassesSniff sniff.
*/
final class ForbiddenFunctionsAndClassesUnitTest extends AbstractSniffUnitTest {
- /**
- * Holds the original Ruleset instance.
- *
- * @var Ruleset
- */
- private static $original_ruleset;
-
/**
* Returns the lines where errors should occur.
*
@@ -73,50 +65,22 @@ public function getWarningList() {
}
/**
+ * Returns the fully qualified class name (FQCN) of the sniff.
*
- * This method resets the 'Gutenberg' ruleset in the $GLOBALS['PHP_CODESNIFFER_RULESETS']
- * to its original state.
+ * @return string The fully qualified class name of the sniff.
*/
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] = self::$original_ruleset;
- self::$original_ruleset = null;
+ protected function get_sniff_fqcn() {
+ return ForbiddenFunctionsAndClassesSniff::class;
}
-
/**
- * Prepares the environment before executing tests. Specifically, sets prefixes for the
- * ForbiddenFunctionsAndClassesSniff sniff.This is needed since AbstractSniffUnitTest class
- * doesn't apply sniff properties from the Gutenberg/ruleset.xml file.
+ * Sets the parameters for the sniff.
*
- * @param string $filename The name of the file being tested.
- * @param Config $config The config data for the run.
+ * @throws RuntimeException If unable to set the ruleset parameters required for the test.
*
- * @return void
+ * @param Sniff $sniff The sniff being tested.
*/
- public function setCliValues( $filename, $config ) {
- parent::setCliValues( $filename, $config );
-
- if ( ! isset( $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] )
- || ( ! $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] instanceof Ruleset )
- ) {
- throw new \RuntimeException( 'Cannot set ruleset parameters required for this test.' );
- }
-
- // Backup the original Ruleset instance.
- self::$original_ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'];
-
- $current_ruleset = clone self::$original_ruleset;
- $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] = $current_ruleset;
-
- if ( ! isset( $current_ruleset->sniffs[ ForbiddenFunctionsAndClassesSniff::class ] )
- || ( ! $current_ruleset->sniffs[ ForbiddenFunctionsAndClassesSniff::class ] instanceof ForbiddenFunctionsAndClassesSniff )
- ) {
- throw new \RuntimeException( 'Cannot set ruleset parameters required for this test.' );
- }
-
- $sniff = $current_ruleset->sniffs[ ForbiddenFunctionsAndClassesSniff::class ];
+ public function set_sniff_parameters( Sniff $sniff ) {
$sniff->forbidden_functions = array(
'[Gg]utenberg.*',
);
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php
index fdd5c07c8cb59..652f6b735378c 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php
@@ -10,22 +10,14 @@
namespace GutenbergCS\Gutenberg\Tests\CodeAnalysis;
use GutenbergCS\Gutenberg\Sniffs\CodeAnalysis\GuardedFunctionAndClassNamesSniff;
-use PHP_CodeSniffer\Config;
-use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
-use PHP_CodeSniffer\Ruleset;
+use GutenbergCS\Gutenberg\Tests\AbstractSniffUnitTest;
+use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Unit test class for the GuardedFunctionAndClassNames sniff.
*/
final class GuardedFunctionAndClassNamesUnitTest extends AbstractSniffUnitTest {
- /**
- * Holds the original Ruleset instance.
- *
- * @var Ruleset
- */
- private static $original_ruleset;
-
/**
* Returns the lines where errors should occur.
*
@@ -50,50 +42,22 @@ public function getWarningList() {
}
/**
+ * Returns the fully qualified class name (FQCN) of the sniff.
*
- * This method resets the 'Gutenberg' ruleset in the $GLOBALS['PHP_CODESNIFFER_RULESETS']
- * to its original state.
+ * @return string The fully qualified class name of the sniff.
*/
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] = self::$original_ruleset;
- self::$original_ruleset = null;
+ protected function get_sniff_fqcn() {
+ return GuardedFunctionAndClassNamesSniff::class;
}
-
/**
- * Prepares the environment before executing tests. Specifically, sets prefixes for the
- * GuardedFunctionAndClassNames sniff.This is needed since AbstractSniffUnitTest class
- * doesn't apply sniff properties from the Gutenberg/ruleset.xml file.
+ * Sets the parameters for the sniff.
*
- * @param string $filename The name of the file being tested.
- * @param Config $config The config data for the run.
+ * @throws RuntimeException If unable to set the ruleset parameters required for the test.
*
- * @return void
+ * @param Sniff $sniff The sniff being tested.
*/
- public function setCliValues( $filename, $config ) {
- parent::setCliValues( $filename, $config );
-
- if ( ! isset( $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] )
- || ( ! $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] instanceof Ruleset )
- ) {
- throw new \RuntimeException( 'Cannot set ruleset parameters required for this test.' );
- }
-
- // Backup the original Ruleset instance.
- self::$original_ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'];
-
- $current_ruleset = clone self::$original_ruleset;
- $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] = $current_ruleset;
-
- if ( ! isset( $current_ruleset->sniffs[ GuardedFunctionAndClassNamesSniff::class ] )
- || ( ! $current_ruleset->sniffs[ GuardedFunctionAndClassNamesSniff::class ] instanceof GuardedFunctionAndClassNamesSniff )
- ) {
- throw new \RuntimeException( 'Cannot set ruleset parameters required for this test.' );
- }
-
- $sniff = $current_ruleset->sniffs[ GuardedFunctionAndClassNamesSniff::class ];
+ public function set_sniff_parameters( Sniff $sniff ) {
$sniff->functionsWhiteList = array(
'/^_?gutenberg.+/',
);
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/FunctionCommentSinceTagUnitTest.inc b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/FunctionCommentSinceTagUnitTest.inc
deleted file mode 100644
index 43d11694bb25e..0000000000000
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/FunctionCommentSinceTagUnitTest.inc
+++ /dev/null
@@ -1,39 +0,0 @@
- =>
- */
- public function getErrorList() {
- // The sniff only supports PHP functions for now; it ignores class, trait, and interface methods.
- return array(
- 9 => 1,
- 19 => 1,
- 24 => 1,
- );
- }
-
- /**
- * Returns the lines where warnings should occur.
- *
- * @return array =>
- */
- public function getWarningList() {
- return array();
- }
-}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc
new file mode 100644
index 0000000000000..aa8593b2e2757
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc
@@ -0,0 +1,629 @@
+do_action();
+$foo = new do_action_ref_array();
+$foo->do_action_ref_array();
+$foo = new do_action_deprecated();
+$foo->do_action_deprecated();
+$foo = new apply_filters();
+$foo->apply_filters();
+$foo = new apply_filters_ref_array();
+$foo->apply_filters_ref_array();
+$foo = new apply_filters_deprecated();
+$foo->apply_filters_deprecated();
+$foo = new non_hook_action();
+$foo->non_hook_action();
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php
new file mode 100644
index 0000000000000..bc7ca28c263ff
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php
@@ -0,0 +1,189 @@
+ =>
+ */
+ public function getErrorList() {
+ return array(
+ 2 => 1,
+ 3 => 1,
+ 4 => 1,
+ 5 => 1,
+ 6 => 1,
+ 9 => 1,
+ 15 => 1,
+ 26 => 1,
+ 33 => 1,
+ 35 => 1,
+ 36 => 1,
+ 42 => 1,
+ 49 => 1,
+ 62 => 1,
+ 67 => 1,
+ 69 => 1,
+ 70 => 1,
+ 79 => 1,
+ 82 => 1,
+ 88 => 1,
+ 97 => 1,
+ 99 => 1,
+ 105 => 1,
+ 107 => 1,
+ 108 => 1,
+ 112 => 1,
+ 113 => 1,
+ 114 => 1,
+ 115 => 1,
+ 116 => 1,
+ 119 => 1,
+ 125 => 1,
+ 136 => 1,
+ 142 => 1,
+ 145 => 1,
+ 152 => 1,
+ 165 => 1,
+ 174 => 1,
+ 178 => 1,
+ 180 => 1,
+ 181 => 1,
+ 188 => 1,
+ 195 => 1,
+ 208 => 1,
+ 213 => 1,
+ 215 => 1,
+ 216 => 1,
+ 221 => 1,
+ 223 => 1,
+ 229 => 1,
+ 238 => 1,
+ 240 => 1,
+ 246 => 1,
+ 248 => 1,
+ 249 => 1,
+ 253 => 1,
+ 254 => 1,
+ 255 => 1,
+ 256 => 1,
+ 257 => 1,
+ 260 => 1,
+ 266 => 1,
+ 277 => 1,
+ 283 => 1,
+ 286 => 1,
+ 293 => 1,
+ 306 => 1,
+ 315 => 1,
+ 319 => 1,
+ 321 => 1,
+ 322 => 1,
+ 329 => 1,
+ 336 => 1,
+ 349 => 1,
+ 354 => 1,
+ 356 => 1,
+ 357 => 1,
+ 362 => 1,
+ 365 => 1,
+ 371 => 1,
+ 380 => 1,
+ 382 => 1,
+ 388 => 1,
+ 390 => 1,
+ 391 => 1,
+ 395 => 1,
+ 396 => 1,
+ 397 => 1,
+ 398 => 1,
+ 399 => 1,
+ 402 => 1,
+ 408 => 1,
+ 419 => 1,
+ 426 => 1,
+ 433 => 1,
+ 446 => 1,
+ 455 => 1,
+ 459 => 1,
+ 461 => 1,
+ 462 => 1,
+ 469 => 1,
+ 476 => 1,
+ 489 => 1,
+ 492 => 1,
+ 493 => 1,
+ 496 => 1,
+ 502 => 1,
+ 513 => 1,
+ 517 => 1,
+ 519 => 1,
+ 520 => 1,
+ 526 => 1,
+ 533 => 1,
+ 546 => 1,
+ 549 => 1,
+ 550 => 1,
+ 551 => 1,
+ 552 => 1,
+ 555 => 1,
+ 561 => 1,
+ 572 => 1,
+ 579 => 1,
+ 581 => 1,
+ 582 => 1,
+ 587 => 1,
+ 592 => 1,
+ 597 => 1,
+ 602 => 1,
+ 607 => 1,
+ 612 => 1,
+ );
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array =>
+ */
+ public function getWarningList() {
+ return array();
+ }
+
+ /**
+ * Returns the fully qualified class name (FQCN) of the sniff.
+ *
+ * @return string The fully qualified class name of the sniff.
+ */
+ protected function get_sniff_fqcn() {
+ return SinceTagSniff::class;
+ }
+
+ /**
+ * Sets the parameters for the sniff.
+ *
+ * @throws RuntimeException If unable to set the ruleset parameters required for the test.
+ *
+ * @param Sniff $sniff The sniff being tested.
+ */
+ public function set_sniff_parameters( Sniff $sniff ) {
+ $sniff->minimumVisibility = 'protected';
+ }
+}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php
index 794a088b7bc61..51174dd769d0a 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php
@@ -10,22 +10,14 @@
namespace GutenbergCS\Gutenberg\Tests\NamingConventions;
use GutenbergCS\Gutenberg\Sniffs\NamingConventions\ValidBlockLibraryFunctionNameSniff;
-use PHP_CodeSniffer\Config;
-use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
-use PHP_CodeSniffer\Ruleset;
+use GutenbergCS\Gutenberg\Tests\AbstractSniffUnitTest;
+use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Unit test class for the ValidBlockLibraryFunctionNameSniff sniff.
*/
final class ValidBlockLibraryFunctionNameUnitTest extends AbstractSniffUnitTest {
- /**
- * Holds the original Ruleset instance.
- *
- * @var Ruleset
- */
- private static $original_ruleset;
-
/**
* Returns the lines where errors should occur.
*
@@ -50,50 +42,22 @@ public function getWarningList() {
}
/**
+ * Returns the fully qualified class name (FQCN) of the sniff.
*
- * This method resets the 'Gutenberg' ruleset in the $GLOBALS['PHP_CODESNIFFER_RULESETS']
- * to its original state.
+ * @return string The fully qualified class name of the sniff.
*/
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] = self::$original_ruleset;
- self::$original_ruleset = null;
+ protected function get_sniff_fqcn() {
+ return ValidBlockLibraryFunctionNameSniff::class;
}
-
/**
- * Prepares the environment before executing tests. Specifically, sets prefixes for the
- * ValidBlockLibraryFunctionName sniff.This is needed since AbstractSniffUnitTest class
- * doesn't apply sniff properties from the Gutenberg/ruleset.xml file.
+ * Sets the parameters for the sniff.
*
- * @param string $filename The name of the file being tested.
- * @param Config $config The config data for the run.
+ * @throws RuntimeException If unable to set the ruleset parameters required for the test.
*
- * @return void
+ * @param Sniff $sniff The sniff being tested.
*/
- public function setCliValues( $filename, $config ) {
- parent::setCliValues( $filename, $config );
-
- if ( ! isset( $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] )
- || ( ! $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] instanceof Ruleset )
- ) {
- throw new \RuntimeException( 'Cannot set ruleset parameters required for this test.' );
- }
-
- // Backup the original Ruleset instance.
- self::$original_ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'];
-
- $current_ruleset = clone self::$original_ruleset;
- $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] = $current_ruleset;
-
- if ( ! isset( $current_ruleset->sniffs[ ValidBlockLibraryFunctionNameSniff::class ] )
- || ( ! $current_ruleset->sniffs[ ValidBlockLibraryFunctionNameSniff::class ] instanceof ValidBlockLibraryFunctionNameSniff )
- ) {
- throw new \RuntimeException( 'Cannot set ruleset parameters required for this test.' );
- }
-
- $sniff = $current_ruleset->sniffs[ ValidBlockLibraryFunctionNameSniff::class ];
+ public function set_sniff_parameters( Sniff $sniff ) {
$sniff->prefixes = array(
'block_core_',
'render_block_core_',
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml b/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml
index 74400e0e6d5cd..503f7e09b85d7 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml
+++ b/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml
@@ -6,5 +6,6 @@
+
diff --git a/test/php/gutenberg-coding-standards/composer.json b/test/php/gutenberg-coding-standards/composer.json
index 0aeec177918c0..c1c27f81818aa 100644
--- a/test/php/gutenberg-coding-standards/composer.json
+++ b/test/php/gutenberg-coding-standards/composer.json
@@ -20,7 +20,8 @@
"ext-libxml": "*",
"ext-tokenizer": "*",
"ext-xmlreader": "*",
- "squizlabs/php_codesniffer": "^3.7.2"
+ "squizlabs/php_codesniffer": "^3.7.2",
+ "phpcsstandards/phpcsutils": "^1.0.8"
},
"require-dev": {
"phpcompatibility/php-compatibility": "^9.0",