Test Failed
Pull Request — master (#25)
by Michel
02:51
created

FluentSetterSniff   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 215
Duplicated Lines 13.02 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 96.77%

Importance

Changes 0
Metric Value
wmc 13
lcom 1
cbo 3
dl 28
loc 215
rs 10
c 0
b 0
f 0
ccs 60
cts 62
cp 0.9677

6 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
A processTokenOutsideScope() 0 4 1

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\Files\File;
8
use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
9
10
/**
11
 * Class FluentSetterSniff
12
 *
13
 * @package BestIt\Sniffs\Functions
14
 *
15
 * @author Nick Lubisch <[email protected]>
16
 */
17
class FluentSetterSniff extends 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
     * Processes the tokens that this test is listening for.
78
     *
79 4
     * @param File $phpcsFile The file where this token was found.
80
     * @param int $stackPtr The position in the stack where this token was found.
81
     * @param int $currScope The position in the tokens array that opened the scope that this test is listening for.
82
     *
83
     * @return void
84 4
     */
85 4
    protected function processTokenWithinScope(
86
        File $phpcsFile,
87
        $stackPtr,
88 4
        $currScope
89
    ) {
90
        $className = $phpcsFile->getDeclarationName($currScope);
91
        $methodName = $phpcsFile->getDeclarationName($stackPtr);
92 4
93
        if (!$this->checkIfSetterFunction($methodName)) {
94
            return;
95
        }
96 4
97 4
        $tokens = $phpcsFile->getTokens();
98
        $errorData = sprintf('%s::%s', $className, $methodName);
99 4
100 4
        $functionToken = $tokens[$stackPtr];
101 4
        $openBracePtr = $functionToken['scope_opener'];
102
        $closeBracePtr = $functionToken['scope_closer'];
103 4
104
        $returnPtr = $phpcsFile->findNext(T_RETURN, $openBracePtr, $closeBracePtr);
105 4
106 1 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...
107 1
            $fixNoReturnFound = $phpcsFile->addFixableError(
108
                self::ERROR_NO_RETURN_FOUND,
109 1
                $stackPtr,
110
                self::CODE_NO_RETURN_FOUND,
111
                $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...
112
            );
113 1
114 1
            if ($fixNoReturnFound) {
115
                $this->fixNoReturnFound($phpcsFile, $closeBracePtr);
116
            }
117 1
118
            return;
119
        }
120 4
121 4
        $nextReturnPtr = $phpcsFile->findNext(
122 4
            T_RETURN,
123
            $returnPtr + 1,
124
            $closeBracePtr
125
        );
126 4
127 1
        if ($nextReturnPtr !== false) {
128 1
            $phpcsFile->addError(
129
                self::ERROR_MULTIPLE_RETURN_FOUND,
130 1
                $stackPtr,
131
                self::CODE_MULTIPLE_RETURN_FOUND,
132
                $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...
133 1
            );
134
            return;
135
        }
136 3
137
        $thisVariablePtr = $phpcsFile->findNext(T_VARIABLE, $returnPtr, null, false, '$this', true);
138 3
139 1 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...
140 1
            $fixMustReturnThis = $phpcsFile->addFixableError(
141
                self::ERROR_MUST_RETURN_THIS,
142 1
                $stackPtr,
143
                self::CODE_MUST_RETURN_THIS,
144
                $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...
145
            );
146 1
147 1
            if ($fixMustReturnThis) {
148
                $this->fixMustReturnThis($phpcsFile, $returnPtr);
149
            }
150 1
151
            return;
152 3
        }
153
    }
154
155
    /**
156
     * Checks if the given method name relates to a setter function.
157
     *
158
     * Its not a simple strpos because a method name like "setupDatabase" would be catched.
159
     * We check that the letters until the first upper case character equals "set".
160
     * This way we expect that after "set" follows an upper case letter.
161
     *
162
     * @param string $methodName Current method name
163
     *
164
     * @return bool Indicator if the given method is a setter function
165 4
     */
166
    private function checkIfSetterFunction(string $methodName): bool
167 4
    {
168
        $firstMatch = strcspn($methodName, 'ABCDEFGHJIJKLMNOPQRSTUVWXYZ');
169 4
170
        return substr($methodName, 0, $firstMatch) === 'set';
171
    }
172
173
    /**
174
     * Fixes if no return statement is found.
175
     *
176
     * @param File $phpcsFile The php cs file
177
     * @param int $closingBracePtr Pointer to the closing curly brace of the function
178
     *
179
     * @return void
180 1
     */
181
    private function fixNoReturnFound(File $phpcsFile, int $closingBracePtr)
182 1
    {
183 1
        $tokens = $phpcsFile->getTokens();
184
        $closingBraceToken = $tokens[$closingBracePtr];
185 1
186
        $expectedReturnSpaces = str_repeat($this->identation, $closingBraceToken['level'] + 1);
187 1
188 1
        $phpcsFile->fixer->beginChangeset();
189 1
        $phpcsFile->fixer->addNewlineBefore($closingBracePtr - 1);
190 1
        $phpcsFile->fixer->addContentBefore($closingBracePtr - 1, $expectedReturnSpaces . 'return $this;');
191 1
        $phpcsFile->fixer->addNewlineBefore($closingBracePtr - 1);
192 1
        $phpcsFile->fixer->endChangeset();
193
    }
194
195
    /**
196
     * Fixes the return value of a function to $this.
197
     *
198
     * @param File $phpcsFile The php cs file
199
     * @param int $returnPtr Pointer to the return token
200
     *
201
     * @return void
202 1
     */
203
    private function fixMustReturnThis(File $phpcsFile, $returnPtr)
204 1
    {
205
        $returnSemicolonPtr = $phpcsFile->findEndOfStatement($returnPtr);
206 1
207 1
        for ($i = $returnPtr + 1; $i < $returnSemicolonPtr; $i++) {
208
            $phpcsFile->fixer->replaceToken($i, '');
209
        }
210 1
211 1
        $phpcsFile->fixer->beginChangeset();
212
        $phpcsFile->fixer->addContentBefore(
213 1
            $returnSemicolonPtr,
214
            ' $this'
215 1
        );
216 1
        $phpcsFile->fixer->endChangeset();
217
    }
218
219
    /**
220
     * Processes a token that is found outside the scope that this test is listening to.
221
     *
222
     * @param File $phpcsFile The php cs file
223
     * @param int $stackPtr Pointer to the token
224
     *
225
     * @return void
226
     */
227
    protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
228
    {
229
        // Currently not used.
230
    }
231
}
232