From 0b9db720cc1726da98722f5999317d57b66847f8 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Fri, 10 May 2024 16:55:35 +0200 Subject: [PATCH] 1. Added support for checking `@since` tags for class, trait, interface, and enum declarations. 2. Added support for checking `@since` tags for class and trait properties; 3. Added support for checking `@since` tags for class, interface, and trait method declarations; 4. Added support for checking `@since` tags for WordPress functions that invoke hooks: `do_action()`, `do_action_ref_array()`, `apply_filters()`, `apply_filters_ref_array()`. 5. Refactored unit tests; duplicate code moved to the new `GutenbergCS\Gutenberg\Tests\AbstractSniffUnitTest` class. --- .../Sniffs/Commenting/SinceTagSniff.php | 567 ++++++++++++++-- .../Tests/Commenting/SinceTagUnitTest.inc | 612 +++++++++++++++++- .../Tests/Commenting/SinceTagUnitTest.php | 163 ++++- 3 files changed, 1267 insertions(+), 75 deletions(-) diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php index 54b8b367560fc..f216f4f681f0e 100644 --- a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php +++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php @@ -12,12 +12,49 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Tokens\Collections; +use PHPCSUtils\Utils\FunctionDeclarations; +use PHPCSUtils\Utils\GetTokensAsString; +use PHPCSUtils\Utils\ObjectDeclarations; +use PHPCSUtils\Utils\Scopes; +use PHPCSUtils\Utils\Variables; /** - * This sniff ensures that PHP functions have a valid `@since` tag in the docblock. - * The sniff skips checking files in __experimental block-library blocks. + * This sniff verifies the presence of valid `@since` tags in the docblocks of various PHP structures + * and WordPress hooks. Supported structures include classes, interfaces, traits, enums, functions, methods and properties. + * Files located within the __experimental block of the block-library are excluded from checks. */ -class FunctionCommentSinceTagSniff implements Sniff { +class SinceTagSniff implements Sniff { + + /** + * Disable the check for functions with a lower visibility than the value given. + * + * Allowed values are public, protected, and private. + * + * @var string + */ + public $minimumVisibility = 'private'; + + /** + * A map of tokens representing an object-oriented programming structure to their human-readable names. + * This map helps in identifying different OO structures such as classes, interfaces, traits, and enums. + * + * @var array + */ + protected static $oo_tokens = array( + T_CLASS => 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 @@ -25,7 +62,7 @@ class FunctionCommentSinceTagSniff implements Sniff { * * @var array */ - private static $cache = array(); + protected static $cache = array(); /** * Returns an array of tokens this test wants to listen for. @@ -33,7 +70,14 @@ class FunctionCommentSinceTagSniff implements Sniff { * @return array */ public function register() { - return array( T_FUNCTION ); + return array_merge( + array( + T_FUNCTION, + T_VARIABLE, + T_STRING, + ), + array_keys( static::$oo_tokens ) + ); } /** @@ -48,90 +92,501 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - $tokens = $phpcsFile->getTokens(); - $function_name = $phpcsFile->getDeclarationName( $stackPtr ); + $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(); - $wrapping_tokens_to_check = array( - T_CLASS, - T_INTERFACE, - T_TRAIT, + // 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', ); - 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; + // 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 ) ) + ); } + } - $missing_since_tag_error_message = sprintf( '@since tag is missing for the "%s()" function.', $function_name ); + /** + * 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']; - // 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, + $token_name = ObjectDeclarations::getName( $phpcs_file, $stack_pointer ); + $error_message_data = array( + $token_name, + $token_type, ); - $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' ); + $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; } - // 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; + 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 ) ) + ); } + } - // This is the first non-docblock token, so the next token should be used. - ++$doc_block_start_token; + /** + * 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 ); - $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' ); + $visibility = Variables::getMemberProperties( $phpcs_file, $stack_pointer )['scope']; + if ( $this->check_below_minimum_visibility( $visibility ) ) { 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' ); + $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; } - $version_value = $tokens[ $version_token ]['content']; + 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, + ); - if ( version_compare( $version_value, '0.0.1', '>=' ) ) { - // Validate the version value. + $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; } - $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, - ) + 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 $phpcsFile The file being scanned. + * @param File $phpcs_file 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'; + 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 ]; diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc index 43d11694bb25e..aa8593b2e2757 100644 --- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc +++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.inc @@ -1,39 +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 index b41e794d072fd..bc7ca28c263ff 100644 --- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php +++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php @@ -9,13 +9,14 @@ namespace GutenbergCS\Gutenberg\Tests\Commenting; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -use PHP_CodeSniffer\Ruleset; +use GutenbergCS\Gutenberg\Sniffs\Commenting\SinceTagSniff; +use GutenbergCS\Gutenberg\Tests\AbstractSniffUnitTest; +use PHP_CodeSniffer\Sniffs\Sniff; /** - * Unit test class for the FunctionCommentSinceTagSniff sniff. + * Unit test class for the SinceTagSniff sniff. */ -final class FunctionCommentSinceTagUnitTest extends AbstractSniffUnitTest { +final class SinceTagUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -23,11 +24,137 @@ final class FunctionCommentSinceTagUnitTest extends AbstractSniffUnitTest { * @return array => */ 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, + 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, ); } @@ -39,4 +166,24 @@ public function getErrorList() { 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'; + } }