Completed
Push — master ( e19264...2ec4ff )
by Tomáš
49:11 queued 44:57
created

ComponentFactoryCommentSniff   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 97.5%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 2
dl 0
loc 102
ccs 39
cts 40
cp 0.975
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 4 1
B process() 0 24 4
A isComponentFactoryMethod() 0 5 1
A getCommentEnd() 0 4 1
A hasMethodComment() 0 7 2
B processReturnTag() 0 20 6
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 int
28
	 */
29
	private $position;
30
31
	/**
32
	 * @var PHP_CodeSniffer_File
33
	 */
34
	private $file;
35
36
	/**
37
	 * @var array
38
	 */
39
	private $tokens;
40
41
42 1
	public function register() : array
43
	{
44 1
		return [T_FUNCTION];
45
	}
46
47
48
	/**
49
	 * @param PHP_CodeSniffer_File $file
50
	 * @param int $position
51
	 */
52 1
	public function process(PHP_CodeSniffer_File $file, $position)
53
	{
54 1
		$this->file = $file;
55 1
		$this->position = $position;
56 1
		$this->tokens = $file->getTokens();
57
58 1
		if ( ! $this->isComponentFactoryMethod()) {
59
			return;
60
		}
61
62 1
		$returnTypeHint = FunctionHelper::findReturnTypeHint($file, $position);
63 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...
64 1
			return;
65
		}
66
67 1
		$commentEnd = $this->getCommentEnd();
68 1
		if ( ! $this->hasMethodComment($commentEnd)) {
0 ignored issues
show
Bug introduced by
It seems like $commentEnd defined by $this->getCommentEnd() on line 67 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...
69 1
			$file->addError('createComponent<name> method should have a doc comment or return type.', $position);
70 1
			return;
71
		}
72
73 1
		$commentStart = $this->tokens[$commentEnd]['comment_opener'];
74 1
		$this->processReturnTag($commentStart);
75 1
	}
76
77
78 1
	private function isComponentFactoryMethod() : bool
79
	{
80 1
		$functionName = $this->file->getDeclarationName($this->position);
81 1
		return (strpos($functionName, 'createComponent') === 0);
82
	}
83
84
85
	/**
86
	 * @return bool|int
87
	 */
88 1
	private function getCommentEnd()
89
	{
90 1
		return $this->file->findPrevious(T_WHITESPACE, ($this->position - 3), NULL, TRUE);
91
	}
92
93
94 1
	private function hasMethodComment(int $position) : bool
95
	{
96 1
		if ($this->tokens[$position]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
97 1
			return TRUE;
98
		}
99 1
		return FALSE;
100
	}
101
102
103 1
	private function processReturnTag(int $commentStartPosition)
104
	{
105 1
		$return = NULL;
106 1
		foreach ($this->tokens[$commentStartPosition]['comment_tags'] as $tag) {
107 1
			if ($this->tokens[$tag]['content'] === '@return') {
108 1
				$return = $tag;
109
			}
110
		}
111 1
		if ($return !== NULL) {
112 1
			$content = $this->tokens[($return + 2)]['content'];
113 1
			if (empty($content) === TRUE || $this->tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) {
114 1
				$error = 'Return tag should contain type';
115 1
				$this->file->addError($error, $return);
116
			}
117
118
		} else {
119 1
			$error = 'CreateComponent* method should have a @return tag';
120 1
			$this->file->addError($error, $this->tokens[$commentStartPosition]['comment_closer']);
121
		}
122 1
	}
123
124
}
125