Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHPCSUtils / 285

11 Feb 2020 - 6:43 coverage: 97.696% (-0.002%) from 97.698%
285

Pull #80

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Documentation: various minor fixes
Pull Request #80: Documentation: various minor fixes

2290 of 2344 relevant lines covered (97.7%)

69.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.89
/PHPCSUtils/Utils/Namespaces.php
1
<?php
2
/**
3
 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4
 *
5
 * @package   PHPCSUtils
6
 * @copyright 2019-2020 PHPCSUtils Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSUtils
9
 */
10

11
namespace PHPCSUtils\Utils;
12

13
use PHP_CodeSniffer\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSUtils\BackCompat\BCFile;
17
use PHPCSUtils\Tokens\Collections;
18
use PHPCSUtils\Utils\Conditions;
19
use PHPCSUtils\Utils\GetTokensAsString;
20
use PHPCSUtils\Utils\Parentheses;
21

22
/**
23
 * Utility functions for use when examining T_NAMESPACE tokens and to determine the
24
 * namespace of arbitrary tokens.
25
 *
26
 * @since 1.0.0
27
 */
28
class Namespaces
29
{
30

31
    /**
32
     * Determine what a T_NAMESPACE token is used for.
33
     *
34
     * @since 1.0.0
35
     *
36
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
37
     * @param int                         $stackPtr  The position of the T_NAMESPACE token.
38
     *
39
     * @return string Either 'declaration', 'operator'.
40
     *                An empty string will be returned if it couldn't be
41
     *                reliably determined what the T_NAMESPACE token is used for,
42
     *                which will normally mean the code contains a parse/fatal error.
43
     *
44
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is
45
     *                                                      not a T_NAMESPACE token.
46
     */
47
    public static function getType(File $phpcsFile, $stackPtr)
48
    {
49
        $tokens = $phpcsFile->getTokens();
120×
50

51
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_NAMESPACE) {
120×
52
            throw new RuntimeException('$stackPtr must be of type T_NAMESPACE');
8×
53
        }
54

55
        $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
112×
56
        if ($next === false) {
112×
57
            // Live coding or parse error.
58
            return '';
8×
59
        }
60

61
        if (empty($tokens[$stackPtr]['conditions']) === false
104×
62
            || empty($tokens[$stackPtr]['nested_parenthesis']) === false
104×
63
        ) {
64
            /*
65
             * Namespace declarations are only allowed at top level, so this can definitely not
66
             * be a namespace declaration.
67
             */
68
            if ($tokens[$next]['code'] === \T_NS_SEPARATOR) {
40×
69
                return 'operator';
24×
70
            }
71

72
            return '';
16×
73
        }
74

75
        $start = BCFile::findStartOfStatement($phpcsFile, $stackPtr);
64×
76
        if ($start === $stackPtr
77
            && ($tokens[$next]['code'] === \T_STRING
52×
78
               || $tokens[$next]['code'] === \T_OPEN_CURLY_BRACKET)
52×
79
        ) {
80
            return 'declaration';
24×
81
        }
82

83
        if ($start !== $stackPtr
84
            && $tokens[$next]['code'] === \T_NS_SEPARATOR
40×
85
        ) {
86
            return 'operator';
16×
87
        }
88

89
        return '';
24×
90
    }
91

92
    /**
93
     * Determine whether a T_NAMESPACE token is the keyword for a namespace declaration.
94
     *
95
     * @since 1.0.0
96
     *
97
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
98
     * @param int                         $stackPtr  The position of a T_NAMESPACE token.
99
     *
100
     * @return bool True if the token passed is the keyword for a namespace declaration.
101
     *              False if not.
102
     *
103
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is
104
     *                                                      not a T_NAMESPACE token.
105
     */
106
    public static function isDeclaration(File $phpcsFile, $stackPtr)
107
    {
108
        return (self::getType($phpcsFile, $stackPtr) === 'declaration');
56×
109
    }
110

111
    /**
112
     * Determine whether a T_NAMESPACE token is used as an operator.
113
     *
114
     * @since 1.0.0
115
     *
116
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
117
     * @param int                         $stackPtr  The position of a T_NAMESPACE token.
118
     *
119
     * @return bool True if the token passed is used as an operator. False if not.
120
     *
121
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is
122
     *                                                      not a T_NAMESPACE token.
123
     */
124
    public static function isOperator(File $phpcsFile, $stackPtr)
125
    {
126
        return (self::getType($phpcsFile, $stackPtr) === 'operator');
56×
127
    }
128

