Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpaceyParenthesisSniff
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 4
1190
0.00% covered (danger)
0.00%
0 / 1
 register
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 process
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
182
 processOpenParenthesis
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
90
 processCloseParenthesis
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 * Make sure calling functions is spacey:
4 * $this->foo( $arg, $arg2 );
5 * wfFoo( $arg, $arg2 );
6 *
7 * But, wfFoo() is ok.
8 *
9 * Also disallow wfFoo( ) and wfFoo(  $param )
10 */
11
12namespace MediaWiki\Sniffs\WhiteSpace;
13
14use PHP_CodeSniffer\Files\File;
15use PHP_CodeSniffer\Sniffs\Sniff;
16use PHP_CodeSniffer\Util\Tokens;
17
18class SpaceyParenthesisSniff implements Sniff {
19
20    /**
21     * @inheritDoc
22     */
23    public function register(): array {
24        return [
25            T_OPEN_PARENTHESIS,
26            T_CLOSE_PARENTHESIS,
27            T_OPEN_SHORT_ARRAY,
28            T_CLOSE_SHORT_ARRAY
29        ];
30    }
31
32    /**
33     * @param File $phpcsFile
34     * @param int $stackPtr The current token index.
35     * @return void|int
36     */
37    public function process( File $phpcsFile, $stackPtr ) {
38        $tokens = $phpcsFile->getTokens();
39        $currentToken = $tokens[$stackPtr];
40        $closer = $currentToken['parenthesis_closer'] ?? $currentToken['bracket_closer'] ?? null;
41
42        if ( !$closer ) {
43            // Syntax error or live coding, bow out.
44            return;
45        }
46
47        if ( $closer === $stackPtr ) {
48            $this->processCloseParenthesis( $phpcsFile, $stackPtr );
49            return;
50        }
51
52        if ( $tokens[$stackPtr - 1]['code'] === T_WHITESPACE
53            && ( $tokens[$stackPtr - 2]['code'] === T_STRING
54                || $tokens[$stackPtr - 2]['code'] === T_ARRAY )
55        ) {
56            // String followed by whitespace followed by
57            // opening brace is probably a function call.
58            $bracketType = $tokens[$stackPtr - 2]['code'] === T_STRING
59                ? 'parenthesis of function call'
60                : 'bracket of array';
61            $fix = $phpcsFile->addFixableWarning(
62                'Space found before opening %s',
63                $stackPtr - 1,
64                'SpaceBeforeOpeningParenthesis',
65                [ $bracketType ]
66            );
67            if ( $fix ) {
68                $phpcsFile->fixer->replaceToken( $stackPtr - 1, '' );
69            }
70        }
71
72        // Shorten out as early as possible on empty parenthesis
73        if ( $closer === $stackPtr + 1 ) {
74            // Intentionally do not process the closing parenthesis again
75            return $stackPtr + 2;
76        }
77
78        // Check for space between parentheses without any arguments
79        if ( $closer === $stackPtr + 2 && $tokens[$stackPtr + 1]['code'] === T_WHITESPACE ) {
80            $bracketType = $currentToken['code'] === T_OPEN_PARENTHESIS ? 'parentheses' : 'brackets';
81            $fix = $phpcsFile->addFixableWarning(
82                'Unnecessary space found within %s',
83                $stackPtr + 1,
84                'UnnecessarySpaceBetweenParentheses',
85                [ $bracketType ]
86            );
87            if ( $fix ) {
88                $phpcsFile->fixer->replaceToken( $stackPtr + 1, '' );
89            }
90
91            // Intentionally do not process the closing parenthesis again
92            return $stackPtr + 3;
93        }
94
95        $this->processOpenParenthesis( $phpcsFile, $stackPtr );
96    }
97
98    /**
99     * @param File $phpcsFile
100     * @param int $stackPtr The current token index.
101     */
102    protected function processOpenParenthesis( File $phpcsFile, int $stackPtr ): void {
103        $tokens = $phpcsFile->getTokens();
104        $nextToken = $tokens[$stackPtr + 1];
105        // No space or not single space
106        if ( ( $nextToken['code'] === T_WHITESPACE &&
107                $nextToken['line'] === $tokens[$stackPtr + 2]['line']
108                && $nextToken['content'] !== ' ' )
109            || $nextToken['code'] !== T_WHITESPACE
110        ) {
111            $fix = $phpcsFile->addFixableWarning(
112                'Single space expected after opening parenthesis',
113                $stackPtr + 1,
114                'SingleSpaceAfterOpenParenthesis'
115            );
116            if ( $fix ) {
117                if ( $nextToken['code'] === T_WHITESPACE
118                    && $nextToken['line'] === $tokens[$stackPtr + 2]['line']
119                    && $nextToken['content'] !== ' '
120                ) {
121                    $phpcsFile->fixer->replaceToken( $stackPtr + 1, ' ' );
122                } else {
123                    $phpcsFile->fixer->addContent( $stackPtr, ' ' );
124                }
125            }
126        }
127    }
128
129    /**
130     * @param File $phpcsFile
131     * @param int $stackPtr The current token index.
132     */
133    protected function processCloseParenthesis( File $phpcsFile, int $stackPtr ): void {
134        $tokens = $phpcsFile->getTokens();
135        $previousToken = $tokens[$stackPtr - 1];
136
137        if ( ( $previousToken['code'] === T_WHITESPACE
138                && $previousToken['content'] === ' ' )
139            || ( isset( Tokens::$commentTokens[ $previousToken['code'] ] )
140                && str_ends_with( $previousToken['content'], "\n" ) )
141        ) {
142            // If previous token was
143            // '(' or ' ' or a comment ending with a newline
144            return;
145        }
146
147        // If any of the whitespace tokens immediately before this token is a newline
148        $ptr = $stackPtr - 1;
149        while ( $tokens[$ptr]['code'] === T_WHITESPACE ) {
150            if ( $tokens[$ptr]['content'] === $phpcsFile->eolChar ) {
151                return;
152            }
153            $ptr--;
154        }
155
156        // If the comment before all the whitespaces immediately preceding the ')' ends with a newline
157        if ( isset( Tokens::$commentTokens[ $tokens[$ptr]['code'] ] )
158            && str_ends_with( $tokens[$ptr]['content'], "\n" )
159        ) {
160            return;
161        }
162
163        $fix = $phpcsFile->addFixableWarning(
164            'Single space expected before closing parenthesis',
165            $stackPtr,
166            'SingleSpaceBeforeCloseParenthesis'
167        );
168        if ( $fix ) {
169            if ( $previousToken['code'] === T_WHITESPACE ) {
170                $phpcsFile->fixer->replaceToken( $stackPtr - 1, ' ' );
171            } else {
172                $phpcsFile->fixer->addContentBefore( $stackPtr, ' ' );
173            }
174        }
175    }
176}