7 declare(strict_types=1);
11 use PHP_CodeSniffer\Sniffs\Sniff;
12 use PHP_CodeSniffer\Files\File;
22 private $validTokensBeforeClosingCommentTag = [
35 private $invalidTypes = [
45 public function register() : array
58 private function isTokenBeforeClosingCommentTagValid(
string $type) : bool
60 return in_array(
$type, $this->validTokensBeforeClosingCommentTag);
71 private function validateCommentBlockExists(File $phpcsFile,
int $previousCommentClosePtr,
int $stackPtr) : bool
73 $tokens = $phpcsFile->getTokens();
74 for ($tempPtr = $previousCommentClosePtr + 1; $tempPtr < $stackPtr; $tempPtr++) {
75 if (!$this->isTokenBeforeClosingCommentTagValid(
$tokens[$tempPtr][
'type'])) {
88 private function isInvalidType(
string $type) : bool
90 return in_array(strtolower(
$type), $this->invalidTypes);
101 private function getMethodArguments(File $phpcsFile,
int $openParenthesisPtr,
int $closedParenthesisPtr) : array
103 $tokens = $phpcsFile->getTokens();
104 $methodArguments = [];
105 for (
$i = $openParenthesisPtr;
$i < $closedParenthesisPtr;
$i++) {
106 $argumentsPtr = $phpcsFile->findNext(T_VARIABLE,
$i + 1, $closedParenthesisPtr);
107 if ($argumentsPtr ===
false) {
109 }
elseif ($argumentsPtr < $closedParenthesisPtr) {
112 $i = $argumentsPtr - 1;
115 return $methodArguments;
124 private function getMethodParameters(array $paramDefinitions) : array
127 for (
$i = 0;
$i < count($paramDefinitions);
$i++) {
128 if (isset($paramDefinitions[
$i][
'paramName'])) {
129 $paramName[] = $paramDefinitions[
$i][
'paramName'];
142 private function validateInheritdocAnnotationWithoutBracesExists(
144 int $previousCommentOpenPtr,
145 int $previousCommentClosePtr
147 return $this->validateInheritdocAnnotationExists(
149 $previousCommentOpenPtr,
150 $previousCommentClosePtr,
162 private function validateInheritdocAnnotationWithBracesExists(
164 int $previousCommentOpenPtr,
165 int $previousCommentClosePtr
167 return $this->validateInheritdocAnnotationExists(
169 $previousCommentOpenPtr,
170 $previousCommentClosePtr,
184 private function validateInheritdocAnnotationExists(
186 int $previousCommentOpenPtr,
187 int $previousCommentClosePtr,
188 string $inheritdocAnnotation
190 $tokens = $phpcsFile->getTokens();
191 for ($ptr = $previousCommentOpenPtr; $ptr < $previousCommentClosePtr; $ptr++) {
192 if (strtolower(
$tokens[$ptr][
'content']) === $inheritdocAnnotation) {
209 private function validateParameterAnnotationForArgumentExists(
212 int $parametersCount,
213 int $previousCommentOpenPtr,
214 int $previousCommentClosePtr,
217 if ($argumentsCount > 0 && $parametersCount === 0) {
218 $inheritdocAnnotationWithoutBracesExists = $this->validateInheritdocAnnotationWithoutBracesExists(
220 $previousCommentOpenPtr,
221 $previousCommentClosePtr
223 $inheritdocAnnotationWithBracesExists = $this->validateInheritdocAnnotationWithBracesExists(
225 $previousCommentOpenPtr,
226 $previousCommentClosePtr
228 if ($inheritdocAnnotationWithBracesExists) {
229 $phpcsFile->addFixableError(
230 '{@inheritdoc} does not import parameter annotation',
234 }
elseif ($this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)
235 && !$inheritdocAnnotationWithoutBracesExists
237 $phpcsFile->addFixableError(
238 'Missing @param for argument in method annotation',
254 private function validateCommentBlockDoesnotHaveExtraParameterAnnotation(
257 int $parametersCount,
260 if ($argumentsCount < $parametersCount && $argumentsCount > 0) {
261 $phpcsFile->addFixableError(
262 'Extra @param found in method annotation',
266 }
elseif ($argumentsCount > 0 && $argumentsCount != $parametersCount && $parametersCount != 0) {
267 $phpcsFile->addFixableError(
268 '@param is not found for one or more params in method annotation',
284 private function validateArgumentNameInParameterAnnotationExists(
288 array $methodArguments,
289 array $paramDefinitions
291 $parameterNames = $this->getMethodParameters($paramDefinitions);
292 if (!in_array($methodArguments[$ptr], $parameterNames)) {
293 $error = $methodArguments[$ptr].
' parameter is missing in method annotation';
294 $phpcsFile->addFixableError($error, $stackPtr,
'MethodArguments');
307 private function validateParameterPresentInMethodSignature(
309 string $paramDefinitionsArguments,
310 array $methodArguments,
314 if (!in_array($paramDefinitionsArguments, $methodArguments)) {
315 $phpcsFile->addFixableError(
316 $paramDefinitionsArguments .
' parameter is missing in method arguments signature',
317 $paramPointers[$ptr],
331 private function validateParameterOrderIsCorrect(
332 array $paramDefinitions,
333 array $methodArguments,
337 $parameterNames = $this->getMethodParameters($paramDefinitions);
338 $paramDefinitionsCount = count($paramDefinitions);
339 for ($ptr = 0; $ptr < $paramDefinitionsCount; $ptr++) {
340 if (isset($methodArguments[$ptr]) && isset($parameterNames[$ptr])
341 && in_array($methodArguments[$ptr], $parameterNames)
343 if ($methodArguments[$ptr] != $parameterNames[$ptr]) {
344 $phpcsFile->addFixableError(
345 $methodArguments[$ptr].
' parameter is not in order',
346 $paramPointers[$ptr],
363 private function validateDuplicateAnnotationDoesnotExists(
365 array $paramDefinitions,
366 array $paramPointers,
368 array $methodArguments
370 $argumentsCount = count($methodArguments);
371 $parametersCount = count($paramPointers);
372 if ($argumentsCount <= $parametersCount && $argumentsCount > 0) {
373 $duplicateParameters = [];
374 for (
$i = 0;
$i <
sizeof($paramDefinitions);
$i++) {
375 if (isset($paramDefinitions[
$i][
'paramName'])) {
376 $parameterContent = $paramDefinitions[
$i][
'paramName'];
377 for ($j =
$i + 1; $j < count($paramDefinitions); $j++) {
378 if (isset($paramDefinitions[$j][
'paramName'])
379 && $parameterContent === $paramDefinitions[$j][
'paramName']
381 $duplicateParameters[] = $parameterContent;
386 foreach ($duplicateParameters as
$value) {
387 $phpcsFile->addFixableError(
388 $value .
' duplicate found in method annotation',
405 private function validateParameterAnnotationFormatIsCorrect(
408 array $methodArguments,
409 array $paramDefinitions,
412 switch (count($paramDefinitions)) {
414 $phpcsFile->addFixableError(
415 'Missing both type and parameter',
416 $paramPointers[$ptr],
421 if (preg_match(
'/^\$.*/', $paramDefinitions[0])) {
422 $phpcsFile->addError(
423 'Type is not specified',
424 $paramPointers[$ptr],
430 if ($this->isInvalidType($paramDefinitions[0])) {
431 $phpcsFile->addFixableError(
432 $paramDefinitions[0].
' is not a valid PHP type',
433 $paramPointers[$ptr],
437 $this->validateParameterPresentInMethodSignature(
439 ltrim($paramDefinitions[1],
'&'),
446 if (preg_match(
'/^\$.*/', $paramDefinitions[0])) {
447 $phpcsFile->addError(
448 'Type is not specified',
449 $paramPointers[$ptr],
452 if ($this->isInvalidType($paramDefinitions[0])) {
453 $phpcsFile->addFixableError(
454 $paramDefinitions[0].
' is not a valid PHP type',
455 $paramPointers[$ptr],
475 private function validateMethodParameterAnnotations(
477 array $paramDefinitions,
478 array $paramPointers,
480 array $methodArguments,
481 int $previousCommentOpenPtr,
482 int $previousCommentClosePtr
484 $argumentCount = count($methodArguments);
485 $paramCount = count($paramPointers);
486 $this->validateParameterAnnotationForArgumentExists(
490 $previousCommentOpenPtr,
491 $previousCommentClosePtr,
494 $this->validateCommentBlockDoesnotHaveExtraParameterAnnotation(
500 $this->validateDuplicateAnnotationDoesnotExists(
507 $this->validateParameterOrderIsCorrect(
513 for ($ptr = 0; $ptr < count($methodArguments); $ptr++) {
514 $tokens = $phpcsFile->getTokens();
515 if (isset($paramPointers[$ptr])) {
516 $this->validateArgumentNameInParameterAnnotationExists(
523 $paramContent =
$tokens[$paramPointers[$ptr]+2][
'content'];
524 $paramContentExplode = explode(
' ', $paramContent);
525 $this->validateParameterAnnotationFormatIsCorrect(
529 $paramContentExplode,
539 public function process(File $phpcsFile, $stackPtr)
541 $tokens = $phpcsFile->getTokens();
543 $previousCommentOpenPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr-1, 0);
544 $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr-1, 0);
545 if (!$this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)) {
546 $phpcsFile->addError(
'Comment block is missing', $stackPtr,
'MethodArguments');
549 $openParenthesisPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr+1, $numTokens);
550 $closedParenthesisPtr = $phpcsFile->findNext(T_CLOSE_PARENTHESIS, $stackPtr+1, $numTokens);
551 $methodArguments = $this->getMethodArguments($phpcsFile, $openParenthesisPtr, $closedParenthesisPtr);
552 $paramPointers = $paramDefinitions = [];
553 for ($tempPtr = $previousCommentOpenPtr; $tempPtr < $previousCommentClosePtr; $tempPtr++) {
554 if (strtolower(
$tokens[$tempPtr][
'content']) ===
'@param') {
555 $paramPointers[] = $tempPtr;
556 $paramAnnotationParts = explode(
' ',
$tokens[$tempPtr+2][
'content']);
557 if (count($paramAnnotationParts) === 1) {
558 if ((preg_match(
'/^\$.*/', $paramAnnotationParts[0]))) {
559 $paramDefinitions[] = [
561 'paramName' => rtrim(ltrim(
$tokens[$tempPtr+2][
'content'],
'&'),
',')
564 $paramDefinitions[] = [
565 'type' =>
$tokens[$tempPtr+2][
'content'],
570 $paramDefinitions[] = [
571 'type' => $paramAnnotationParts[0],
572 'paramName' => rtrim(ltrim($paramAnnotationParts[1],
'&'),
',')
577 $this->validateMethodParameterAnnotations(
583 $previousCommentOpenPtr,
584 $previousCommentClosePtr
elseif(isset( $params[ 'redirect_parent']))
process(File $phpcsFile, $stackPtr)