129
    /**
130
     * Get the complete namespace name as declared.
131
     *
132
     * For hierarchical namespaces, the name will be composed of several tokens,
133
     * i.e. MyProject\Sub\Level which will be returned together as one string.
134
     *
135
     * @since 1.0.0
136
     *
137
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
138
     * @param int                         $stackPtr  The position of a T_NAMESPACE token.
139
     * @param bool                        $clean     Optional. Whether to get the name stripped
140
     *                                               of potentially interlaced whitespace and/or
141
     *                                               comments. Defaults to true.
142
     *
143
     * @return string|false The namespace name, or false if the specified position is not a
144
     *                      T_NAMESPACE token, the token points to a namespace operator
145
     *                      or when parse errors are encountered/during live coding.
146
     *                      Note: The name can be an empty string for a valid global
147
     *                      namespace declaration.
148
     */
149
    public static function getDeclaredName(File $phpcsFile, $stackPtr, $clean = true)
150
    {
151
        try {
152
            if (self::isDeclaration($phpcsFile, $stackPtr) === false) {
100×
153
                // Not a namespace declaration.
154
                return false;
56×
155
            }
156
        } catch (RuntimeException $e) {
44×
157
            // Non-existent token or not a namespace keyword token.
158
            return false;
4×
159
        }
160

161
        $endOfStatement = $phpcsFile->findNext(Collections::$namespaceDeclarationClosers, ($stackPtr + 1));
80×
162
        if ($endOfStatement === false) {
80×
163
            // Live coding or parse error.
164
            return false;
8×
165
        }
166

167
        $tokens = $phpcsFile->getTokens();
72×
168
        $next   = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), ($endOfStatement + 1), true);
72×
169
        if ($next === $endOfStatement) {
170
            // Declaration of global namespace. I.e.: namespace {}.
171
            // If not a scoped {} namespace declaration, no name/global declarations are invalid
172
            // and result in parse errors, but that's not our concern.
8×
173
            return '';
174
        }
175

64×
176
        if ($clean === false) {
32×
177
            return \trim(GetTokensAsString::origContent($phpcsFile, $next, ($endOfStatement - 1)));
178
        }
179

32×
180
        return \trim(GetTokensAsString::noEmpties($phpcsFile, $next, ($endOfStatement - 1)));
181
    }
182

183
    /**
184
     * Determine the namespace an arbitrary token lives in.
185
     *
186
     * Note: when a namespace declaration token or a token which is part of the namespace
187
     * name is passed to this method, the result will be false as technically, they are not
188
     * **within** a namespace.
189
     *
190
     * Note: this method has no opinion on whether the token passed is actually _subject_
191
     * to namespacing.
192
     *
193
     * @since 1.0.0
194
     *
195
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
196
     * @param int                         $stackPtr  The token for which to determine
197
     *                                               the namespace.
198
     *
199
     * @return int|false Token pointer to the applicable namespace keyword or
200
     *                   false if it couldn't be determined or no namespace applies.
201
     */
202
    public static function findNamespacePtr(File $phpcsFile, $stackPtr)
