Completed
Push — feature/issue-367-phpcs-3.x-co... ( e185aa )
by Juliette
01:53
created

NewNowdocQuotedHeredocSniff::process()   F

Complexity

Conditions 23
Paths 246

Size

Total Lines 124
Code Lines 55

Duplication

Lines 7
Ratio 5.65 %

Importance

Changes 0
Metric Value
dl 7
loc 124
rs 3.6435
c 0
b 0
f 0
cc 23
eloc 55
nc 246
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\PHP\NewNowdocQuotedHeredocSniff.
4
 *
5
 * PHP version 5.3
6
 *
7
 * @category PHP
8
 * @package  PHPCompatibility
9
 * @author   Juliette Reinders Folmer <[email protected]>
10
 */
11
12
namespace PHPCompatibility\Sniffs\PHP;
13
14
use PHPCompatibility\Sniff;
15
16
/**
17
 * \PHPCompatibility\Sniffs\PHP\NewNowdocQuotedHeredocSniff.
18
 *
19
 * PHP 5.3 introduces Nowdoc syntax and (double) quoted identifiers for heredocs.
20
 *
21
 * @category PHP
22
 * @package  PHPCompatibility
23
 * @author   Juliette Reinders Folmer <[email protected]>
24
 */
25
class NewNowdocQuotedHeredocSniff extends Sniff
26
{
27
28
    /**
29
     * Returns an array of tokens this test wants to listen for.
30
     *
31
     * @return array
32
     */
33
    public function register()
34
    {
35
        $targets = array();
36
37
        if (version_compare(PHP_VERSION, '5.3', '<') === true) {
38
            $targets[] = T_SL;
39
        }
40
41
        if (defined('T_START_NOWDOC')) {
42
            $targets[] = constant('T_START_NOWDOC');
43
        }
44
        if (defined('T_END_NOWDOC')) {
45
            $targets[] = constant('T_END_NOWDOC');
46
        }
47
48
        $targets[] = T_START_HEREDOC;
49
50
        return $targets;
51
52
    }//end register()
53
54
55
    /**
56
     * Processes this test, when one of its tokens is encountered.
57
     *
58
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
59
     * @param int                   $stackPtr  The position of the current token in
60
     *                                         the stack passed in $tokens.
61
     *
62
     * @return int|void On older PHP versions passes a pointer to the nowdoc/heredoc closer
63
     *                  to skip passed anything in between in regards to processing
64
     *                  the file for this sniff.
65
     */
66
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
67
    {
68
        if ($this->supportsBelow('5.2') === false) {
69
            return;
70
        }
71
72
        $tokens    = $phpcsFile->getTokens();
73
        $isNowdoc  = false;
74
        $isHeredoc = false;
75
76
        switch ($tokens[$stackPtr]['type']) {
77
            case 'T_START_NOWDOC':
78
            case 'T_END_NOWDOC':
79
                $isNowdoc = true;
80
                break;
81
82
            case 'T_START_HEREDOC':
83
                /*
84
                 * If we have a heredoc opener, make sure we only report on double quoted identifiers.
85
                 * A double quoted identifier will have the opening quote on position 3 in the string:
86
                 * `<<<"ID"`
87
                 */
88
                if ($tokens[$stackPtr]['content'][3] !== '"') {
89
                    return;
90
                }
91
92
                $isHeredoc = true;
93
                break;
94
        }
95
96
        /*
97
         * In PHP 5.2 the T_NOWDOC and the quoted T_HEREDOC tokens aren't recognized yet
98
         * and PHPCS does not backfill for it, so we have to sniff for a specific
99
         * combination of tokens.
100
         */
101
        if ($tokens[$stackPtr]['code'] === T_SL) {
102
            /*
103
             * Check for the right token combination.
104
             */
105 View Code Duplication
            if (isset($tokens[($stackPtr + 1)]) === false || $tokens[($stackPtr + 1)]['code'] !== T_LESS_THAN) {
1 ignored issue
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...
106
                return;
107
            }
108
109 View Code Duplication
            if (isset($tokens[($stackPtr + 2)]) === false
1 ignored issue
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...
110
                || $tokens[($stackPtr + 2)]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
111
                return;
112
            }
113
114
            /*
115
             * Heredoc and nowdoc naming rules:
116
             * "it must contain only alphanumeric characters and underscores and
117
             *  must start with a non-digit character or underscore"
118
             * @link http://php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
119
             */
120
            if (preg_match('`^(["\'])([a-z][a-z0-9_]*)\1$`iD', $tokens[($stackPtr + 2)]['content'], $matches) < 1) {
121
                return;
122
            }
123
124
            // Remember whether we found a nowdoc or a heredoc.
125
            switch ($matches[1]) {
126
                case "'":
127
                    $isNowdoc = true;
128
                    break;
129
130
                case '"':
131
                    $isHeredoc = true;
132
                    break;
133
            }
134
135
136
            /*
137
             * See if we can find the nowdoc/heredoc closer.
138
             */
139
            $closer = null;
140
141
            for ($i = ($stackPtr + 3); $i < $phpcsFile->numTokens; $i++) {
142
                $maybeCloser = $phpcsFile->findNext(T_STRING, $i, null, false, $matches[2]);
143
                if ($maybeCloser === false) {
144
                    return;
145
                }
146
147
                // The closing identifier must begin in the first column of the line.
148
                if ($tokens[$maybeCloser]['column'] !== 1) {
149
                    continue;
150
                }
151
152
                // The closing identifier must be the only content on that line, except for maybe a semi-colon.
153
                $next = $phpcsFile->findNext(array(T_WHITESPACE, T_SEMICOLON), ($maybeCloser + 1), null, true);
154
                if ($tokens[$maybeCloser]['line'] === $tokens[$next]['line']) {
155
                    continue;
156
                }
157
158
                $closer = $maybeCloser;
159
                break;
160
            }
161
162
            if (isset($closer) === false) {
163
                // No valid closer found.
164
                return;
165
            }
166
        }
167
168
        if ($isNowdoc === true) {
169
            $error = 'Nowdocs are not present in PHP version 5.2 or earlier.';
170
            $phpcsFile->addError($error, $stackPtr, 'Found');
171
172
            if (isset($closer) !== false) {
173
                // Also throw an error for the closer on older PHP versions and skip forward past it.
174
                // Not needed for newer PHP versions as those will trigger this sniff for the closer token itself.
175
                $phpcsFile->addError($error, $closer, 'Found');
0 ignored issues
show
Bug introduced by
The variable $closer does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
176
                return ($closer + 1);
177
            }
178
        }
179
180
        if ($isHeredoc === true) {
181
            // Only throw an error for the opener.
182
            $phpcsFile->addError('The Heredoc identifier may not be enclosed in (double) quotes in PHP version 5.2 or earlier.', $stackPtr, 'Found');
183
184
            if (isset($closer) !== false) {
185
                return ($closer + 1);
186
            }
187
        }
188
189
    }//end process()
190
191
}//end class
192