Passed
Push — master ( 480623...7f2bbe )
by Maximilian
02:52
created

ArrayDoubleArrowAlignmentSniff::getPreviousComma()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 3
dl 0
loc 20
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the mo4-coding-standard (phpcs standard)
5
 *
6
 * @author  Xaver Loppenstedt <[email protected]>
7
 * @license http://spdx.org/licenses/MIT MIT License
8
 * @link    https://github.com/mayflower/mo4-coding-standard
9
 */
10
namespace MO4\Sniffs\Arrays;
11
12
use PHP_CodeSniffer\Files\File;
13
use PHP_CodeSniffer\Sniffs\Sniff;
14
use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens;
15
16
/**
17
 * Array Double Arrow Alignment sniff.
18
 *
19
 * '=>' must be aligned in arrays, and the key and the '=>' must be in the same line
20
 *
21
 * @author    Xaver Loppenstedt <[email protected]>
22
 * @copyright 2013 Xaver Loppenstedt, some rights reserved.
23
 * @license   http://spdx.org/licenses/MIT MIT License
24
 * @link      https://github.com/mayflower/mo4-coding-standard
25
 */
26
class ArrayDoubleArrowAlignmentSniff implements Sniff
27
{
28
    /**
29
     * Define all types of arrays.
30
     *
31
     * @var array
32
     */
33
    protected  $arrayTokens = [
34
        T_ARRAY,
35
        T_OPEN_SHORT_ARRAY,
36
    ];
37
38
39
    /**
40
     * Registers the tokens that this sniff wants to listen for.
41
     *
42
     * @return array<int, int>
43
     * @see    Tokens.php
44
     */
45
    public function register(): array
46
    {
47
        return $this->arrayTokens;
48
49
    }//end register()
50
51
52
    /**
53
     * Processes this test, when one of its tokens is encountered.
54
     *
55
     * @param File $phpcsFile The file being scanned.
56
     * @param int  $stackPtr  The position of the current token in
57
     *                        the stack passed in $tokens.
58
     *
59
     * @return void
60
     */
61
    public function process(File $phpcsFile, $stackPtr): void
62
    {
63
        $tokens  = $phpcsFile->getTokens();
64
        $current = $tokens[$stackPtr];
65
66
        if ($current['code'] === T_ARRAY) {
67
            $start = $current['parenthesis_opener'];
68
            $end   = $current['parenthesis_closer'];
69
        } else {
70
            $start = $current['bracket_opener'];
71
            $end   = $current['bracket_closer'];
72
        }
73
74
        if ($tokens[$start]['line'] === $tokens[$end]['line']) {
75
            return;
76
        }
77
78
        $assignments  = [];
79
        $keyEndColumn = -1;
80
        $lastLine     = -1;
81
82
        for ($i = ($start + 1); $i < $end; $i++) {
83
            $current  = $tokens[$i];
84
            $previous = $tokens[($i - 1)];
85
86
            // Skip nested arrays.
87
            if (\in_array($current['code'], $this->arrayTokens, true) === true) {
88
                if ($current['code'] === T_ARRAY) {
89
                    $i = ($current['parenthesis_closer'] + 1);
90
                } else {
91
                    $i = ($current['bracket_closer'] + 1);
92
                }
93
94
                continue;
95
            }
96
97
            // Skip closures in array.
98
            if ($current['code'] === T_CLOSURE) {
99
                $i = ($current['scope_closer'] + 1);
100
                continue;
101
            }
102
103
            if ($current['code'] === T_DOUBLE_ARROW) {
104
                $assignments[] = $i;
105
                $column        = $previous['column'];
106
                $line          = $current['line'];
107
108
                if ($lastLine === $line) {
109
                    $previousComma = $this->getPreviousComma($phpcsFile, $i, $start);
110
111
                    $msg = 'only one "=>" assignments per line is allowed in a multi line array';
112
113
                    if ($previousComma !== false) {
114
                        $fixable = $phpcsFile->addFixableError($msg, $i, 'OneAssignmentPerLine');
115
116
                        if ($fixable === true) {
117
                            $phpcsFile->fixer->beginChangeset();
118
                            $phpcsFile->fixer->addNewline((int) $previousComma);
119
                            $phpcsFile->fixer->endChangeset();
120
                        }
121
                    } else {
122
                        // Remove current and previous '=>' from array for further processing.
123
                        array_pop($assignments);
124
                        array_pop($assignments);
125
                        $phpcsFile->addError($msg, $i, 'OneAssignmentPerLine');
126
                    }
127
                }
128
129
                $hasKeyInLine = false;
130
131
                $j = ((int) $i - 1);
132
                while (($j >= 0) && ($tokens[$j]['line'] === $current['line'])) {
133
                    if (\in_array($tokens[$j]['code'], PHP_CodeSniffer_Tokens::$emptyTokens, true) === false) {
134
                        $hasKeyInLine = true;
135
                    }
136
137
                    $j--;
138
                }
139
140
                if ($hasKeyInLine === false) {
141
                    $fixable = $phpcsFile->addFixableError(
142
                        'in arrays, keys and "=>" must be on the same line',
143
                        $i,
144
                        'KeyAndValueNotOnSameLine'
145
                    );
146
147
                    if ($fixable === true) {
148
                        $phpcsFile->fixer->beginChangeset();
149
                        $phpcsFile->fixer->replaceToken($j, '');
150
                        $phpcsFile->fixer->endChangeset();
151
                    }
152
                }
153
154
                if ($column > $keyEndColumn) {
155
                    $keyEndColumn = $column;
156
                }
157
158
                $lastLine = $line;
159
            }//end if
160
        }//end for
161
162
        $doubleArrowStartColumn = ($keyEndColumn + 1);
163
164
        foreach ($assignments as $ptr) {
165
            $current = $tokens[$ptr];
166
            $column  = $current['column'];
167
168
            $beforeArrowPtr = ((int) $ptr - 1);
169
            $currentIndent  = \strlen($tokens[$beforeArrowPtr]['content']);
170
            $correctIndent  = (int) ($currentIndent - $column + $doubleArrowStartColumn);
171
            if ($column !== $doubleArrowStartColumn) {
172
                $fixable = $phpcsFile->addFixableError("each \"=>\" assignments must be aligned; current indentation before \"=>\" are $currentIndent space(s), must be $correctIndent space(s)", (int) $ptr, 'AssignmentsNotAligned');
173
174
                if ($fixable === false) {
175
                    continue;
176
                }
177
178
                $phpcsFile->fixer->beginChangeset();
179
                if ($tokens[$beforeArrowPtr]['code'] === T_WHITESPACE) {
180
                    $phpcsFile->fixer->replaceToken($beforeArrowPtr, str_repeat(' ', $correctIndent));
181
                } else {
182
                    $phpcsFile->fixer->addContent($beforeArrowPtr, str_repeat(' ', $correctIndent));
183
                }
184
185
                $phpcsFile->fixer->endChangeset();
186
            }
187
        }//end foreach
188
189
    }//end process()
190
191
192
    /**
193
     * Find previous comma in array.
194
     *
195
     * @param File $phpcsFile The file being scanned.
196
     * @param int  $stackPtr  The position of the current token in
197
     *                        the stack passed in $tokens.
198
     * @param int  $start     Start of the array
199
     *
200
     * @return bool|int
201
     */
202
    private function getPreviousComma(File $phpcsFile, $stackPtr, $start)
203
    {
204
        $previousComma = false;
205
        $tokens        = $phpcsFile->getTokens();
206
207
        $ptr = $phpcsFile->findPrevious([T_COMMA, T_CLOSE_SHORT_ARRAY], $stackPtr, $start);
208
        while ($ptr !== false) {
209
            if ($tokens[$ptr]['code'] === T_COMMA) {
210
                $previousComma = $ptr;
211
                break;
212
            }
213
214
            if ($tokens[$ptr]['code'] === T_CLOSE_SHORT_ARRAY) {
215
                $ptr = $tokens[$ptr]['bracket_opener'];
216
            }
217
218
            $ptr = $phpcsFile->findPrevious([T_COMMA, T_CLOSE_SHORT_ARRAY], ($ptr - 1), $start);
219
        }
220
221
        return $previousComma;
222
223
    }//end getPreviousComma()
224
225
226
}//end class
227