Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ValidGlobalNameSniff
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 2
156
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 / 40
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 * Verify MediaWiki global variable naming convention.
4 * A global name must be prefixed with 'wg'.
5 */
6
7namespace MediaWiki\Sniffs\NamingConventions;
8
9use PHP_CodeSniffer\Files\File;
10use PHP_CodeSniffer\Sniffs\Sniff;
11
12class ValidGlobalNameSniff implements Sniff {
13
14    /**
15     * https://php.net/manual/en/reserved.variables.argv.php
16     */
17    private const PHP_RESERVED = [
18        '$GLOBALS',
19        '$_SERVER',
20        '$_GET',
21        '$_POST',
22        '$_FILES',
23        '$_REQUEST',
24        '$_SESSION',
25        '$_ENV',
26        '$_COOKIE',
27        '$php_errormsg',
28        '$HTTP_RAW_POST_DATA',
29        '$http_response_header',
30        '$argc',
31        '$argv'
32    ];
33
34    /**
35     * A list of global variable prefixes allowed.
36     *
37     * @var array
38     */
39    public array $allowedPrefixes = [ 'wg' ];
40
41    /** @var string[] */
42    public array $ignoreList = [];
43
44    /**
45     * @inheritDoc
46     */
47    public function register(): array {
48        return [ T_GLOBAL ];
49    }
50
51    /**
52     * @param File $phpcsFile
53     * @param int $stackPtr The current token index.
54     * @return int|void
55     */
56    public function process( File $phpcsFile, $stackPtr ) {
57        // If there are no prefixes specified, we have nothing to do for this file
58        if ( $this->allowedPrefixes === [] ) {
59            // @codeCoverageIgnoreStart
60            return $phpcsFile->numTokens;
61            // @codeCoverageIgnoreEnd
62        }
63
64        $tokens = $phpcsFile->getTokens();
65
66        $nameIndex = $phpcsFile->findNext( T_VARIABLE, $stackPtr + 1 );
67        if ( !$nameIndex ) {
68            // Avoid possibly running in an endless loop below
69            return;
70        }
71
72        // Note: This requires at least 1 character after the prefix
73        $allowedPrefixesPattern = '/^\$(?:' . implode( '|', $this->allowedPrefixes ) . ')(.)/';
74        $semicolonIndex = $phpcsFile->findNext( T_SEMICOLON, $stackPtr + 1 );
75
76        while ( $nameIndex < $semicolonIndex ) {
77            // Note, this skips dynamic identifiers.
78            if ( $tokens[$nameIndex ]['code'] === T_VARIABLE && $tokens[$nameIndex - 1]['code'] !== T_DOLLAR ) {
79                $globalName = $tokens[$nameIndex]['content'];
80
81                if ( in_array( $globalName, $this->ignoreList ) ||
82                    in_array( $globalName, self::PHP_RESERVED )
83                ) {
84                    // need to manually increment $nameIndex here since
85                    // we won't reach the line at the end that does it
86                    $nameIndex++;
87                    continue;
88                }
89
90                // Determine if a simple error message can be used
91
92                if ( count( $this->allowedPrefixes ) === 1 ) {
93                    // Skip '$' and forge a valid global variable name
94                    $expected = '"$' . $this->allowedPrefixes[0] . ucfirst( substr( $globalName, 1 ) ) . '"';
95
96                    // Build message telling you the allowed prefix
97                    $allowedPrefix = '\'' . $this->allowedPrefixes[0] . '\'';
98                } else {
99                    // We already checked for an empty set of allowed prefixes earlier,
100                    // so if the count is not 1 them it must be multiple;
101                    // build a list of forged valid global variable names
102                    $expected = 'one of "$'
103                        . implode( ucfirst( substr( $globalName, 1 ) . '", "$' ), $this->allowedPrefixes )
104                        . ucfirst( substr( $globalName, 1 ) )
105                        . '"';
106
107                    // Build message telling you which prefixes are allowed
108                    $allowedPrefix = 'one of \''
109                        . implode( '\', \'', $this->allowedPrefixes )
110                        . '\'';
111                }
112
113                // Verify global is prefixed with an allowed prefix
114                $isAllowed = preg_match( $allowedPrefixesPattern, $globalName, $matches );
115                if ( !$isAllowed ) {
116                    $phpcsFile->addError(
117                        'Global variable "%s" is lacking an allowed prefix (%s). Should be %s.',
118                        $stackPtr,
119                        'allowedPrefix',
120                        [ $globalName, $allowedPrefix, $expected ]
121                    );
122                } elseif ( ctype_lower( $matches[1] ) ) {
123                    $phpcsFile->addError(
124                        'Global variable "%s" should use CamelCase: %s',
125                        $stackPtr,
126                        'CamelCase',
127                        [ $globalName, $expected ]
128                    );
129                }
130            }
131            $nameIndex++;
132        }
133    }
134}