From 9459f49ce3a4636d7f461f308177cdf6b83f5ab1 Mon Sep 17 00:00:00 2001 From: "Hagen, Kody J" Date: Tue, 19 May 2026 14:02:19 -0500 Subject: [PATCH] feat(sniffs): add the multiline comment sniff to enforce /** over /* and fix some lint errors --- .../Commenting/MultiLineBlockCommentSniff.php | 171 ++++++++++++++++++ Sniffs/Functions/GetSiteUrlSniff.php | 8 +- .../Functions/GetTemplateDirectorySniff.php | 8 +- .../NoBlankLineAfterOpenBraceSniff.php | 16 +- .../NoBlankLineBeforeCloseBraceSniff.php | 14 +- .../MultiLineBlockCommentUnitTest.inc | 28 +++ .../MultiLineBlockCommentUnitTest.inc.fixed | 28 +++ .../MultiLineBlockCommentUnitTest.php | 57 ++++++ package.json | 2 +- 9 files changed, 308 insertions(+), 24 deletions(-) create mode 100644 Sniffs/Commenting/MultiLineBlockCommentSniff.php create mode 100644 Tests/Commenting/MultiLineBlockCommentUnitTest.inc create mode 100644 Tests/Commenting/MultiLineBlockCommentUnitTest.inc.fixed create mode 100644 Tests/Commenting/MultiLineBlockCommentUnitTest.php diff --git a/Sniffs/Commenting/MultiLineBlockCommentSniff.php b/Sniffs/Commenting/MultiLineBlockCommentSniff.php new file mode 100644 index 0000000..e7ddf7f --- /dev/null +++ b/Sniffs/Commenting/MultiLineBlockCommentSniff.php @@ -0,0 +1,171 @@ +getTokens(); + + /** + * Define comment content + * + * Get the raw content of the token. + */ + $comment_content = $tokens[ $stack_position ]['content']; + + /** + * Define trimmed comment + * + * Get the trimmed content for checks. + */ + $trimmed_comment = trim( $comment_content ); + + /** + * Define $check_content + * + * Create a whitespace-less version for robust linter directive checks. + */ + $check_content = preg_replace( '/[\s\xA0\xC2\xA0]+/', '', $comment_content ); + + /** + * Check if comment is a phpcs or coverage directive — ignore it. + */ + if ( + strpos( $check_content, 'phpcs:' ) !== false || + strpos( $check_content, '@codeCoverageIgnore' ) !== false + ) { + + return; + + } + + /** + * Check if comment is already a PHPDoc — ignore it. + */ + if ( substr( $trimmed_comment, 0, 3 ) === '/**' ) { + + return; + + } + + /** + * Check if comment is a block comment — if not, ignore it. + */ + if ( substr( $trimmed_comment, 0, 2 ) !== '/*' ) { + + return; + + } + + /** + * Check if comment contains a newline. + * Single-line /* ... *\/ comments are handled by SingleLineCommentSniff, not this one. + */ + if ( preg_match( '/\R/', $comment_content ) !== 1 ) { + + return; + + } + + /** + * Define $last_code_token_pos + * + * Find the position of the last non-whitespace/non-HTML token before the comment. + * Non-breaking spaces are often tokenized as T_INLINE_HTML. + */ + $last_code_token_pos = $phpcs_file->findPrevious( array( T_WHITESPACE, T_INLINE_HTML ), $stack_position - 1, null, true ); + + /** + * Check if a non-whitespace token was found + * AND if it's on the same line as the comment. + * If so, this is an inline comment handled by InlineCommentSniff — ignore it. + */ + if ( false !== $last_code_token_pos && $tokens[ $last_code_token_pos ]['line'] === $tokens[ $stack_position ]['line'] ) { + + return; + + } + + /** + * Define the error + * + * Set the error message for the sniff. + */ + $error = 'Multi-line block comments must use the PHPDoc format (/** ... */).'; + + /** + * Define $fix + * + * Add the fixable error. + */ + $fix = $phpcs_file->addFixableError( $error, $stack_position, 'MultiLineBlockComment' ); + + /** + * Check if $fix is true. + */ + if ( true === $fix ) { + + /** + * Define $fixed_content + * + * Replace the opening /* with /** to convert to PHPDoc format. + */ + $fixed_content = preg_replace( '/^(\s*)\/\*(\s)/', '$1/**$2', $comment_content ); + + $phpcs_file->fixer->beginChangeset(); + $phpcs_file->fixer->replaceToken( $stack_position, $fixed_content ); + $phpcs_file->fixer->endChangeset(); + + } + } +} diff --git a/Sniffs/Functions/GetSiteUrlSniff.php b/Sniffs/Functions/GetSiteUrlSniff.php index b890530..5509cf8 100644 --- a/Sniffs/Functions/GetSiteUrlSniff.php +++ b/Sniffs/Functions/GetSiteUrlSniff.php @@ -45,24 +45,24 @@ public function register() { */ public function process( File $phpcs_file, $stack_position ) { - /* + /** * Get the token from the file. */ $tokens = $phpcs_file->getTokens(); - /* + /** * Check if the current token content is the function name 'get_site_url'. */ if ( 'get_site_url' === $tokens[ $stack_position ]['content'] ) { - /* + /** * Optional: Check if it's being called as a function. */ $next_token = $phpcs_file->findNext( T_WHITESPACE, $stack_position + 1, null, true ); if ( T_OPEN_PARENTHESIS === $tokens[ $next_token ]['code'] ) { - /* + /** * Add the Error message */ $phpcs_file->addError( diff --git a/Sniffs/Functions/GetTemplateDirectorySniff.php b/Sniffs/Functions/GetTemplateDirectorySniff.php index d8bdf30..92e8412 100644 --- a/Sniffs/Functions/GetTemplateDirectorySniff.php +++ b/Sniffs/Functions/GetTemplateDirectorySniff.php @@ -45,24 +45,24 @@ public function register() { */ public function process( File $phpcs_file, $stack_position ) { - /* + /** * Get the token from the file. */ $tokens = $phpcs_file->getTokens(); - /* + /** * Check if the current token content is the function name 'get_template_directory'. */ if ( 'get_template_directory' === $tokens[ $stack_position ]['content'] ) { - /* + /** * Optional: Check if it's being called as a function. */ $next_token = $phpcs_file->findNext( T_WHITESPACE, $stack_position + 1, null, true ); if ( T_OPEN_PARENTHESIS === $tokens[ $next_token ]['code'] ) { - /* + /** * Add the Error message */ $phpcs_file->addError( diff --git a/Sniffs/Spacing/NoBlankLineAfterOpenBraceSniff.php b/Sniffs/Spacing/NoBlankLineAfterOpenBraceSniff.php index 3833a41..fc23348 100644 --- a/Sniffs/Spacing/NoBlankLineAfterOpenBraceSniff.php +++ b/Sniffs/Spacing/NoBlankLineAfterOpenBraceSniff.php @@ -54,12 +54,12 @@ public function register() { */ public function process( File $phpcs_file, $stack_position ) { - /* + /** * Get the token from the file. */ $tokens = $phpcs_file->getTokens(); - /* + /** * Use scope_opener when PHPCS has already resolved it. This correctly * handles functions with return type declarations (e.g. ): bool {) where * a findNext() for T_COLON would match the return-type colon rather than @@ -75,17 +75,17 @@ public function process( File $phpcs_file, $stack_position ) { } - /* + /** * Find the first non-whitespace line after the opening brace or colon. */ $next_line_position = $phpcs_file->findNext( T_WHITESPACE, $open_brace_position + 1, null, true ); - /* + /** * Check if the next non-whitespace token is a closing brace. */ $closing_brace_position = $phpcs_file->findNext( array( T_CLOSE_CURLY_BRACKET, T_ENDIF, T_ENDFOREACH ), $open_brace_position + 1 ); - /* + /** * Skip the block if it opens and closes immediately. */ if ( false !== $closing_brace_position && $tokens[ $closing_brace_position ]['line'] === $tokens[ $open_brace_position ]['line'] + 1 ) { @@ -94,7 +94,7 @@ public function process( File $phpcs_file, $stack_position ) { } - /* + /** * Ensure there is a blank line after the opening brace, even if a comment follows. */ if ( $tokens[ $next_line_position ]['line'] - $tokens[ $open_brace_position ]['line'] <= 1 ) { @@ -109,7 +109,7 @@ public function process( File $phpcs_file, $stack_position ) { $phpcs_file->fixer->beginChangeset(); - /* + /** * Retrieve the indentation of the line after the opening brace. */ $indentation = ''; @@ -126,7 +126,7 @@ public function process( File $phpcs_file, $stack_position ) { } } - /* + /** * Add the new blank line with the correct indentation. */ $phpcs_file->fixer->addContent( $open_brace_position, PHP_EOL . $indentation ); diff --git a/Sniffs/Spacing/NoBlankLineBeforeCloseBraceSniff.php b/Sniffs/Spacing/NoBlankLineBeforeCloseBraceSniff.php index a7330fb..0a46c52 100644 --- a/Sniffs/Spacing/NoBlankLineBeforeCloseBraceSniff.php +++ b/Sniffs/Spacing/NoBlankLineBeforeCloseBraceSniff.php @@ -52,12 +52,12 @@ public function register() { */ public function process( File $phpcs_file, $stack_position ) { - /* + /** * Get the token from the file. */ $tokens = $phpcs_file->getTokens(); - /* + /** * Find the opening brace or colon (for alternative syntax). */ $open_brace_position = $phpcs_file->findNext( @@ -65,7 +65,7 @@ public function process( File $phpcs_file, $stack_position ) { $stack_position + 1 ); - /* + /** * If not found, exit the sniff. */ if ( false === $open_brace_position ) { @@ -74,7 +74,7 @@ public function process( File $phpcs_file, $stack_position ) { } - /* + /** * Define $close_brace_position using bracket_closer. */ $close_brace_position = $tokens[ $open_brace_position ]['bracket_closer'] ?? null; @@ -84,7 +84,7 @@ public function process( File $phpcs_file, $stack_position ) { } - /* + /** * Define $prev_code_position for last meaningful line before the close brace. */ $prev_code_position = $phpcs_file->findPrevious( @@ -120,7 +120,7 @@ public function process( File $phpcs_file, $stack_position ) { } - /* + /** * Find next and previous non-whitespace tokens around the closing brace. */ $next_code_position = $phpcs_file->findNext( @@ -176,7 +176,7 @@ public function process( File $phpcs_file, $stack_position ) { } - /* + /** * Calculate line difference to detect missing blank line. */ $line_difference = $tokens[ $close_brace_position ]['line'] - $tokens[ $prev_code_position ]['line']; diff --git a/Tests/Commenting/MultiLineBlockCommentUnitTest.inc b/Tests/Commenting/MultiLineBlockCommentUnitTest.inc new file mode 100644 index 0000000..bd41a9f --- /dev/null +++ b/Tests/Commenting/MultiLineBlockCommentUnitTest.inc @@ -0,0 +1,28 @@ + Key is line number, value is number of expected errors. + */ + public function getErrorList( $testFile = '' ) { + + return array( + 10 => 1, + 26 => 1, + ); + + } + + + + + + + + /** + * Returns the lines where warnings should occur. + * + * @param string $testFile The name of the file being tested. + * + * @return array Key is line number, value is number of expected warnings. + */ + public function getWarningList( $testFile = '' ) { + + return array(); + + } +} diff --git a/package.json b/package.json index 83d641c..0ca9cce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bsu/BSUWordPressCS", - "version": "1.1.4", + "version": "1.1.5", "description": "Custom PHPCS sniffs for BSU WordPress standards", "author": "BSU|NTC Web Services", "license": "Copyright Web Services, Bemidji State University - All Rights Reserved. Unauthorized copying of any files or code in this package, via any medium is strictly prohibited.",