Skip to content

Commit

Permalink
PEAR/FunctionComment: prevent false positives with attributes
Browse files Browse the repository at this point in the history
PHP 8.0+ attributes can be placed between a docblock and the function declaration it applies to.

The `PEAR.Commenting.FunctionComment` sniff - and by extension the `Squiz.Commenting.FunctionComment` sniff - did not yet take this into account.

This would result in a false positive `Missing doc comment for function ... ` error.

Resolving that error though, would result in a new `There must be no blank lines after the function comment` error, so the blank line check also needed to be fixed.

With the current fix, blank lines between the docblock and the function declaration are still not allowed, but non-blank lines between the two (i.e. lines containing attributes) will be ignored.

Only one "no blank lines between" error will be thrown per function declaration.
  • Loading branch information
jrfnl committed Jul 23, 2021
1 parent d5c1cf7 commit 5b710b8
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 6 deletions.
37 changes: 31 additions & 6 deletions src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,25 @@ public function process(File $phpcsFile, $stackPtr)
return;
}

$tokens = $phpcsFile->getTokens();
$ignore = Tokens::$methodPrefixes;
$ignore[] = T_WHITESPACE;
$tokens = $phpcsFile->getTokens();
$ignore = Tokens::$methodPrefixes;
$ignore[T_WHITESPACE] = T_WHITESPACE;

for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) {
if (isset($ignore[$tokens[$commentEnd]['code']]) === true) {
continue;
}

if ($tokens[$commentEnd]['code'] === T_ATTRIBUTE_END
&& isset($tokens[$commentEnd]['attribute_opener']) === true
) {
$commentEnd = $tokens[$commentEnd]['attribute_opener'];
continue;
}

break;
}

$commentEnd = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true);
if ($tokens[$commentEnd]['code'] === T_COMMENT) {
// Inline comments might just be closing comments for
// control structures or functions instead of function comments
Expand Down Expand Up @@ -106,8 +120,19 @@ public function process(File $phpcsFile, $stackPtr)
}

if ($tokens[$commentEnd]['line'] !== ($tokens[$stackPtr]['line'] - 1)) {
$error = 'There must be no blank lines after the function comment';
$phpcsFile->addError($error, $commentEnd, 'SpacingAfter');
for ($i = ($commentEnd + 1); $i < $stackPtr; $i++) {
if ($tokens[$i]['column'] !== 1) {
continue;
}

if ($tokens[$i]['code'] === T_WHITESPACE
&& $tokens[$i]['line'] !== $tokens[($i + 1)]['line']
) {
$error = 'There must be no blank lines after the function comment';
$phpcsFile->addError($error, $commentEnd, 'SpacingAfter');
break;
}
}
}

$commentStart = $tokens[$commentEnd]['comment_opener'];
Expand Down
47 changes: 47 additions & 0 deletions src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,50 @@ public function ignored() {
}

// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] __construct,__destruct

class Something implements JsonSerializable {
/**
* Single attribute.
*
* @return mixed
*/
#[ReturnTypeWillChange]
public function jsonSerialize() {}

/**
* Multiple attributes.
*
* @return Something
*/
#[AttributeA]
#[AttributeB]
public function methodName() {}

/**
* Blank line between docblock and attribute.
*
* @return mixed
*/

#[ReturnTypeWillChange]
public function blankLineDetectionA() {}

/**
* Blank line between attribute and function declaration.
*
* @return mixed
*/
#[ReturnTypeWillChange]

public function blankLineDetectionB() {}

/**
* Blank line between both docblock and attribute and attribute and function declaration.
*
* @return mixed
*/

#[ReturnTypeWillChange]

public function blankLineDetectionC() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,50 @@ public function ignored() {
}

// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] __construct,__destruct

class Something implements JsonSerializable {
/**
* Single attribute.
*
* @return mixed
*/
#[ReturnTypeWillChange]
public function jsonSerialize() {}

/**
* Multiple attributes.
*
* @return Something
*/
#[AttributeA]
#[AttributeB]
public function methodName() {}

/**
* Blank line between docblock and attribute.
*
* @return mixed
*/

#[ReturnTypeWillChange]
public function blankLineDetectionA() {}

/**
* Blank line between attribute and function declaration.
*
* @return mixed
*/
#[ReturnTypeWillChange]

public function blankLineDetectionB() {}

/**
* Blank line between both docblock and attribute and attribute and function declaration.
*
* @return mixed
*/

#[ReturnTypeWillChange]

public function blankLineDetectionC() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public function getErrorList()
364 => 1,
406 => 1,
417 => 1,
455 => 1,
464 => 1,
473 => 1,
];

}//end getErrorList()
Expand Down

0 comments on commit 5b710b8

Please sign in to comment.