Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
UnusedGlobalVariablesSniff
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 2
420
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 / 39
0.00% covered (danger)
0.00%
0 / 1
380
1<?php
2/**
3 * Detect unused MediaWiki global variable.
4 * Unused global variables should be removed.
5 */
6
7namespace MediaWiki\Sniffs\VariableAnalysis;
8
9use PHP_CodeSniffer\Files\File;
10use PHP_CodeSniffer\Sniffs\Sniff;
11
12class UnusedGlobalVariablesSniff implements Sniff {
13
14    /**
15     * @inheritDoc
16     */
17    public function register(): array {
18        return [ T_FUNCTION, T_CLOSURE ];
19    }
20
21    /**
22     * @param File $phpcsFile
23     * @param int $stackPtr The current token index.
24     * @return void
25     */
26    public function process( File $phpcsFile, $stackPtr ) {
27        $tokens = $phpcsFile->getTokens();
28        if ( !isset( $tokens[$stackPtr]['scope_opener'] ) ) {
29            // An interface or abstract function which doesn't have a body
30            return;
31        }
32
33        $scopeOpener = $tokens[$stackPtr]['scope_opener'] + 1;
34        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
35
36        $endOfGlobal = 0;
37        $globalVariables = [];
38        $matches = [];
39        $delayedSkip = [];
40
41        for ( $i = $scopeOpener; $i < $scopeCloser; $i++ ) {
42            // Process a delayed skip
43            if ( isset( $delayedSkip[$i] ) ) {
44                $i = $delayedSkip[$i];
45                continue;
46            }
47            $code = $tokens[$i]['code'];
48            if ( ( $code === T_CLOSURE || $code === T_FUNCTION || $code === T_ANON_CLASS )
49                && isset( $tokens[$i]['scope_closer'] )
50            ) {
51                if ( $code === T_CLOSURE && isset( $tokens[$i]['parenthesis_closer'] ) ) {
52                    // Cannot skip directly to the end of closure
53                    // The use statement needs to be processed
54                    $delayedSkip[$tokens[$i]['scope_opener']] = $tokens[$i]['scope_closer'];
55
56                    // Skip the argument list of the closure
57                    $i = $tokens[$i]['parenthesis_closer'];
58                } else {
59                    // Skip to the end of the inner function/anon class and continue
60                    $i = $tokens[$i]['scope_closer'];
61                }
62                continue;
63            }
64
65            if ( $code === T_GLOBAL ) {
66                $endOfGlobal = $phpcsFile->findEndOfStatement( $i, T_COMMA );
67            } elseif ( $code === T_VARIABLE && $tokens[$i - 1]['code'] !== T_DOLLAR ) {
68                // Note, this skips dynamic variable names.
69                $variableName = $tokens[$i]['content'];
70                if ( $i < $endOfGlobal ) {
71                    $globalVariables[$variableName] = $i;
72                } else {
73                    unset( $globalVariables[$variableName] );
74                }
75            } elseif ( ( $code === T_DOUBLE_QUOTED_STRING || $code === T_HEREDOC )
76                // Avoid the regex below when there are no globals to look for anyway
77                && $globalVariables
78            ) {
79                preg_match_all( '/\${?(\w+)/', $tokens[$i]['content'], $matches );
80                foreach ( $matches[1] as $variableName ) {
81                    unset( $globalVariables[ '$' . $variableName ] );
82                }
83            }
84        }
85
86        foreach ( $globalVariables as $variableName => $variableIndex ) {
87            $phpcsFile->addWarning(
88                'Global %s is never used.',
89                $variableIndex,
90                'UnusedGlobal' . $variableName,
91                [ $variableName ]
92            );
93        }
94    }
95}