-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Member variables and methods should not require DocBlock when properly typed #476
base: develop
Are you sure you want to change the base?
Changes from 5 commits
ee3ddb3
83c8668
b442f9a
38f565c
59b5d46
17e120f
b5c1496
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,8 @@ class MethodArgumentsSniff implements Sniff | |
'self' | ||
]; | ||
|
||
private const MAXIMUM_COMPLEXITY_ALLOWED_FOR_NO_DOCBLOCK = 5; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
|
@@ -568,6 +570,9 @@ public function process(File $phpcsFile, $stackPtr) | |
$previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr - 1, 0); | ||
if ($previousCommentClosePtr && $previousCommentOpenPtr) { | ||
if (!$this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)) { | ||
if (!$this->checkIfMethodNeedsDocBlock($phpcsFile, $stackPtr)) { | ||
return; | ||
} | ||
$phpcsFile->addError('Comment block is missing', $stackPtr, 'NoCommentBlock'); | ||
return; | ||
} | ||
|
@@ -617,6 +622,104 @@ public function process(File $phpcsFile, $stackPtr) | |
); | ||
} | ||
|
||
/** | ||
* Check method if it has defined return & parameter types, then it doesn't need DocBlock | ||
* | ||
* @param File $phpcsFile | ||
* @param int $stackPtr | ||
* @return bool | ||
*/ | ||
private function checkIfMethodNeedsDocBlock(File $phpcsFile, $stackPtr) : bool | ||
{ | ||
if ($this->checkIfNamespaceContainsApi($phpcsFile)) { | ||
// Note: API classes MUST have DocBlock because it uses them instead of reflection for types | ||
return true; | ||
} | ||
$foundAllParameterTypes = true; | ||
$methodParameters = $phpcsFile->getMethodParameters($stackPtr); | ||
foreach ($methodParameters as $parameter) { | ||
if (!$parameter['type_hint']) { | ||
return true; // doesn't have type hint | ||
} | ||
} | ||
$methodProperties = $phpcsFile->getMethodProperties($stackPtr); | ||
$foundReturnType = !!$methodProperties['return_type']; | ||
if (!$foundReturnType) { | ||
$methodName = $phpcsFile->getDeclarationName($stackPtr); | ||
// Note: __construct and __destruct can't have return types | ||
if ('__construct' !== $methodName && '__destruct' !== $methodName) { | ||
return true; | ||
} | ||
} | ||
$complexity = $this->getMethodComplexity($phpcsFile, $stackPtr); | ||
return $complexity > static::MAXIMUM_COMPLEXITY_ALLOWED_FOR_NO_DOCBLOCK; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a test to cover this case - where a docblock is required due to high complexity score. |
||
} | ||
|
||
/** | ||
* Check if namespace contains API | ||
* | ||
* @param File $phpcsFile | ||
* @return bool | ||
*/ | ||
private function checkIfNamespaceContainsApi(File $phpcsFile) : bool | ||
{ | ||
$namespaceStackPtr = $phpcsFile->findNext(T_NAMESPACE, 0); | ||
if (!is_int($namespaceStackPtr)) { | ||
return false; | ||
} | ||
$tokens = $phpcsFile->getTokens(); | ||
// phpcs:ignore Squiz.ControlStructures.ForLoopDeclaration.SpacingAfterOpen,Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed | ||
JacobBrownAustin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for ( | ||
// phpcs:ignore Squiz.ControlStructures.ForLoopDeclaration.SpacingAfterFirst | ||
JacobBrownAustin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$index = $namespaceStackPtr; | ||
// phpcs:ignore Squiz.ControlStructures.ForLoopDeclaration.SpacingAfterSecond | ||
JacobBrownAustin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
array_key_exists($index, $tokens) && 'T_SEMICOLON' !== $tokens[$index]['type']; | ||
$index++ | ||
// phpcs:ignore Squiz.ControlStructures.ForLoopDeclaration.SpacingBeforeClose | ||
JacobBrownAustin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) { | ||
if ('T_STRING' === $tokens[$index]['type'] && 'Api' === $tokens[$index]['content']) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Get method CyclomaticComplexity | ||
* | ||
* @param File $phpcsFile | ||
* @param int $stackPtr | ||
* @return int | ||
*/ | ||
private function getMethodComplexity(File $phpcsFile, $stackPtr) : int | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
$start = $tokens[$stackPtr]['scope_opener']; | ||
$end = $tokens[$stackPtr]['scope_closer']; | ||
$predicateNodes = [ | ||
T_CASE => true, | ||
T_DEFAULT => true, | ||
T_CATCH => true, | ||
T_IF => true, | ||
T_FOR => true, | ||
T_FOREACH => true, | ||
T_WHILE => true, | ||
T_ELSEIF => true, | ||
T_INLINE_THEN => true, | ||
T_COALESCE => true, | ||
T_COALESCE_EQUAL => true, | ||
T_MATCH_ARROW => true, | ||
T_NULLSAFE_OBJECT_OPERATOR => true, | ||
]; | ||
$complexity = 1; | ||
for ($stackPtr = $start + 1; $stackPtr < $end; $stackPtr++) { | ||
if (isset($predicateNodes[$tokens[$stackPtr]['code']])) { | ||
$complexity++; | ||
} | ||
} | ||
return $complexity; | ||
} | ||
|
||
/** | ||
* Validates function params format consistency. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,13 +41,63 @@ public function invalidDocBlockShouldNotCauseFatalErrorInSniff(int $number): int | |
* @return string | ||
*/ | ||
#[\ReturnTypeWillChange] | ||
public function methodWithAttributeAndValidDocblock(string $text): string | ||
public function methodWithAttributeAndValidDocblockWithTypes(string $text): string | ||
{ | ||
return $text; | ||
} | ||
|
||
/** | ||
* Short description. | ||
* | ||
* @param string $text | ||
* @return string | ||
*/ | ||
#[\ReturnTypeWillChange] | ||
public function methodWithAttributeAndValidDocblockWithoutTypes() | ||
Comment on lines
+52
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method has no |
||
{ | ||
return $text; | ||
} | ||
|
||
#[\ReturnTypeWillChange] | ||
public function methodWithAttributeAndWithoutDocblock(string $text): string | ||
public function methodWithAttributeAndWithoutDocblockWithTypes(string $text): string | ||
{ | ||
return $text; | ||
} | ||
|
||
#[\ReturnTypeWillChange] | ||
public function methodWithAttributeAndWithoutDocblockWithoutTypes($text) | ||
{ | ||
return $text; | ||
} | ||
|
||
public function methodWithoutDockblockWithParameterTypeWithoutReturnType(string $text) | ||
{ | ||
return $text; | ||
} | ||
|
||
public function methodWithoutDockblockWithoutParameterTypeWithReturnType($text) : string | ||
{ | ||
return $text; | ||
} | ||
|
||
/** | ||
* Short description. | ||
* | ||
* @param string $text | ||
* @return string | ||
*/ | ||
public function methodWithDockblockWithParameterTypeWithoutReturnType(string $text) | ||
{ | ||
return $text; | ||
} | ||
|
||
/** | ||
* Short description. | ||
* | ||
* @param mixed $text | ||
* @return string | ||
*/ | ||
public function methodWithDockblockWithoutParameterTypeWithReturnType($text) : string | ||
{ | ||
return $text; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,9 @@ public function getErrorList() | |
12 => 1, | ||
21 => 1, | ||
32 => 1, | ||
50 => 1 | ||
68 => 1, | ||
73 => 1, | ||
78 => 1, | ||
]; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test to cover this case - where a docblock is required due to a class being an API.