203
    {
124×
204
        $tokens = $phpcsFile->getTokens();
205

206
        // Check for the existence of the token.
124×
207
        if (isset($tokens[$stackPtr]) === false) {
4×
208
            return false;
209
        }
210

211
        // The namespace keyword in a namespace declaration is itself not namespaced.
120×
212
        if ($tokens[$stackPtr]['code'] === \T_NAMESPACE
120×
213
            && self::isDeclaration($phpcsFile, $stackPtr) === true
214
        ) {
4×
215
            return false;
216
        }
217

218
        // Check for scoped namespace {}.
120×
219
        $namespacePtr = Conditions::getCondition($phpcsFile, $stackPtr, \T_NAMESPACE);
120×
220
        if ($namespacePtr !== false) {
32×
221
            return $namespacePtr;
222
        }
223

224
        /*
225
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
226
         * Keeping in mind that:
227
         * - there can be multiple non-scoped namespaces in a file (bad practice, but is allowed);
228
         * - the namespace keyword can also be used as an operator;
229
         * - a non-named namespace resolves to the global namespace;
230
         * - and that namespace declarations can't be nested in anything, so we can skip over any
231
         *   nesting structures.
232
         */
233

234
        // Start by breaking out of any scoped structures this token is in.
88×
235
        $prev           = $stackPtr;
88×
236
        $firstCondition = Conditions::getFirstCondition($phpcsFile, $stackPtr);
88×
237
        if ($firstCondition !== false) {
48×
238
            $prev = $firstCondition;
239
        }
240

241
        // And break out of any surrounding parentheses as well.
88×
242
        $firstParensOpener = Parentheses::getFirstOpener($phpcsFile, $prev);
88×
243
        if ($firstParensOpener !== false) {
8×
244
            $prev = $firstParensOpener;
245
        }
246

247
        $find = [
88×
248
            \T_NAMESPACE,
249
            \T_CLOSE_CURLY_BRACKET,
250
            \T_CLOSE_PARENTHESIS,
251
            \T_CLOSE_SHORT_ARRAY,
252
            \T_CLOSE_SQUARE_BRACKET,
253
            \T_DOC_COMMENT_CLOSE_TAG,
254
        ];
255

256
        do {
88×
257
            $prev = $phpcsFile->findPrevious($find, ($prev - 1));
88×
258
            if ($prev === false) {
16×
259
                break;
260
            }
261

88×
262
            if ($tokens[$prev]['code'] === \T_CLOSE_CURLY_BRACKET) {
263
                // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
56×
264
                if (isset($tokens[$prev]['scope_condition']) === true
56×
265
                    && $tokens[$tokens[$prev]['scope_condition']]['code'] === \T_NAMESPACE
266
                    /*
267
                     * BC: Work around a bug where curlies for variable variables received an incorrect
268
                     * and irrelevant scope condition in PHPCS < 3.3.0.
269
                     * {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1882}
270
                     */
56×
271
                    && self::isDeclaration($phpcsFile, $tokens[$prev]['scope_condition']) === true
272
                ) {
32×
273
                    break;
274
                }
275

276
                // Skip over other scoped structures for efficiency.
24×
277
                if (isset($tokens[$prev]['scope_condition']) === true) {
16×
278
                    $prev = $tokens[$prev]['scope_condition'];
16×
UNCOV
279
                } elseif (isset($tokens[$prev]['scope_opener']) === true) {
!
280
                    $prev = $tokens[$prev]['scope_opener'];
281
                }
282

24×
283
                continue;
284
            }
285

286
            // Skip over other nesting structures for efficiency.
88×
287
            if (isset($tokens[$prev]['bracket_opener']) === true) {
8×
288
                $prev = $tokens[$prev]['bracket_opener'];
8×
289
                continue;
290
            }
291

88×
292
            if (isset($tokens[$prev]['parenthesis_owner']) === true) {
8×
293
                $prev = $tokens[$prev]['parenthesis_owner'];
8×
294
                continue;
88×
295
            } elseif (isset($tokens[$prev]['parenthesis_opener']) === true) {
72×
296
                $prev = $tokens[$prev]['parenthesis_opener'];
72×
297
                continue;
298
            }
299

300
            // Skip over potentially large docblocks.
88×
301
            if (isset($tokens[$prev]['comment_opener'])) {
8×
302
                $prev = $tokens[$prev]['comment_opener'];
8×
303
                continue;
304
            }
305

306
            // So this is a namespace keyword, check if it's a declaration.
88×
307
            if ($tokens[$prev]['code'] === \T_NAMESPACE
88×
308
                && self::isDeclaration($phpcsFile, $prev) === true
309
            ) {
310
                // Now make sure the token was not part of the declaration.
40×
311
                $endOfStatement = $phpcsFile->findNext(Collections::$namespaceDeclarationClosers, ($prev + 1));
40×
312
                if ($endOfStatement > $stackPtr) {
4×
313
                    return false;
314
                }
315

36×
316
                return $prev;
317
            }
80×
318
        } while (true);
319

48×
320
        return false;
321
    }
322

323
    /**
324
     * Determine the namespace name an arbitrary token lives in.
325
     *
326
     * Note: this method has no opinion on whether the token passed is actually _subject_
327
     * to namespacing.
328
     *
329
     * @since 1.0.0
330
     *
331
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
332
     * @param int                         $stackPtr  The token for which to determine
333
     *                                               the namespace.
334
     *
335
     * @return string Namespace name or empty string if it couldn't be determined
336
     *                or no namespace applies.
337
     */
338
    public static function determineNamespace(File $phpcsFile, $stackPtr)
339
    {
60×
340
        $namespacePtr = self::findNamespacePtr($phpcsFile, $stackPtr);
60×
341
        if ($namespacePtr === false) {
24×
342
            return '';
343
        }
344

36×
345
        $namespace = self::getDeclaredName($phpcsFile, $namespacePtr);
36×
346
        if ($namespace !== false) {
32×
347
            return $namespace;
348
        }
349

4×
350
        return '';
351
    }
352
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc