Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
TrailingCommaSniff
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 3
272
0.00% covered (danger)
0.00%
0 / 1
 register
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 process
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
72
 checkWarnAndFix
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22namespace MediaWiki\Sniffs\Arrays;
23
24use PHP_CodeSniffer\Files\File;
25use PHP_CodeSniffer\Sniffs\Sniff;
26use PHP_CodeSniffer\Util\Tokens;
27
28/**
29 * Sniff to enforce the presence or absence of trailing commas in single- and multi-line arrays.
30 *
31 * By default, this sniff does nothing.
32 * Configure it as follows to enforce trailing commas in multi-line arrays
33 * while removing them in single-line arrays:
34 * <rule ref="MediaWiki.Arrays.TrailingComma">
35 *     <properties>
36 *         <property name="singleLine" value="false" />
37 *         <property name="multiLine" value="true" />
38 *     </properties>
39 * </rule>
40 */
41class TrailingCommaSniff implements Sniff {
42
43    /**
44     * Enforce the presence (true) or absence (false) of trailing commas in single-line arrays.
45     * By default (null), do nothing.
46     * @var bool|null
47     */
48    public ?bool $singleLine = null;
49
50    /**
51     * Enforce the presence (true) or absence (false) of trailing commas in multi-line arrays.
52     * By default (null), do nothing.
53     * @var bool|null
54     */
55    public ?bool $multiLine = null;
56
57    public function register(): array {
58        return [ T_CLOSE_SHORT_ARRAY ];
59    }
60
61    /**
62     * @param File $phpcsFile
63     * @param int $stackPtr The current token index.
64     * @return void|int
65     */
66    public function process( File $phpcsFile, $stackPtr ) {
67        if ( $this->singleLine === null && $this->multiLine === null ) {
68            // not configured to do anything, skip to end of file
69            return $phpcsFile->numTokens;
70        }
71
72        $tokens = $phpcsFile->getTokens();
73
74        $lastContentToken = $phpcsFile->findPrevious(
75            Tokens::$emptyTokens,
76            $stackPtr - 1,
77            null,
78            true
79        );
80
81        $isEmptyArray = $tokens[$lastContentToken]['code'] === T_OPEN_SHORT_ARRAY;
82        if ( $isEmptyArray ) {
83            // PHP syntax doesn't allow [,] so we can stop early
84            return;
85        }
86
87        $isMultiline = false;
88        for ( $token = $lastContentToken + 1; $token < $stackPtr; $token++ ) {
89            if ( str_contains( $tokens[$token]['content'], "\n" ) ) {
90                $isMultiline = true;
91                break;
92            }
93        }
94
95        if ( $isMultiline ) {
96            $wantTrailingComma = $this->multiLine;
97        } else {
98            $wantTrailingComma = $this->singleLine;
99        }
100        if ( $wantTrailingComma === null ) {
101            return;
102        }
103
104        $hasTrailingComma = $tokens[$lastContentToken]['code'] === T_COMMA;
105
106        $this->checkWarnAndFix(
107            $phpcsFile,
108            $lastContentToken,
109            $hasTrailingComma,
110            $wantTrailingComma,
111            $isMultiline
112        );
113    }
114
115    /**
116     * Check whether a warning should be emitted,
117     * emit one if necessary, and fix it if requested.
118     *
119     * @param File $phpcsFile
120     * @param int $token
121     * @param bool $hasTrailingComma Whether the trailing comma *is* present.
122     * @param bool $wantTrailingComma Whether the trailing comma *should* be present.
123     * @param bool $isMultiline Whether the array is multi-line or single-line.
124     * (Only used for the warning message and code at this point.)
125     */
126    private function checkWarnAndFix(
127        File $phpcsFile,
128        int $token,
129        bool $hasTrailingComma,
130        bool $wantTrailingComma,
131        bool $isMultiline
132    ): void {
133        if ( $hasTrailingComma === $wantTrailingComma ) {
134            return;
135        }
136
137        // possible messages (for grepping):
138        // Multi-line array with trailing comma
139        // Multi-line array without trailing comma
140        // Single-line array with trailing comma
141        // Single-line array without trailing comma
142        $fix = $phpcsFile->addFixableWarning(
143            '%s array %s trailing comma',
144            $token,
145            $isMultiline ? 'MultiLine' : 'SingleLine',
146            [
147                $isMultiline ? 'Multi-line' : 'Single-line',
148                $wantTrailingComma ? 'without' : 'with',
149            ]
150        );
151        if ( !$fix ) {
152            return;
153        }
154
155        // adding/removing the trailing comma works the same regardless of $isMultiline
156        if ( $wantTrailingComma ) {
157            $phpcsFile->fixer->addContent( $token, ',' );
158        } else {
159            $phpcsFile->fixer->replaceToken( $token, '' );
160        }
161    }
162
163}