Passed
Pull Request — master (#10)
by
unknown
02:28
created

FluentSetterSniff   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 196
Duplicated Lines 14.29 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 12
lcom 1
cbo 3
dl 28
loc 196
ccs 60
cts 60
cp 1
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C processTokenWithinScope() 28 69 7
A checkIfSetterFunction() 0 6 1
A fixNoReturnFound() 0 13 1
A fixMustReturnThis() 0 15 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BestIt\Sniffs\Functions;
6
7
use PHP_CodeSniffer_File;
8
use PHP_CodeSniffer_Standards_AbstractScopeSniff;
9
10
/**
11
 * Class FluentSetterSniff
12
 *
13
 * @package BestIt\Sniffs\Functions
14
 *
15
 * @author Nick Lubisch <[email protected]>
16
 */
17
class FluentSetterSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff
18
{
19
    /**
20
     * Code when multiple return statements are found.
21
     *
22
     * @var string
23
     */
24
    const CODE_MULTIPLE_RETURN_FOUND = 'MultipleReturnFound';
25
26
    /**
27
     * Code when the method does not return $this.
28
     *
29
     * @var string
30
     */
31
    const CODE_MUST_RETURN_THIS = 'MustReturnThis';
32
33
    /**
34
     * Code when no return statement is found.
35
     *
36
     * @var string
37
     */
38
    const CODE_NO_RETURN_FOUND = 'NoReturnFound';
39
40
    /**
41
     * Error message when no return statement is found.
42
     *
43
     * @var string
44
     */
45
    const ERROR_NO_RETURN_FOUND = 'Method "%s" has no return statement';
46
47
    /**
48
     * Error message when multiple return statements are found.
49
     *
50
     * @var string
51
     */
52
    const ERROR_MULTIPLE_RETURN_FOUND = 'Method "%s" has multiple return statements';
53
54
    /**
55
     * Error message when the method does not return $this.
56
     *
57
     * @var string
58
     */
59
    const ERROR_MUST_RETURN_THIS = 'The method "%s" must return $this';
60
61
    /**
62
     * Specifies how an identation looks like.
63
     *
64
     * @var string
65
     */
66
    public $identation = '    ';
67
68
    /**
69
     * FluentSetterSniff constructor.
70
     */
71 4
    public function __construct()
72
    {
73 4
        parent::__construct([T_CLASS, T_ANON_CLASS, T_TRAIT], [T_FUNCTION], false);
74 4
    }
75
76
    /**
77
     * @inheritdoc
78
     */
79 4
    protected function processTokenWithinScope(
80
        PHP_CodeSniffer_File $phpcsFile,
81
        $stackPtr,
82
        $currScope
83
    ) {
84 4
        $className = $phpcsFile->getDeclarationName($currScope);
85 4
        $methodName = $phpcsFile->getDeclarationName($stackPtr);
86
87 4
        if (!$this->checkIfSetterFunction($methodName)) {
88 1
            return;
89
        }
90
91 4
        $tokens = $phpcsFile->getTokens();
92 4
        $errorData = sprintf('%s::%s', $className, $methodName);
93
94 4
        $functionToken = $tokens[$stackPtr];
95 4
        $openBracePtr = $functionToken['scope_opener'];
96 4
        $closeBracePtr = $functionToken['scope_closer'];
97
98 4
        $returnPtr = $phpcsFile->findNext(T_RETURN, $openBracePtr, $closeBracePtr);
99
100 4 View Code Duplication
        if ($returnPtr === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101 1
            $fixNoReturnFound = $phpcsFile->addFixableError(
102 1
                self::ERROR_NO_RETURN_FOUND,
103
                $stackPtr,
104 1
                self::CODE_NO_RETURN_FOUND,
105
                $errorData
0 ignored issues
show
Documentation introduced by
$errorData is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
106
            );
107
108 1
            if ($fixNoReturnFound) {
109 1
                $this->fixNoReturnFound($phpcsFile, $closeBracePtr);
110
            }
111
112 1
            return;
113
        }
114
115 4
        $nextReturnPtr = $phpcsFile->findNext(
116 4
            T_RETURN,
117 4
            $returnPtr + 1,
118
            $closeBracePtr
119
        );
120
121 4
        if ($nextReturnPtr !== false) {
122 1
            $phpcsFile->addError(
123 1
                self::ERROR_MULTIPLE_RETURN_FOUND,
124
                $stackPtr,
125 1
                self::CODE_MULTIPLE_RETURN_FOUND,
126
                $errorData
0 ignored issues
show
Documentation introduced by
$errorData is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
            );
128 1
            return;
129
        }
130
131 3
        $thisVariablePtr = $phpcsFile->findNext(T_VARIABLE, $returnPtr, null, false, '$this', true);
132
133 3 View Code Duplication
        if ($thisVariablePtr === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134 1
            $fixMustReturnThis = $phpcsFile->addFixableError(
135 1
                self::ERROR_MUST_RETURN_THIS,
136
                $stackPtr,
137 1
                self::CODE_MUST_RETURN_THIS,
138
                $errorData
0 ignored issues
show
Documentation introduced by
$errorData is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
139
            );
140
141 1
            if ($fixMustReturnThis) {
142 1
                $this->fixMustReturnThis($phpcsFile, $returnPtr);
143
            }
144
145 1
            return;
146
        }
147 3
    }
148
149
    /**
150
     * Checks if the given method name relates to a setter function.
151
     *
152
     * Its not a simple strpos because a method name like "setupDatabase" would be catched.
153
     * We check that the letters until the first upper case character equals "set".
154
     * This way we expect that after "set" follows an upper case letter.
155
     *
156
     * @param string $methodName
157
     *
158
     * @return bool
159
     */
160 4
    private function checkIfSetterFunction($methodName)
161
    {
162 4
        $firstMatch = strcspn($methodName, 'ABCDEFGHJIJKLMNOPQRSTUVWXYZ');
163
164 4
        return substr($methodName, 0, $firstMatch) === 'set';
165
    }
166
167
    /**
168
     * Fixes if no return statement is found.
169
     *
170
     * @param PHP_CodeSniffer_File $phpcsFile
171
     * @param int $closingBracePtr
172
     *
173
     * @return void
174
     */
175 1
    private function fixNoReturnFound(PHP_CodeSniffer_File $phpcsFile, $closingBracePtr)
176
    {
177 1
        $tokens = $phpcsFile->getTokens();
178 1
        $closingBraceToken = $tokens[$closingBracePtr];
179
180 1
        $expectedReturnSpaces = str_repeat($this->identation, $closingBraceToken['level'] + 1);
181
182 1
        $phpcsFile->fixer->beginChangeset();
183 1
        $phpcsFile->fixer->addNewlineBefore($closingBracePtr - 1);
184 1
        $phpcsFile->fixer->addContentBefore($closingBracePtr - 1, $expectedReturnSpaces . 'return $this;');
185 1
        $phpcsFile->fixer->addNewlineBefore($closingBracePtr - 1);
186 1
        $phpcsFile->fixer->endChangeset();
187 1
    }
188
189
    /**
190
     * Fixes the return value of a function to $this.
191
     *
192
     * @param PHP_CodeSniffer_File $phpcsFile
193
     * @param int $returnPtr
194
     *
195
     * @return void
196
     */
197 1
    private function fixMustReturnThis(PHP_CodeSniffer_File $phpcsFile, $returnPtr)
198
    {
199 1
        $returnSemicolonPtr = $phpcsFile->findEndOfStatement($returnPtr);
200
201 1
        for ($i = $returnPtr + 1; $i < $returnSemicolonPtr; $i++) {
202 1
            $phpcsFile->fixer->replaceToken($i, '');
203
        }
204
205 1
        $phpcsFile->fixer->beginChangeset();
206 1
        $phpcsFile->fixer->addContentBefore(
207
            $returnSemicolonPtr,
208 1
            ' $this'
209
        );
210 1
        $phpcsFile->fixer->endChangeset();
211 1
    }
212
}
213