ComponentFactoryCommentSniff::hasMethodComment()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types = 1);
4
5
/*
6
 * This file is part of Zenify
7
 * Copyright (c) 2012 Tomas Votruba (http://tomasvotruba.cz)
8
 */
9
10
namespace ZenifyCodingStandard\Sniffs\Commenting;
11
12
use PHP_CodeSniffer_File;
13
use PHP_CodeSniffer_Sniff;
14
use ZenifyCodingStandard\Helper\Commenting\FunctionHelper;
15
16
17
/**
18
 * Rules:
19
 * - CreateComponent* method should have a doc comment.
20
 * - CreateComponent* method should have a return tag.
21
 * - Return tag should contain type.
22
 */
23
final class ComponentFactoryCommentSniff implements PHP_CodeSniffer_Sniff
24
{
25
26
	/**
27
	 * @var string
28
	 */
29
	const NAME = 'ZenifyCodingStandard.Commenting.ComponentFactoryComment';
30
31
	/**
32
	 * @var int
33
	 */
34
	private $position;
35
36
	/**
37
	 * @var PHP_CodeSniffer_File
38
	 */
39
	private $file;
40
41
	/**
42
	 * @var array
43
	 */
44
	private $tokens;
45
46
47 1
	public function register() : array
48
	{
49 1
		return [T_FUNCTION];
50
	}
51
52
53
	/**
54
	 * @param PHP_CodeSniffer_File $file
55
	 * @param int $position
56
	 */
57 1
	public function process(PHP_CodeSniffer_File $file, $position)
58
	{
59 1
		$this->file = $file;
60 1
		$this->position = $position;
61 1
		$this->tokens = $file->getTokens();
62
63 1
		if ( ! $this->isComponentFactoryMethod()) {
64
			return;
65
		}
66
67 1
		$returnTypeHint = FunctionHelper::findReturnTypeHint($file, $position);
68 1
		if ($returnTypeHint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $returnTypeHint of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
69 1
			return;
70
		}
71
72 1
		$commentEnd = $this->getCommentEnd();
73 1
		if ( ! $this->hasMethodComment($commentEnd)) {
0 ignored issues
show
Bug introduced by
It seems like $commentEnd defined by $this->getCommentEnd() on line 72 can also be of type boolean; however, ZenifyCodingStandard\Sni...iff::hasMethodComment() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
74 1
			$file->addError('createComponent<name> method should have a doc comment or return type.', $position);
75 1
			return;
76
		}
77
78 1
		$commentStart = $this->tokens[$commentEnd]['comment_opener'];
79 1
		$this->processReturnTag($commentStart);
80 1
	}
81
82
83 1
	private function isComponentFactoryMethod() : bool
84
	{
85 1
		$functionName = $this->file->getDeclarationName($this->position);
86 1
		return (strpos($functionName, 'createComponent') === 0);
87
	}
88
89
90
	/**
91
	 * @return bool|int
92
	 */
93 1
	private function getCommentEnd()
94
	{
95 1
		return $this->file->findPrevious(T_WHITESPACE, ($this->position - 3), NULL, TRUE);
96
	}
97
98
99 1
	private function hasMethodComment(int $position) : bool
100
	{
101 1
		if ($this->tokens[$position]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
102 1
			return TRUE;
103
		}
104 1
		return FALSE;
105
	}
106
107
108 1
	private function processReturnTag(int $commentStartPosition)
109
	{
110 1
		$return = NULL;
111 1
		foreach ($this->tokens[$commentStartPosition]['comment_tags'] as $tag) {
112 1
			if ($this->tokens[$tag]['content'] === '@return') {
113 1
				$return = $tag;
114
			}
115
		}
116 1
		if ($return !== NULL) {
117 1
			$content = $this->tokens[($return + 2)]['content'];
118 1
			if (empty($content) === TRUE || $this->tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) {
119 1
				$error = 'Return tag should contain type';
120 1
				$this->file->addError($error, $return);
121
			}
122
123
		} else {
124 1
			$error = 'CreateComponent* method should have a @return tag';
125 1
			$this->file->addError($error, $this->tokens[$commentStartPosition]['comment_closer']);
126
		}
127 1
	}
128
129
}
130