├── CHANGELOG.md
├── LICENSE
├── PHPCSUtils
├── AbstractSniffs
│ └── AbstractArrayDeclarationSniff.php
├── BackCompat
│ ├── BCFile.php
│ ├── BCTokens.php
│ └── Helper.php
├── Exceptions
│ ├── InvalidTokenArray.php
│ ├── LogicException.php
│ ├── MissingArgumentError.php
│ ├── OutOfBoundsStackPtr.php
│ ├── RuntimeException.php
│ ├── TestFileNotFound.php
│ ├── TestMarkerNotFound.php
│ ├── TestTargetNotFound.php
│ ├── TypeError.php
│ ├── UnexpectedTokenType.php
│ └── ValueError.php
├── Fixers
│ └── SpacesFixer.php
├── Internal
│ ├── Cache.php
│ ├── IsShortArrayOrList.php
│ ├── IsShortArrayOrListWithCache.php
│ ├── NoFileCache.php
│ └── StableCollections.php
├── TestUtils
│ ├── ConfigDouble.php
│ └── UtilityMethodTestCase.php
├── Tokens
│ ├── Collections.php
│ └── TokenHelper.php
├── Utils
│ ├── Arrays.php
│ ├── Conditions.php
│ ├── Constants.php
│ ├── Context.php
│ ├── ControlStructures.php
│ ├── FileInfo.php
│ ├── FilePath.php
│ ├── FunctionDeclarations.php
│ ├── GetTokensAsString.php
│ ├── Lists.php
│ ├── MessageHelper.php
│ ├── Namespaces.php
│ ├── NamingConventions.php
│ ├── Numbers.php
│ ├── ObjectDeclarations.php
│ ├── Operators.php
│ ├── Orthography.php
│ ├── Parentheses.php
│ ├── PassedParameters.php
│ ├── Scopes.php
│ ├── TextStrings.php
│ ├── TypeString.php
│ ├── UseStatements.php
│ └── Variables.php
└── ruleset.xml
├── README.md
├── composer.json
└── phpcsutils-autoload.php
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/PHPCSUtils/BackCompat/BCTokens.php:
--------------------------------------------------------------------------------
1 | `PHPCSUtils\BackCompat\BCTokens::emptyTokens()`
38 | * - `PHP_CodeSniffer\Util\Tokens::$operators` => `PHPCSUtils\BackCompat\BCTokens::operators()`
39 | * - ... etc
40 | *
41 | * The order of the tokens in the arrays may differ between the PHPCS native token arrays and
42 | * the token arrays returned by this class.
43 | *
44 | * @since 1.0.0
45 | *
46 | * @method static array arithmeticTokens() Tokens that represent arithmetic operators.
47 | * @method static array assignmentTokens() Tokens that represent assignments.
48 | * @method static array blockOpeners() Tokens that open code blocks.
49 | * @method static array booleanOperators() Tokens that perform boolean operations.
50 | * @method static array bracketTokens() Tokens that represent brackets and parenthesis.
51 | * @method static array castTokens() Tokens that represent type casting.
52 | * @method static array commentTokens() Tokens that are comments.
53 | * @method static array comparisonTokens() Tokens that represent comparison operator.
54 | * @method static array contextSensitiveKeywords() Tokens representing context sensitive keywords
55 | * in PHP.
56 | * @method static array emptyTokens() Tokens that don't represent code.
57 | * @method static array equalityTokens() Tokens that represent equality comparisons.
58 | * @method static array heredocTokens() Tokens that make up a heredoc string.
59 | * @method static array includeTokens() Tokens that include files.
60 | * @method static array magicConstants() Tokens representing PHP magic constants.
61 | * @method static array methodPrefixes() Tokens that can prefix a method name.
62 | * @method static array ooScopeTokens() Tokens that open class and object scopes.
63 | * @method static array operators() Tokens that perform operations.
64 | * @method static array parenthesisOpeners() Token types that open parenthesis.
65 | * @method static array phpcsCommentTokens() Tokens that are comments containing PHPCS
66 | * instructions.
67 | * @method static array scopeModifiers() Tokens that represent scope modifiers.
68 | * @method static array scopeOpeners() Tokens that are allowed to open scopes.
69 | * @method static array stringTokens() Tokens that represent strings.
70 | * Note that `T_STRING`s are NOT represented in this
71 | * list as this list is about _text_ strings.
72 | * @method static array textStringTokens() Tokens that represent text strings.
73 | */
74 | final class BCTokens
75 | {
76 |
77 | /**
78 | * Handle calls to (undeclared) methods for token arrays which haven't received any
79 | * changes since PHPCS 3.13.0.
80 | *
81 | * @since 1.0.0
82 | *
83 | * @param string $name The name of the method which has been called.
84 | * @param array $args Any arguments passed to the method.
85 | * Unused as none of the methods take arguments.
86 | *
87 | * @return array Token array
88 | *
89 | * @throws \PHPCSUtils\Exceptions\InvalidTokenArray When an invalid token array is requested.
90 | */
91 | public static function __callStatic($name, $args)
92 | {
93 | if (isset(Tokens::${$name})) {
94 | return Tokens::${$name};
95 | }
96 |
97 | // Unknown token array requested.
98 | throw InvalidTokenArray::create($name);
99 | }
100 |
101 | /**
102 | * Tokens that represent the names of called functions.
103 | *
104 | * Retrieve the PHPCS function name tokens array in a cross-version compatible manner.
105 | *
106 | * Changelog for the PHPCS native array:
107 | * - Introduced in PHPCS 2.3.3.
108 | * - PHPCS 4.0.0: `T_NAME_QUALIFIED`, `T_NAME_FULLY_QUALIFIED` and `T_NAME_RELATIVE` added to the array.
109 | *
110 | * @see \PHP_CodeSniffer\Util\Tokens::$functionNameTokens Original array.
111 | *
112 | * @since 1.0.0
113 | *
114 | * @return array Token array.
115 | */
116 | public static function functionNameTokens()
117 | {
118 | $tokens = Tokens::$functionNameTokens;
119 | $tokens += Collections::nameTokens();
120 |
121 | return $tokens;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/PHPCSUtils/BackCompat/Helper.php:
--------------------------------------------------------------------------------
1 | config` property should work
63 | * in PHPCS 3.x and higher.
64 | *
65 | * @return bool Whether the setting of the data was successfull.
66 | *
67 | * @throws \PHPCSUtils\Exceptions\MissingArgumentError When using PHPCS 4.x and not passing the $config parameter.
68 | */
69 | public static function setConfigData($key, $value, $temp = false, $config = null)
70 | {
71 | if (isset($config) === true) {
72 | // PHPCS 3.x and 4.x.
73 | return $config->setConfigData($key, $value, $temp);
74 | }
75 |
76 | if (\version_compare(self::getVersion(), '3.99.99', '>') === true) {
77 | throw MissingArgumentError::create(4, '$config', 'when running on PHPCS 4.x');
78 | }
79 |
80 | // PHPCS 3.x.
81 | return Config::setConfigData($key, $value, $temp);
82 | }
83 |
84 | /**
85 | * Get the value of a single PHP_CodeSniffer config key.
86 | *
87 | * @see Helper::getCommandLineData() Alternative for the same which is more reliable
88 | * if the `$phpcsFile` object is available.
89 | *
90 | * @since 1.0.0
91 | *
92 | * @param string $key The name of the config value.
93 | *
94 | * @return string|null
95 | */
96 | public static function getConfigData($key)
97 | {
98 | return Config::getConfigData($key);
99 | }
100 |
101 | /**
102 | * Get the value of a CLI overrulable single PHP_CodeSniffer config key.
103 | *
104 | * Use this for config keys which can be set in the `CodeSniffer.conf` file,
105 | * on the command-line or in a ruleset.
106 | *
107 | * @since 1.0.0
108 | *
109 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed.
110 | * @param string $key The name of the config value.
111 | *
112 | * @return string|null
113 | */
114 | public static function getCommandLineData(File $phpcsFile, $key)
115 | {
116 | if (isset($phpcsFile->config->{$key})) {
117 | return $phpcsFile->config->{$key};
118 | }
119 |
120 | return null;
121 | }
122 |
123 | /**
124 | * Get the applicable tab width as passed to PHP_CodeSniffer from the
125 | * command-line or the ruleset.
126 | *
127 | * @since 1.0.0
128 | *
129 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed.
130 | *
131 | * @return int Tab width. Defaults to the PHPCS native default of 4.
132 | */
133 | public static function getTabWidth(File $phpcsFile)
134 | {
135 | $tabWidth = self::getCommandLineData($phpcsFile, 'tabWidth');
136 | if ($tabWidth > 0) {
137 | return (int) $tabWidth;
138 | }
139 |
140 | return self::DEFAULT_TABWIDTH;
141 | }
142 |
143 | /**
144 | * Get the applicable (file) encoding as passed to PHP_CodeSniffer from the
145 | * command-line or the ruleset.
146 | *
147 | * @since 1.0.0
148 | * @since 1.0.10 The `File` type declaration has been removed from the parameter declaration.
149 | *
150 | * @param \PHP_CodeSniffer\Files\File|null $phpcsFile Optional. The current file being processed.
151 | *
152 | * @return string Encoding. Defaults to the PHPCS native default, which is 'utf-8'
153 | * for PHPCS 3.x.
154 | */
155 | public static function getEncoding($phpcsFile = null)
156 | {
157 | $default = 'utf-8';
158 |
159 | if ($phpcsFile instanceof File) {
160 | // Most reliable.
161 | $encoding = self::getCommandLineData($phpcsFile, 'encoding');
162 | if ($encoding === null) {
163 | $encoding = $default;
164 | }
165 |
166 | return $encoding;
167 | }
168 |
169 | // Less reliable.
170 | $encoding = self::getConfigData('encoding');
171 | if ($encoding === null) {
172 | $encoding = $default;
173 | }
174 |
175 | return $encoding;
176 | }
177 |
178 | /**
179 | * Check whether the "--ignore-annotations" option is in effect.
180 | *
181 | * @since 1.0.0
182 | * @since 1.0.10 The `File` type declaration has been removed from the parameter declaration.
183 | *
184 | * @param \PHP_CodeSniffer\Files\File|null $phpcsFile Optional. The current file being processed.
185 | *
186 | * @return bool `TRUE` if annotations should be ignored, `FALSE` otherwise.
187 | */
188 | public static function ignoreAnnotations($phpcsFile = null)
189 | {
190 | if (isset($phpcsFile)
191 | && $phpcsFile instanceof File
192 | && isset($phpcsFile->config->annotations)
193 | ) {
194 | return ! $phpcsFile->config->annotations;
195 | }
196 |
197 | $annotations = Config::getConfigData('annotations');
198 | if (isset($annotations)) {
199 | return ! $annotations;
200 | }
201 |
202 | return false;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/PHPCSUtils/Exceptions/InvalidTokenArray.php:
--------------------------------------------------------------------------------
1 | getTokens();
106 |
107 | /*
108 | * Validate the received function input.
109 | */
110 |
111 | if (\is_int($stackPtr) === false) {
112 | throw TypeError::create(2, '$stackPtr', 'integer', $stackPtr);
113 | }
114 |
115 | if (\is_int($secondPtr) === false) {
116 | throw TypeError::create(3, '$secondPtr', 'integer', $secondPtr);
117 | }
118 |
119 | if (isset($tokens[$stackPtr]) === false) {
120 | throw OutOfBoundsStackPtr::create(2, '$stackPtr', $stackPtr);
121 | }
122 |
123 | if (isset($tokens[$secondPtr]) === false) {
124 | throw OutOfBoundsStackPtr::create(3, '$secondPtr', $secondPtr);
125 | }
126 |
127 | if ($tokens[$stackPtr]['code'] === \T_WHITESPACE) {
128 | throw UnexpectedTokenType::create(2, '$stackPtr', 'any, except whitespace', 'T_WHITESPACE');
129 | }
130 |
131 | if ($tokens[$secondPtr]['code'] === \T_WHITESPACE) {
132 | throw UnexpectedTokenType::create(3, '$secondPtr', 'any, except whitespace', 'T_WHITESPACE');
133 | }
134 |
135 | $expected = false;
136 | if ($expectedSpaces === 'newline') {
137 | $expected = $expectedSpaces;
138 | } elseif (\is_int($expectedSpaces) === true && $expectedSpaces >= 0) {
139 | $expected = $expectedSpaces;
140 | } elseif (\is_string($expectedSpaces) === true && Numbers::isDecimalInt($expectedSpaces) === true) {
141 | $expected = (int) $expectedSpaces;
142 | }
143 |
144 | if ($expected === false) {
145 | $message = \sprintf('should be either "newline", 0 or a positive integer; %s given', $expectedSpaces);
146 | throw ValueError::create(4, '$expectedSpaces', $message);
147 | }
148 |
149 | $ptrA = $stackPtr;
150 | $ptrB = $secondPtr;
151 | if ($stackPtr > $secondPtr) {
152 | $ptrA = $secondPtr;
153 | $ptrB = $stackPtr;
154 | }
155 |
156 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($ptrA + 1), null, true);
157 | if ($nextNonEmpty !== false && $nextNonEmpty < $ptrB) {
158 | throw LogicException::create(
159 | 'The $stackPtr and the $secondPtr token must be adjacent tokens separated only'
160 | . ' by whitespace and/or comments'
161 | );
162 | }
163 |
164 | /*
165 | * Determine how many spaces are between the two tokens.
166 | */
167 |
168 | $found = 0;
169 | $foundPhrase = 'no spaces';
170 | if ($tokens[$ptrA]['line'] !== $tokens[$ptrB]['line']) {
171 | $found = 'newline';
172 | $foundPhrase = 'a new line';
173 | if (($tokens[$ptrA]['line'] + 1) !== $tokens[$ptrB]['line']) {
174 | $foundPhrase = 'multiple new lines';
175 | }
176 | } elseif (($ptrA + 1) !== $ptrB) {
177 | if ($tokens[($ptrA + 1)]['code'] === \T_WHITESPACE) {
178 | $found = $tokens[($ptrA + 1)]['length'];
179 | $foundPhrase = $found . (($found === 1) ? ' space' : ' spaces');
180 | } else {
181 | $found = 'non-whitespace tokens';
182 | $foundPhrase = 'non-whitespace tokens';
183 | }
184 | }
185 |
186 | if ($metricName !== '') {
187 | $phpcsFile->recordMetric($stackPtr, $metricName, $foundPhrase);
188 | }
189 |
190 | if ($found === $expected) {
191 | return;
192 | }
193 |
194 | /*
195 | * Handle the violation message.
196 | */
197 |
198 | $expectedPhrase = 'no space';
199 | if ($expected === 'newline') {
200 | $expectedPhrase = 'a new line';
201 | } elseif ($expected === 1) {
202 | $expectedPhrase = $expected . ' space';
203 | } elseif ($expected > 1) {
204 | $expectedPhrase = $expected . ' spaces';
205 | }
206 |
207 | $fixable = true;
208 | $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($ptrA + 1), null, true);
209 | if ($nextNonWhitespace !== $ptrB) {
210 | // Comment found between the tokens and we don't know where it should go, so don't auto-fix.
211 | $fixable = false;
212 | }
213 |
214 | if ($found === 'newline'
215 | && $tokens[$ptrA]['code'] === \T_COMMENT
216 | && \substr($tokens[$ptrA]['content'], -2) !== '*/'
217 | ) {
218 | /*
219 | * $ptrA is a slash-style trailing comment, removing the new line would comment out
220 | * the code, so don't auto-fix.
221 | */
222 | $fixable = false;
223 | }
224 |
225 | $method = 'add';
226 | $method .= ($fixable === true) ? 'Fixable' : '';
227 | $method .= ($errorType === 'error') ? 'Error' : 'Warning';
228 |
229 | $recorded = $phpcsFile->$method(
230 | $errorTemplate,
231 | $stackPtr,
232 | $errorCode,
233 | [$expectedPhrase, $foundPhrase],
234 | $errorSeverity
235 | );
236 |
237 | if ($fixable === false || $recorded === false) {
238 | return;
239 | }
240 |
241 | /*
242 | * Fix the violation.
243 | */
244 |
245 | $phpcsFile->fixer->beginChangeset();
246 |
247 | /*
248 | * Remove existing whitespace. No need to check if it's whitespace as otherwise the fixer
249 | * wouldn't have kicked in.
250 | */
251 | for ($i = ($ptrA + 1); $i < $ptrB; $i++) {
252 | $phpcsFile->fixer->replaceToken($i, '');
253 | }
254 |
255 | // If necessary: add the correct amount whitespace.
256 | if ($expected !== 0) {
257 | if ($expected === 'newline') {
258 | $phpcsFile->fixer->addContent($ptrA, $phpcsFile->eolChar);
259 | } else {
260 | $replacement = $tokens[$ptrA]['content'] . \str_repeat(' ', $expected);
261 | $phpcsFile->fixer->replaceToken($ptrA, $replacement);
262 | }
263 | }
264 |
265 | $phpcsFile->fixer->endChangeset();
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/PHPCSUtils/Internal/Cache.php:
--------------------------------------------------------------------------------
1 | >>>
72 | * Format: $cache[$loop][$fileName][$key][$id] = mixed $value;
73 | */
74 | private static $cache = [];
75 |
76 | /**
77 | * Check whether a result has been cached for a certain utility function.
78 | *
79 | * @since 1.0.0
80 | *
81 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
82 | * @param string $key The key to identify a particular set of results.
83 | * It is recommended to pass __METHOD__ to this parameter.
84 | * @param int|string $id Unique identifier for these results.
85 | * Generally speaking this will be the $stackPtr passed
86 | * to the utility function, but it can also something else,
87 | * like a serialization of args passed to a function or an
88 | * md5 hash of an input.
89 | *
90 | * @return bool
91 | */
92 | public static function isCached(File $phpcsFile, $key, $id)
93 | {
94 | if (self::$enabled === false) {
95 | return false;
96 | }
97 |
98 | $fileName = $phpcsFile->getFilename();
99 | $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0;
100 |
101 | return isset(self::$cache[$loop][$fileName][$key])
102 | && \array_key_exists($id, self::$cache[$loop][$fileName][$key]);
103 | }
104 |
105 | /**
106 | * Retrieve a previously cached result for a certain utility function.
107 | *
108 | * @since 1.0.0
109 | *
110 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
111 | * @param string $key The key to identify a particular set of results.
112 | * It is recommended to pass __METHOD__ to this parameter.
113 | * @param int|string $id Unique identifier for these results.
114 | * Generally speaking this will be the $stackPtr passed
115 | * to the utility function, but it can also something else,
116 | * like a serialization of args passed to a function or an
117 | * md5 hash of an input.
118 | *
119 | * @return mixed
120 | */
121 | public static function get(File $phpcsFile, $key, $id)
122 | {
123 | if (self::$enabled === false) {
124 | return null;
125 | }
126 |
127 | $fileName = $phpcsFile->getFilename();
128 | $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0;
129 |
130 | if (isset(self::$cache[$loop][$fileName][$key])
131 | && \array_key_exists($id, self::$cache[$loop][$fileName][$key])
132 | ) {
133 | return self::$cache[$loop][$fileName][$key][$id];
134 | }
135 |
136 | return null;
137 | }
138 |
139 | /**
140 | * Retrieve all previously cached results for a certain utility function and a certain file.
141 | *
142 | * @since 1.0.0
143 | *
144 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
145 | * @param string $key The key to identify a particular set of results.
146 | * It is recommended to pass __METHOD__ to this parameter.
147 | *
148 | * @return array
149 | */
150 | public static function getForFile(File $phpcsFile, $key)
151 | {
152 | if (self::$enabled === false) {
153 | return [];
154 | }
155 |
156 | $fileName = $phpcsFile->getFilename();
157 | $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0;
158 |
159 | if (isset(self::$cache[$loop][$fileName])
160 | && \array_key_exists($key, self::$cache[$loop][$fileName])
161 | ) {
162 | return self::$cache[$loop][$fileName][$key];
163 | }
164 |
165 | return [];
166 | }
167 |
168 | /**
169 | * Cache the result for a certain utility function.
170 | *
171 | * @since 1.0.0
172 | *
173 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
174 | * @param string $key The key to identify a particular set of results.
175 | * It is recommended to pass __METHOD__ to this parameter.
176 | * @param int|string $id Unique identifier for these results.
177 | * Generally speaking this will be the $stackPtr passed
178 | * to the utility function, but it can also something else,
179 | * like a serialization of args passed to a function or an
180 | * md5 hash of an input.
181 | * @param mixed $value An arbitrary value to write to the cache.
182 | *
183 | * @return mixed
184 | */
185 | public static function set(File $phpcsFile, $key, $id, $value)
186 | {
187 | if (self::$enabled === false) {
188 | return;
189 | }
190 |
191 | $fileName = $phpcsFile->getFilename();
192 | $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0;
193 |
194 | /*
195 | * If this is a phpcbf run and we've reached the next loop, clear the cache
196 | * of all previous loops to free up memory.
197 | */
198 | if (isset(self::$cache[$loop]) === false
199 | && empty(self::$cache) === false
200 | ) {
201 | self::clear();
202 | }
203 |
204 | self::$cache[$loop][$fileName][$key][$id] = $value;
205 | }
206 |
207 | /**
208 | * Clear the cache.
209 | *
210 | * @since 1.0.0
211 | *
212 | * @return void
213 | */
214 | public static function clear()
215 | {
216 | self::$cache = [];
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php:
--------------------------------------------------------------------------------
1 | >
62 | */
63 | private $tokens;
64 |
65 | /**
66 | * The current stack pointer.
67 | *
68 | * @since 1.0.0
69 | *
70 | * @var int
71 | */
72 | private $stackPtr;
73 |
74 | /**
75 | * Stack pointer to the open bracket.
76 | *
77 | * @since 1.0.0
78 | *
79 | * @var int
80 | */
81 | private $opener;
82 |
83 | /**
84 | * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short array construct
85 | * and not a short list.
86 | *
87 | * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be
88 | * PHPCS cross-version compatible as the short array tokenizing has been plagued by
89 | * a number of bugs over time.
90 | *
91 | * @since 1.0.0
92 | *
93 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
94 | * @param int $stackPtr The position of the short array bracket token.
95 | *
96 | * @return bool `TRUE` if the token passed is the open/close bracket of a short array.
97 | * `FALSE` if the token is a short list bracket, a plain square bracket
98 | * or not one of the accepted tokens.
99 | */
100 | public static function isShortArray(File $phpcsFile, $stackPtr)
101 | {
102 | return (self::getType($phpcsFile, $stackPtr) === IsShortArrayOrList::SHORT_ARRAY);
103 | }
104 |
105 | /**
106 | * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short list construct
107 | * in contrast to a short array.
108 | *
109 | * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be
110 | * PHPCS cross-version compatible as the short array tokenizing has been plagued by
111 | * a number of bugs over time, which affects the short list determination.
112 | *
113 | * @since 1.0.0
114 | *
115 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
116 | * @param int $stackPtr The position of the short array bracket token.
117 | *
118 | * @return bool `TRUE` if the token passed is the open/close bracket of a short list.
119 | * `FALSE` if the token is a short array bracket or plain square bracket
120 | * or not one of the accepted tokens.
121 | */
122 | public static function isShortList(File $phpcsFile, $stackPtr)
123 | {
124 | return (self::getType($phpcsFile, $stackPtr) === IsShortArrayOrList::SHORT_LIST);
125 | }
126 |
127 | /**
128 | * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short array or short list construct.
129 | *
130 | * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be
131 | * PHPCS cross-version compatible as the short array tokenizing has been plagued by
132 | * a number of bugs over time, which affects the short list determination.
133 | *
134 | * @since 1.0.0
135 | *
136 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
137 | * @param int $stackPtr The position of the short array bracket token.
138 | *
139 | * @return string|false The type of construct this bracket was determined to be.
140 | * Either 'short array', 'short list' or 'square brackets'.
141 | * Or FALSE if this was not a bracket token.
142 | */
143 | public static function getType(File $phpcsFile, $stackPtr)
144 | {
145 | return (new self($phpcsFile, $stackPtr))->process();
146 | }
147 |
148 | /**
149 | * Constructor.
150 | *
151 | * @since 1.0.0
152 | *
153 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
154 | * @param int $stackPtr The position of the short array bracket token.
155 | *
156 | * @return void
157 | */
158 | private function __construct(File $phpcsFile, $stackPtr)
159 | {
160 | $this->phpcsFile = $phpcsFile;
161 | $this->tokens = $phpcsFile->getTokens();
162 | $this->stackPtr = $stackPtr;
163 | }
164 |
165 | /**
166 | * Determine whether a T_[OPEN|CLOSE}_[SHORT_ARRAY|SQUARE_BRACKET] token is a short array
167 | * or short list construct using previously cached results whenever possible.
168 | *
169 | * @since 1.0.0
170 | *
171 | * @return string|false The type of construct this bracket was determined to be.
172 | * Either 'short array', 'short list' or 'square brackets'.
173 | * Or FALSE is this was not a bracket token.
174 | */
175 | private function process()
176 | {
177 | if ($this->isValidStackPtr() === false) {
178 | return false;
179 | }
180 |
181 | $this->opener = $this->getOpener();
182 |
183 | /*
184 | * Check the cache in case we've seen this token before.
185 | */
186 | $type = $this->getFromCache();
187 | if ($type !== false) {
188 | return $type;
189 | }
190 |
191 | /*
192 | * If we've not seen the token before, try and solve it and cache the results.
193 | *
194 | * Make sure to safeguard against unopened/unclosed square brackets (parse error),
195 | * which should always be regarded as real square brackets.
196 | */
197 | $type = IsShortArrayOrList::SQUARE_BRACKETS;
198 | if (isset($this->tokens[$this->stackPtr]['bracket_opener'], $this->tokens[$this->stackPtr]['bracket_closer'])) {
199 | $solver = new IsShortArrayOrList($this->phpcsFile, $this->opener);
200 | $type = $solver->solve();
201 | }
202 |
203 | $this->updateCache($type);
204 |
205 | return $type;
206 | }
207 |
208 | /**
209 | * Verify the passed token could potentially be a short array or short list token.
210 | *
211 | * @since 1.0.0
212 | *
213 | * @return bool
214 | */
215 | private function isValidStackPtr()
216 | {
217 | return (isset($this->tokens[$this->stackPtr]) === true
218 | && isset(StableCollections::$shortArrayListTokensBC[$this->tokens[$this->stackPtr]['code']]) === true);
219 | }
220 |
221 | /**
222 | * Get the stack pointer to the short array/list opener.
223 | *
224 | * @since 1.0.0
225 | *
226 | * @return int
227 | */
228 | private function getOpener()
229 | {
230 | $opener = $this->stackPtr;
231 | if (isset($this->tokens[$this->stackPtr]['bracket_opener'])) {
232 | $opener = $this->tokens[$this->stackPtr]['bracket_opener'];
233 | }
234 |
235 | return $opener;
236 | }
237 |
238 | /**
239 | * Retrieve the bracket "type" of a token from the cache.
240 | *
241 | * @since 1.0.0
242 | *
243 | * @return string|false The previously determined type (which could be an empty string)
244 | * or FALSE if no cache entry was found for this token.
245 | */
246 | private function getFromCache()
247 | {
248 | if (Cache::isCached($this->phpcsFile, self::CACHE_KEY, $this->opener) === true) {
249 | return Cache::get($this->phpcsFile, self::CACHE_KEY, $this->opener);
250 | }
251 |
252 | return false;
253 | }
254 |
255 | /**
256 | * Update the cache with information about a particular bracket token.
257 | *
258 | * @since 1.0.0
259 | *
260 | * @param string $type The type this bracket has been determined to be.
261 | * Either 'short array', 'short list' or 'square brackets'.
262 | *
263 | * @return void
264 | */
265 | private function updateCache($type)
266 | {
267 | Cache::set($this->phpcsFile, self::CACHE_KEY, $this->opener, $type);
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/PHPCSUtils/Internal/NoFileCache.php:
--------------------------------------------------------------------------------
1 | > Format: $cache[$key][$id] = mixed $value;
68 | */
69 | private static $cache = [];
70 |
71 | /**
72 | * Check whether a result has been cached for a certain utility function.
73 | *
74 | * @since 1.0.0
75 | *
76 | * @param string $key The key to identify a particular set of results.
77 | * It is recommended to pass `__METHOD__` to this parameter.
78 | * @param int|string $id Unique identifier for these results.
79 | * It is recommended for this to be a serialization of arguments passed
80 | * to the function or an md5 hash of an input.
81 | *
82 | * @return bool
83 | */
84 | public static function isCached($key, $id)
85 | {
86 | return self::$enabled === true && isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key]);
87 | }
88 |
89 | /**
90 | * Retrieve a previously cached result for a certain utility function.
91 | *
92 | * @since 1.0.0
93 | *
94 | * @param string $key The key to identify a particular set of results.
95 | * It is recommended to pass `__METHOD__` to this parameter.
96 | * @param int|string $id Unique identifier for these results.
97 | * It is recommended for this to be a serialization of arguments passed
98 | * to the function or an md5 hash of an input.
99 | *
100 | * @return mixed
101 | */
102 | public static function get($key, $id)
103 | {
104 | if (self::$enabled === true && isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key])) {
105 | return self::$cache[$key][$id];
106 | }
107 |
108 | return null;
109 | }
110 |
111 | /**
112 | * Retrieve all previously cached results for a certain utility function.
113 | *
114 | * @since 1.0.0
115 | *
116 | * @param string $key The key to identify a particular set of results.
117 | * It is recommended to pass `__METHOD__` to this parameter.
118 | *
119 | * @return array
120 | */
121 | public static function getForKey($key)
122 | {
123 | if (self::$enabled === true && \array_key_exists($key, self::$cache)) {
124 | return self::$cache[$key];
125 | }
126 |
127 | return [];
128 | }
129 |
130 | /**
131 | * Cache the result for a certain utility function.
132 | *
133 | * @since 1.0.0
134 | *
135 | * @param string $key The key to identify a particular set of results.
136 | * It is recommended to pass `__METHOD__` to this parameter.
137 | * @param int|string $id Unique identifier for these results.
138 | * It is recommended for this to be a serialization of arguments passed
139 | * to the function or an md5 hash of an input.
140 | * @param mixed $value An arbitrary value to write to the cache.
141 | *
142 | * @return mixed
143 | */
144 | public static function set($key, $id, $value)
145 | {
146 | if (self::$enabled === false) {
147 | return;
148 | }
149 |
150 | self::$cache[$key][$id] = $value;
151 | }
152 |
153 | /**
154 | * Clear the cache.
155 | *
156 | * @since 1.0.0
157 | *
158 | * @return void
159 | */
160 | public static function clear()
161 | {
162 | self::$cache = [];
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/PHPCSUtils/Internal/StableCollections.php:
--------------------------------------------------------------------------------
1 |
50 | */
51 | public static $shortArrayListOpenTokensBC = [
52 | \T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
53 | \T_OPEN_SQUARE_BRACKET => \T_OPEN_SQUARE_BRACKET,
54 | ];
55 |
56 | /**
57 | * Tokens which are used for short lists.
58 | *
59 | * This array will ALWAYS include the `T_OPEN_SQUARE_BRACKET` and `T_CLOSE_SQUARE_BRACKET` tokens
60 | * to allow for handling intermittent tokenizer issues related to the retokenization to `T_OPEN_SHORT_ARRAY`.
61 | *
62 | * @internal
63 | * @ignore This array is only for internal use by PHPCSUtils and is not part of the public API.
64 | *
65 | * @since 1.0.2
66 | *
67 | * @var array
68 | */
69 | public static $shortArrayListTokensBC = [
70 | \T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
71 | \T_CLOSE_SHORT_ARRAY => \T_CLOSE_SHORT_ARRAY,
72 | \T_OPEN_SQUARE_BRACKET => \T_OPEN_SQUARE_BRACKET,
73 | \T_CLOSE_SQUARE_BRACKET => \T_CLOSE_SQUARE_BRACKET,
74 | ];
75 | }
76 |
--------------------------------------------------------------------------------
/PHPCSUtils/TestUtils/ConfigDouble.php:
--------------------------------------------------------------------------------
1 | $cliArgs An array of values gathered from CLI args.
57 | * @param bool $skipSettingStandard Whether to skip setting a standard to prevent
58 | * the Config class trying to auto-discover a ruleset file.
59 | * Should only be set to `true` for tests which actually test
60 | * the ruleset auto-discovery.
61 | * Note: there is no need to set this to `true` when a standard
62 | * is being passed via the `$cliArgs`. Those settings will always
63 | * respected.
64 | * Defaults to `false`. Will result in the standard being set
65 | * to "PSR1" if not provided via `$cliArgs`.
66 | * @param bool $skipSettingReportWidth Whether to skip setting a report-width to prevent
67 | * the Config class trying to auto-discover the screen width.
68 | * Should only be set to `true` for tests which actually test
69 | * the screen width auto-discovery.
70 | * Note: there is no need to set this to `true` when a report-width
71 | * is being passed via the `$cliArgs`. Those settings will always
72 | * respected.
73 | * Defaults to `false`. Will result in the reportWidth being set
74 | * to "80" if not provided via `$cliArgs`.
75 | *
76 | * @return void
77 | */
78 | public function __construct(array $cliArgs = [], $skipSettingStandard = false, $skipSettingReportWidth = false)
79 | {
80 | $this->skipSettingStandard = $skipSettingStandard;
81 | $this->phpcsVersion = Helper::getVersion();
82 |
83 | $this->resetSelectProperties();
84 | $this->preventReadingCodeSnifferConfFile();
85 |
86 | parent::__construct($cliArgs);
87 |
88 | if ($skipSettingReportWidth !== true) {
89 | $this->preventAutoDiscoveryScreenWidth();
90 | }
91 | }
92 |
93 | /**
94 | * Ensures the static properties in the Config class are reset to their default values
95 | * when the ConfigDouble is no longer used.
96 | *
97 | * @since 1.1.0
98 | *
99 | * @return void
100 | */
101 | public function __destruct()
102 | {
103 | $this->setStaticConfigProperty('overriddenDefaults', []);
104 | $this->setStaticConfigProperty('executablePaths', []);
105 | $this->setStaticConfigProperty('configData', null);
106 | $this->setStaticConfigProperty('configDataFile', null);
107 | }
108 |
109 | /**
110 | * Sets the command line values and optionally prevents a file system search for a custom ruleset.
111 | *
112 | * {@internal Note: `array` type declaration can't be added as the parent class does not have a type declaration
113 | * for the parameter in the original method.}
114 | *
115 | * @since 1.1.0
116 | *
117 | * @param array $args An array of command line arguments to set.
118 | *
119 | * @return void
120 | */
121 | public function setCommandLineValues($args)
122 | {
123 | parent::setCommandLineValues($args);
124 |
125 | if ($this->skipSettingStandard !== true) {
126 | $this->preventSearchingForRuleset();
127 | }
128 | }
129 |
130 | /**
131 | * Reset a few properties on the Config class to their default values.
132 | *
133 | * @since 1.1.0
134 | *
135 | * @return void
136 | */
137 | private function resetSelectProperties()
138 | {
139 | $this->setStaticConfigProperty('overriddenDefaults', []);
140 | $this->setStaticConfigProperty('executablePaths', []);
141 | }
142 |
143 | /**
144 | * Prevent the values in a potentially available user-specific `CodeSniffer.conf` file
145 | * from influencing the tests.
146 | *
147 | * This also prevents some file system calls which can influence the test runtime.
148 | *
149 | * @since 1.1.0
150 | *
151 | * @return void
152 | */
153 | private function preventReadingCodeSnifferConfFile()
154 | {
155 | $this->setStaticConfigProperty('configData', []);
156 | $this->setStaticConfigProperty('configDataFile', '');
157 | }
158 |
159 | /**
160 | * Prevent searching for a custom ruleset by setting a standard, but only if the test
161 | * being run doesn't set a standard itself.
162 | *
163 | * This also prevents some file system calls which can influence the test runtime.
164 | *
165 | * The standard being set is the smallest one available so the ruleset initialization
166 | * will be the fastest possible.
167 | *
168 | * @since 1.1.0
169 | *
170 | * @return void
171 | */
172 | private function preventSearchingForRuleset()
173 | {
174 | $overriddenDefaults = $this->getStaticConfigProperty('overriddenDefaults');
175 | if (isset($overriddenDefaults['standards']) === false) {
176 | $this->standards = ['PSR1'];
177 | $overriddenDefaults['standards'] = true;
178 | }
179 |
180 | self::setStaticConfigProperty('overriddenDefaults', $overriddenDefaults);
181 | }
182 |
183 | /**
184 | * Prevent a call to stty to figure out the screen width, but only if the test being run
185 | * doesn't set a report width itself.
186 | *
187 | * @since 1.1.0
188 | *
189 | * @return void
190 | */
191 | private function preventAutoDiscoveryScreenWidth()
192 | {
193 | $settings = $this->getSettings();
194 | if ($settings['reportWidth'] === 'auto') {
195 | $this->reportWidth = self::DEFAULT_REPORT_WIDTH;
196 | }
197 | }
198 |
199 | /**
200 | * Helper function to retrieve the value of a private static property on the Config class.
201 | *
202 | * Note: As of PHPCS 4.0, the "overriddenDefaults" property is no longer static, but this method
203 | * will still handle this property.
204 | *
205 | * @since 1.1.0
206 | *
207 | * @param string $name The name of the property to retrieve.
208 | *
209 | * @return mixed
210 | */
211 | public function getStaticConfigProperty($name)
212 | {
213 | $property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
214 | $property->setAccessible(true);
215 |
216 | if ($name === 'overriddenDefaults' && \version_compare($this->phpcsVersion, '3.99.99', '>')) {
217 | return $property->getValue($this);
218 | }
219 |
220 | return $property->getValue();
221 | }
222 |
223 | /**
224 | * Helper function to set the value of a private static property on the Config class.
225 | *
226 | * Note: As of PHPCS 4.0, the "overriddenDefaults" property is no longer static, but this method
227 | * will still handle this property.
228 | *
229 | * @since 1.1.0
230 | *
231 | * @param string $name The name of the property to set.
232 | * @param mixed $value The value to set the property to.
233 | *
234 | * @return void
235 | */
236 | public function setStaticConfigProperty($name, $value)
237 | {
238 | $property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
239 | $property->setAccessible(true);
240 |
241 | if ($name === 'overriddenDefaults' && \version_compare($this->phpcsVersion, '3.99.99', '>')) {
242 | $property->setValue($this, $value);
243 | } else {
244 | $property->setValue(null, $value);
245 | }
246 |
247 | $property->setAccessible(false);
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/PHPCSUtils/Tokens/TokenHelper.php:
--------------------------------------------------------------------------------
1 | = 9.3 (which uses PHP-Parser),
29 | * this logic breaks because PHP-Parser also polyfills tokens.
30 | * This method takes potentially polyfilled tokens from PHP-Parser into account and will regard the token
31 | * as undefined if it was declared by PHP-Parser.
32 | *
33 | * Note: this method only _needs_ to be used for PHP native tokens, not for PHPCS specific tokens.
34 | * Also, realistically, it only needs to be used for tokens introduced in PHP in recent versions (PHP 7.4 and up).
35 | * Having said that, the method _will_ also work correctly when a name of a PHPCS native token is passed or
36 | * of an older PHP native token.
37 | *
38 | * {@internal PHP native tokens have a positive integer value. PHPCS polyfilled tokens are strings.
39 | * PHP-Parser polyfilled tokens will always have a negative integer value < 0, which is how
40 | * these are filtered out.}
41 | *
42 | * @link https://github.com/sebastianbergmann/php-code-coverage/issues/798 PHP-Code-Coverage#798
43 | * @link https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Lexer.php PHP-Parser Lexer code
44 | *
45 | * @since 1.0.0
46 | *
47 | * @param string $name The token name.
48 | *
49 | * @return bool
50 | */
51 | public static function tokenExists($name)
52 | {
53 | return (\defined($name) && (\is_int(\constant($name)) === false || \constant($name) > 0));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Arrays.php:
--------------------------------------------------------------------------------
1 |
35 | */
36 | private static $doubleArrowTargets = [
37 | \T_DOUBLE_ARROW => \T_DOUBLE_ARROW,
38 |
39 | // Nested arrays.
40 | \T_ARRAY => \T_ARRAY,
41 | \T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
42 |
43 | // Inline function, control structures and other things to skip over.
44 | \T_LIST => \T_LIST,
45 | \T_FN => \T_FN,
46 | \T_MATCH => \T_MATCH,
47 | \T_ATTRIBUTE => \T_ATTRIBUTE,
48 | ];
49 |
50 | /**
51 | * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short array construct
52 | * and not a short list.
53 | *
54 | * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be
55 | * PHPCS cross-version compatible as the short array tokenizing has been plagued by
56 | * a number of bugs over time.
57 | *
58 | * @since 1.0.0
59 | *
60 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
61 | * @param int $stackPtr The position of the short array bracket token.
62 | *
63 | * @return bool `TRUE` if the token passed is the open/close bracket of a short array.
64 | * `FALSE` if the token is a short list bracket, a plain square bracket
65 | * or not one of the accepted tokens.
66 | */
67 | public static function isShortArray(File $phpcsFile, $stackPtr)
68 | {
69 | return IsShortArrayOrListWithCache::isShortArray($phpcsFile, $stackPtr);
70 | }
71 |
72 | /**
73 | * Find the array opener and closer based on a T_ARRAY or T_OPEN_SHORT_ARRAY token.
74 | *
75 | * This method also accepts `T_OPEN_SQUARE_BRACKET` tokens to allow it to be
76 | * PHPCS cross-version compatible as the short array tokenizing has been plagued by
77 | * a number of bugs over time, which affects the short array determination.
78 | *
79 | * @since 1.0.0
80 | *
81 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
82 | * @param int $stackPtr The position of the `T_ARRAY` or `T_OPEN_SHORT_ARRAY`
83 | * token in the stack.
84 | * @param true|null $isShortArray Short-circuit the short array check for `T_OPEN_SHORT_ARRAY`
85 | * tokens if it isn't necessary.
86 | * Efficiency tweak for when this has already been established,
87 | * i.e. when encountering a nested array while walking the
88 | * tokens in an array.
89 | * Use with care.
90 | *
91 | * @return array|false An array with the token pointers; or `FALSE` if this is not a
92 | * (short) array token or if the opener/closer could not be determined.
93 | * The format of the array return value is:
94 | * ```php
95 | * array(
96 | * 'opener' => integer, // Stack pointer to the array open bracket.
97 | * 'closer' => integer, // Stack pointer to the array close bracket.
98 | * )
99 | * ```
100 | */
101 | public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortArray = null)
102 | {
103 | $tokens = $phpcsFile->getTokens();
104 |
105 | // Is this one of the tokens this function handles ?
106 | if (isset($tokens[$stackPtr]) === false
107 | || isset(Collections::arrayOpenTokensBC()[$tokens[$stackPtr]['code']]) === false
108 | ) {
109 | return false;
110 | }
111 |
112 | switch ($tokens[$stackPtr]['code']) {
113 | case \T_ARRAY:
114 | if (isset($tokens[$stackPtr]['parenthesis_opener'])) {
115 | $opener = $tokens[$stackPtr]['parenthesis_opener'];
116 |
117 | if (isset($tokens[$opener]['parenthesis_closer'])) {
118 | $closer = $tokens[$opener]['parenthesis_closer'];
119 | }
120 | }
121 | break;
122 |
123 | case \T_OPEN_SHORT_ARRAY:
124 | case \T_OPEN_SQUARE_BRACKET:
125 | if ($isShortArray === true || self::isShortArray($phpcsFile, $stackPtr) === true) {
126 | $opener = $stackPtr;
127 | $closer = $tokens[$stackPtr]['bracket_closer'];
128 | }
129 | break;
130 | }
131 |
132 | if (isset($opener, $closer)) {
133 | return [
134 | 'opener' => $opener,
135 | 'closer' => $closer,
136 | ];
137 | }
138 |
139 | return false;
140 | }
141 |
142 | /**
143 | * Get the stack pointer position of the double arrow within an array item.
144 | *
145 | * Expects to be passed the array item start and end tokens as retrieved via
146 | * {@see \PHPCSUtils\Utils\PassedParameters::getParameters()}.
147 | *
148 | * @since 1.0.0
149 | *
150 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being examined.
151 | * @param int $start Stack pointer to the start of the array item.
152 | * @param int $end Stack pointer to the last token in the array item.
153 | *
154 | * @return int|false Stack pointer to the double arrow if this array item has a key; or `FALSE` otherwise.
155 | *
156 | * @throws \PHPCSUtils\Exceptions\TypeError If the $start or $end parameters are not integers.
157 | * @throws \PHPCSUtils\Exceptions\OutOfBoundsStackPtr If the tokens passed do not exist in the $phpcsFile.
158 | * @throws \PHPCSUtils\Exceptions\LogicException If $end pointer is before the $start pointer.
159 | */
160 | public static function getDoubleArrowPtr(File $phpcsFile, $start, $end)
161 | {
162 | $tokens = $phpcsFile->getTokens();
163 |
164 | if (\is_int($start) === false) {
165 | throw TypeError::create(2, '$start', 'integer', $start);
166 | }
167 |
168 | if (\is_int($end) === false) {
169 | throw TypeError::create(3, '$end', 'integer', $end);
170 | }
171 |
172 | if (isset($tokens[$start]) === false) {
173 | throw OutOfBoundsStackPtr::create(2, '$start', $start);
174 | }
175 |
176 | if (isset($tokens[$end]) === false) {
177 | throw OutOfBoundsStackPtr::create(3, '$end', $end);
178 | }
179 |
180 | if ($start > $end) {
181 | throw LogicException::create(
182 | \sprintf('The $start token must be before the $end token. Received: $start %d, $end %d', $start, $end)
183 | );
184 | }
185 |
186 | $cacheId = "$start-$end";
187 | if (Cache::isCached($phpcsFile, __METHOD__, $cacheId) === true) {
188 | return Cache::get($phpcsFile, __METHOD__, $cacheId);
189 | }
190 |
191 | $targets = self::$doubleArrowTargets;
192 | $targets += Collections::closedScopes();
193 |
194 | $doubleArrow = ($start - 1);
195 | $returnValue = false;
196 | ++$end;
197 | do {
198 | $doubleArrow = $phpcsFile->findNext(
199 | $targets,
200 | ($doubleArrow + 1),
201 | $end
202 | );
203 |
204 | if ($doubleArrow === false) {
205 | break;
206 | }
207 |
208 | if ($tokens[$doubleArrow]['code'] === \T_DOUBLE_ARROW) {
209 | $returnValue = $doubleArrow;
210 | break;
211 | }
212 |
213 | // Skip over closed scopes which may contain foreach structures or generators.
214 | if ((isset(Collections::closedScopes()[$tokens[$doubleArrow]['code']]) === true
215 | || $tokens[$doubleArrow]['code'] === \T_FN
216 | || $tokens[$doubleArrow]['code'] === \T_MATCH)
217 | && isset($tokens[$doubleArrow]['scope_closer']) === true
218 | ) {
219 | $doubleArrow = $tokens[$doubleArrow]['scope_closer'];
220 | continue;
221 | }
222 |
223 | // Skip over attributes which may contain arrays as a passed parameters.
224 | if ($tokens[$doubleArrow]['code'] === \T_ATTRIBUTE
225 | && isset($tokens[$doubleArrow]['attribute_closer'])
226 | ) {
227 | $doubleArrow = $tokens[$doubleArrow]['attribute_closer'];
228 | continue;
229 | }
230 |
231 | // Skip over potentially keyed long lists.
232 | if ($tokens[$doubleArrow]['code'] === \T_LIST
233 | && isset($tokens[$doubleArrow]['parenthesis_closer'])
234 | ) {
235 | $doubleArrow = $tokens[$doubleArrow]['parenthesis_closer'];
236 | continue;
237 | }
238 |
239 | // Start of nested long/short array.
240 | break;
241 | } while ($doubleArrow < $end);
242 |
243 | Cache::set($phpcsFile, __METHOD__, $cacheId, $returnValue);
244 | return $returnValue;
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Conditions.php:
--------------------------------------------------------------------------------
1 | $types Optional. The type(s) of tokens to search for.
45 | * @param bool $first Optional. Whether to search for the first (outermost)
46 | * (`true`) or the last (innermost) condition (`false`) of
47 | * the specified type(s).
48 | *
49 | * @return int|false Integer stack pointer to the condition; or `FALSE` if the token
50 | * does not have the condition or has no conditions at all.
51 | */
52 | public static function getCondition(File $phpcsFile, $stackPtr, $types = [], $first = true)
53 | {
54 | $tokens = $phpcsFile->getTokens();
55 |
56 | // Check for the existence of the token.
57 | if (isset($tokens[$stackPtr]) === false) {
58 | return false;
59 | }
60 |
61 | // Make sure the token has conditions.
62 | if (empty($tokens[$stackPtr]['conditions'])) {
63 | return false;
64 | }
65 |
66 | $types = (array) $types;
67 | $conditions = $tokens[$stackPtr]['conditions'];
68 |
69 | if (empty($types) === true) {
70 | // No types specified, just return the first/last condition pointer.
71 | if ($first === false) {
72 | \end($conditions);
73 | } else {
74 | \reset($conditions);
75 | }
76 |
77 | return \key($conditions);
78 | }
79 |
80 | if ($first === false) {
81 | $conditions = \array_reverse($conditions, true);
82 | }
83 |
84 | foreach ($conditions as $ptr => $type) {
85 | if (isset($tokens[$ptr]) === true
86 | && \in_array($type, $types, true) === true
87 | ) {
88 | // We found a token with the required type.
89 | return $ptr;
90 | }
91 | }
92 |
93 | return false;
94 | }
95 |
96 | /**
97 | * Determine if the passed token has a condition of one of the passed types.
98 | *
99 | * This method is not significantly different from the PHPCS native version.
100 | *
101 | * @see \PHP_CodeSniffer\Files\File::hasCondition() Original source.
102 | * @see \PHPCSUtils\BackCompat\BCFile::hasCondition() Cross-version compatible version of the original.
103 | *
104 | * @since 1.0.0
105 | *
106 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
107 | * @param int $stackPtr The position of the token we are checking.
108 | * @param int|string|array $types The type(s) of tokens to search for.
109 | *
110 | * @return bool
111 | */
112 | public static function hasCondition(File $phpcsFile, $stackPtr, $types)
113 | {
114 | return (self::getCondition($phpcsFile, $stackPtr, $types) !== false);
115 | }
116 |
117 | /**
118 | * Return the position of the first (outermost) condition of a certain type for the passed token.
119 | *
120 | * If no types are specified, the first condition for the token, independently of type,
121 | * will be returned.
122 | *
123 | * @since 1.0.0
124 | *
125 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
126 | * @param int $stackPtr The position of the token we are checking.
127 | * @param int|string|array $types Optional. The type(s) of tokens to search for.
128 | *
129 | * @return int|false Integer stack pointer to the condition; or `FALSE` if the token
130 | * does not have the condition or has no conditions at all.
131 | */
132 | public static function getFirstCondition(File $phpcsFile, $stackPtr, $types = [])
133 | {
134 | return self::getCondition($phpcsFile, $stackPtr, $types, true);
135 | }
136 |
137 | /**
138 | * Return the position of the last (innermost) condition of a certain type for the passed token.
139 | *
140 | * If no types are specified, the last condition for the token, independently of type,
141 | * will be returned.
142 | *
143 | * @since 1.0.0
144 | *
145 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
146 | * @param int $stackPtr The position of the token we are checking.
147 | * @param int|string|array $types Optional. The type(s) of tokens to search for.
148 | *
149 | * @return int|false Integer stack pointer to the condition; or `FALSE` if the token
150 | * does not have the condition or has no conditions at all.
151 | */
152 | public static function getLastCondition(File $phpcsFile, $stackPtr, $types = [])
153 | {
154 | return self::getCondition($phpcsFile, $stackPtr, $types, false);
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Constants.php:
--------------------------------------------------------------------------------
1 | Array with information about the constant declaration.
41 | * The format of the return value is:
42 | * ```php
43 | * array(
44 | * 'scope' => string, // Public, private, or protected.
45 | * 'scope_token' => integer|false, // The stack pointer to the scope keyword or
46 | * // FALSE if the scope was not explicitly specified.
47 | * 'is_final' => boolean, // TRUE if the final keyword was found.
48 | * 'final_token' => integer|false, // The stack pointer to the final keyword
49 | * // or FALSE if the const is not declared final.
50 | * 'type' => string, // The type of the const (empty if no type specified).
51 | * 'type_token' => integer|false, // The stack pointer to the start of the type
52 | * // or FALSE if there is no type.
53 | * 'type_end_token' => integer|false, // The stack pointer to the end of the type
54 | * // or FALSE if there is no type.
55 | * 'nullable_type' => boolean, // TRUE if the type is preceded by the
56 | * // nullability operator.
57 | * 'name_token' => integer, // The stack pointer to the constant name.
58 | * // Note: for group declarations this points to the
59 | * // name of the first constant.
60 | * 'equal_token' => integer, // The stack pointer to the equal sign.
61 | * // Note: for group declarations this points to the
62 | * // equal sign of the first constant.
63 | * );
64 | * ```
65 | *
66 | * @throws \PHPCSUtils\Exceptions\TypeError If the $stackPtr parameter is not an integer.
67 | * @throws \PHPCSUtils\Exceptions\OutOfBoundsStackPtr If the token passed does not exist in the $phpcsFile.
68 | * @throws \PHPCSUtils\Exceptions\UnexpectedTokenType If the token passed is not a `T_CONST` token.
69 | * @throws \PHPCSUtils\Exceptions\ValueError If the specified position is not an OO constant.
70 | */
71 | public static function getProperties(File $phpcsFile, $stackPtr)
72 | {
73 | $tokens = $phpcsFile->getTokens();
74 |
75 | if (\is_int($stackPtr) === false) {
76 | throw TypeError::create(2, '$stackPtr', 'integer', $stackPtr);
77 | }
78 |
79 | if (isset($tokens[$stackPtr]) === false) {
80 | throw OutOfBoundsStackPtr::create(2, '$stackPtr', $stackPtr);
81 | }
82 |
83 | if ($tokens[$stackPtr]['code'] !== \T_CONST) {
84 | throw UnexpectedTokenType::create(2, '$stackPtr', 'T_CONST', $tokens[$stackPtr]['type']);
85 | }
86 |
87 | if (Scopes::isOOConstant($phpcsFile, $stackPtr) === false) {
88 | throw ValueError::create(2, '$stackPtr', 'must be the pointer to an OO constant');
89 | }
90 |
91 | if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) {
92 | return Cache::get($phpcsFile, __METHOD__, $stackPtr);
93 | }
94 |
95 | $find = [\T_EQUAL, \T_SEMICOLON, \T_OPEN_CURLY_BRACKET, \T_CLOSE_CURLY_BRACKET, \T_CLOSE_TAG];
96 | $assignmentPtr = $phpcsFile->findNext($find, ($stackPtr + 1));
97 | if ($assignmentPtr === false || $tokens[$assignmentPtr]['code'] !== \T_EQUAL) {
98 | // Probably a parse error. Don't cache the result.
99 | throw ValueError::create(2, '$stackPtr', 'must be the pointer to an OO constant');
100 | }
101 |
102 | $namePtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($assignmentPtr - 1), ($stackPtr + 1), true);
103 |
104 | $returnValue = [
105 | 'scope' => 'public',
106 | 'scope_token' => false,
107 | 'is_final' => false,
108 | 'final_token' => false,
109 | 'type' => '',
110 | 'type_token' => false,
111 | 'type_end_token' => false,
112 | 'nullable_type' => false,
113 | 'name_token' => $namePtr,
114 | 'equal_token' => $assignmentPtr,
115 | ];
116 |
117 | for ($i = ($stackPtr - 1);; $i--) {
118 | // Skip over potentially large docblocks.
119 | if ($tokens[$i]['code'] === \T_DOC_COMMENT_CLOSE_TAG
120 | && isset($tokens[$i]['comment_opener'])
121 | ) {
122 | $i = $tokens[$i]['comment_opener'];
123 | continue;
124 | }
125 |
126 | if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
127 | continue;
128 | }
129 |
130 | switch ($tokens[$i]['code']) {
131 | case \T_PUBLIC:
132 | $returnValue['scope'] = 'public';
133 | $returnValue['scope_token'] = $i;
134 | break;
135 |
136 | case \T_PROTECTED:
137 | $returnValue['scope'] = 'protected';
138 | $returnValue['scope_token'] = $i;
139 | break;
140 |
141 | case \T_PRIVATE:
142 | $returnValue['scope'] = 'private';
143 | $returnValue['scope_token'] = $i;
144 | break;
145 |
146 | case \T_FINAL:
147 | $returnValue['is_final'] = true;
148 | $returnValue['final_token'] = $i;
149 | break;
150 |
151 | default:
152 | // Any other token means that the start of the statement has been reached.
153 | break 2;
154 | }
155 | }
156 |
157 | $type = '';
158 | $typeToken = false;
159 | $typeEndToken = false;
160 | $constantTypeTokens = Collections::constantTypeTokens();
161 |
162 | // Now, let's check for a type.
163 | for ($i = ($stackPtr + 1); $i < $namePtr; $i++) {
164 | if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
165 | continue;
166 | }
167 |
168 | if ($tokens[$i]['code'] === \T_NULLABLE) {
169 | $returnValue['nullable_type'] = true;
170 | continue;
171 | }
172 |
173 | if (isset($constantTypeTokens[$tokens[$i]['code']]) === true) {
174 | $typeEndToken = $i;
175 | if ($typeToken === false) {
176 | $typeToken = $i;
177 | }
178 |
179 | $type .= $tokens[$i]['content'];
180 | }
181 | }
182 |
183 | if ($type !== '' && $returnValue['nullable_type'] === true) {
184 | $type = '?' . $type;
185 | }
186 |
187 | $returnValue['type'] = $type;
188 | $returnValue['type_token'] = $typeToken;
189 | $returnValue['type_end_token'] = $typeEndToken;
190 |
191 | Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue);
192 | return $returnValue;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Context.php:
--------------------------------------------------------------------------------
1 | getTokens();
100 |
101 | // Check for the existence of the token.
102 | if (isset($tokens[$stackPtr]) === false) {
103 | return false;
104 | }
105 |
106 | if (isset($tokens[$stackPtr]['attribute_opener'], $tokens[$stackPtr]['attribute_closer']) === false) {
107 | return false;
108 | }
109 |
110 | return ($stackPtr !== $tokens[$stackPtr]['attribute_opener']
111 | && $stackPtr !== $tokens[$stackPtr]['attribute_closer']);
112 | }
113 |
114 | /**
115 | * Check whether an arbitrary token is in a foreach condition and if so, in which part:
116 | * before or after the "as".
117 | *
118 | * @since 1.0.0
119 | *
120 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
121 | * @param int $stackPtr The position of the token we are checking.
122 | *
123 | * @return string|false String `'beforeAs'`, `'as'` or `'afterAs'` when the token is within
124 | * a `foreach` condition.
125 | * `FALSE` in all other cases, including for parse errors.
126 | */
127 | public static function inForeachCondition(File $phpcsFile, $stackPtr)
128 | {
129 | $tokens = $phpcsFile->getTokens();
130 |
131 | // Check for the existence of the token.
132 | if (isset($tokens[$stackPtr]) === false) {
133 | return false;
134 | }
135 |
136 | $foreach = Parentheses::getLastOwner($phpcsFile, $stackPtr, \T_FOREACH);
137 | if ($foreach === false) {
138 | return false;
139 | }
140 |
141 | $opener = $tokens[$foreach]['parenthesis_opener'];
142 | $closer = $tokens[$foreach]['parenthesis_closer'];
143 |
144 | for ($i = ($opener + 1); $i < $closer; $i++) {
145 | // Skip past a short array declaration in the "before as" part.
146 | if (isset($tokens[$i]['bracket_closer'])) {
147 | $i = $tokens[$i]['bracket_closer'];
148 | continue;
149 | }
150 |
151 | // Skip past a long array declaration in the "before as" part.
152 | if (isset($tokens[$i]['parenthesis_closer'])) {
153 | $i = $tokens[$i]['parenthesis_closer'];
154 | continue;
155 | }
156 |
157 | if ($tokens[$i]['code'] === \T_AS) {
158 | $asPtr = $i;
159 | break;
160 | }
161 | }
162 |
163 | if (isset($asPtr) === false) {
164 | // Parse error or live coding.
165 | return false;
166 | }
167 |
168 | if ($asPtr === $stackPtr) {
169 | return 'as';
170 | }
171 |
172 | if ($stackPtr < $asPtr) {
173 | return 'beforeAs';
174 | }
175 |
176 | return 'afterAs';
177 | }
178 |
179 | /**
180 | * Check whether an arbitrary token is in a for condition and if so, in which part:
181 | * the first, second or third expression.
182 | *
183 | * Note: the semicolons separating the conditions are regarded as belonging with the
184 | * expression before it.
185 | *
186 | * @since 1.0.0
187 | *
188 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
189 | * @param int $stackPtr The position of the token we are checking.
190 | *
191 | * @return string|false String `'expr1'`, `'expr2'` or `'expr3'` when the token is within
192 | * a `for` condition.
193 | * `FALSE` in all other cases, including for parse errors.
194 | */
195 | public static function inForCondition(File $phpcsFile, $stackPtr)
196 | {
197 | $tokens = $phpcsFile->getTokens();
198 |
199 | // Check for the existence of the token.
200 | if (isset($tokens[$stackPtr]) === false) {
201 | return false;
202 | }
203 |
204 | $for = Parentheses::getLastOwner($phpcsFile, $stackPtr, \T_FOR);
205 | if ($for === false) {
206 | return false;
207 | }
208 |
209 | $semicolons = [];
210 | $count = 0;
211 | $opener = $tokens[$for]['parenthesis_opener'];
212 | $closer = $tokens[$for]['parenthesis_closer'];
213 | $level = $tokens[$for]['level'];
214 | $parens = 1;
215 |
216 | if (isset($tokens[$for]['nested_parenthesis'])) {
217 | $parens = (\count($tokens[$for]['nested_parenthesis']) + 1);
218 | }
219 |
220 | for ($i = ($opener + 1); $i < $closer; $i++) {
221 | if ($tokens[$i]['code'] !== \T_SEMICOLON) {
222 | continue;
223 | }
224 |
225 | if ($tokens[$i]['level'] !== $level
226 | || \count($tokens[$i]['nested_parenthesis']) !== $parens
227 | ) {
228 | // Disregard semicolons at lower nesting/condition levels.
229 | continue;
230 | }
231 |
232 | ++$count;
233 | $semicolons[$count] = $i;
234 | }
235 |
236 | if ($count !== 2) {
237 | return false;
238 | }
239 |
240 | foreach ($semicolons as $key => $ptr) {
241 | if ($stackPtr <= $ptr) {
242 | return 'expr' . $key;
243 | }
244 | }
245 |
246 | return 'expr3';
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/ControlStructures.php:
--------------------------------------------------------------------------------
1 | getTokens();
55 |
56 | // Check for the existence of the token.
57 | if (isset($tokens[$stackPtr]) === false
58 | || isset(Collections::controlStructureTokens()[$tokens[$stackPtr]['code']]) === false
59 | ) {
60 | return false;
61 | }
62 |
63 | // Handle `else if`.
64 | if ($tokens[$stackPtr]['code'] === \T_ELSE && isset($tokens[$stackPtr]['scope_opener']) === false) {
65 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
66 | if ($next !== false && $tokens[$next]['code'] === \T_IF) {
67 | $stackPtr = $next;
68 | }
69 | }
70 |
71 | /*
72 | * The scope markers are set. This is the simplest situation.
73 | */
74 | if (isset($tokens[$stackPtr]['scope_opener']) === true) {
75 | if ($allowEmpty === true) {
76 | return true;
77 | }
78 |
79 | // Check whether the body is empty.
80 | $start = ($tokens[$stackPtr]['scope_opener'] + 1);
81 | $end = $phpcsFile->numTokens;
82 | if (isset($tokens[$stackPtr]['scope_closer']) === true) {
83 | $end = $tokens[$stackPtr]['scope_closer'];
84 | }
85 |
86 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
87 | if ($nextNonEmpty !== false) {
88 | return true;
89 | }
90 |
91 | return false;
92 | }
93 |
94 | /*
95 | * Control structure without scope markers.
96 | * Either single line statement or inline control structure.
97 | *
98 | * - Single line statement doesn't have a body and is therefore always empty.
99 | * - Inline control structure has to have a body and can never be empty.
100 | *
101 | * This code also needs to take live coding into account where a scope opener is found, but
102 | * no scope closer.
103 | */
104 | $searchStart = ($stackPtr + 1);
105 | if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
106 | $searchStart = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
107 | }
108 |
109 | $nextNonEmpty = $phpcsFile->findNext(
110 | Tokens::$emptyTokens,
111 | $searchStart,
112 | null,
113 | true
114 | );
115 | if ($nextNonEmpty === false
116 | || $tokens[$nextNonEmpty]['code'] === \T_SEMICOLON
117 | || $tokens[$nextNonEmpty]['code'] === \T_CLOSE_TAG
118 | ) {
119 | // Parse error or single line statement.
120 | return false;
121 | }
122 |
123 | if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_CURLY_BRACKET) {
124 | if ($allowEmpty === true) {
125 | return true;
126 | }
127 |
128 | // Unrecognized scope opener due to parse error.
129 | $nextNext = $phpcsFile->findNext(
130 | Tokens::$emptyTokens,
131 | ($nextNonEmpty + 1),
132 | null,
133 | true
134 | );
135 |
136 | if ($nextNext === false) {
137 | return false;
138 | }
139 |
140 | return true;
141 | }
142 |
143 | return true;
144 | }
145 |
146 | /**
147 | * Check whether an IF or ELSE token is part of an "else if".
148 | *
149 | * @since 1.0.0
150 | *
151 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
152 | * @param int $stackPtr The position of the token we are checking.
153 | *
154 | * @return bool
155 | */
156 | public static function isElseIf(File $phpcsFile, $stackPtr)
157 | {
158 | $tokens = $phpcsFile->getTokens();
159 |
160 | // Check for the existence of the token.
161 | if (isset($tokens[$stackPtr]) === false) {
162 | return false;
163 | }
164 |
165 | if ($tokens[$stackPtr]['code'] === \T_ELSEIF) {
166 | return true;
167 | }
168 |
169 | if ($tokens[$stackPtr]['code'] !== \T_ELSE && $tokens[$stackPtr]['code'] !== \T_IF) {
170 | return false;
171 | }
172 |
173 | if ($tokens[$stackPtr]['code'] === \T_ELSE && isset($tokens[$stackPtr]['scope_opener']) === true) {
174 | return false;
175 | }
176 |
177 | switch ($tokens[$stackPtr]['code']) {
178 | case \T_ELSE:
179 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
180 | if ($next !== false && $tokens[$next]['code'] === \T_IF) {
181 | return true;
182 | }
183 | break;
184 |
185 | case \T_IF:
186 | $previous = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
187 | if ($previous !== false && $tokens[$previous]['code'] === \T_ELSE) {
188 | return true;
189 | }
190 | break;
191 | }
192 |
193 | return false;
194 | }
195 |
196 | /**
197 | * Retrieve the exception(s) being caught in a CATCH condition.
198 | *
199 | * @since 1.0.0
200 | *
201 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
202 | * @param int $stackPtr The position of the token we are checking.
203 | *
204 | * @return array>
205 | * Array with information about the caught Exception(s).
206 | * The returned array will contain the following information for
207 | * each caught exception:
208 | * ```php
209 | * 0 => array(
210 | * 'type' => string, // The type declaration for the exception being caught.
211 | * 'type_token' => integer, // The stack pointer to the start of the type declaration.
212 | * 'type_end_token' => integer, // The stack pointer to the end of the type declaration.
213 | * )
214 | * ```
215 | * In case of an invalid catch structure, the array may be empty.
216 | *
217 | * @throws \PHPCSUtils\Exceptions\TypeError If the $stackPtr parameter is not an integer.
218 | * @throws \PHPCSUtils\Exceptions\OutOfBoundsStackPtr If the token passed does not exist in the $phpcsFile.
219 | * @throws \PHPCSUtils\Exceptions\UnexpectedTokenType If the token passed is not a `T_CATCH` token.
220 | */
221 | public static function getCaughtExceptions(File $phpcsFile, $stackPtr)
222 | {
223 | $tokens = $phpcsFile->getTokens();
224 |
225 | if (\is_int($stackPtr) === false) {
226 | throw TypeError::create(2, '$stackPtr', 'integer', $stackPtr);
227 | }
228 |
229 | if (isset($tokens[$stackPtr]) === false) {
230 | throw OutOfBoundsStackPtr::create(2, '$stackPtr', $stackPtr);
231 | }
232 |
233 | if ($tokens[$stackPtr]['code'] !== \T_CATCH) {
234 | throw UnexpectedTokenType::create(2, '$stackPtr', 'T_CATCH', $tokens[$stackPtr]['type']);
235 | }
236 |
237 | if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
238 | return [];
239 | }
240 |
241 | $opener = $tokens[$stackPtr]['parenthesis_opener'];
242 | $closer = $tokens[$stackPtr]['parenthesis_closer'];
243 | $exceptions = [];
244 |
245 | $foundName = '';
246 | $firstToken = null;
247 | $lastToken = null;
248 |
249 | for ($i = ($opener + 1); $i <= $closer; $i++) {
250 | if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
251 | continue;
252 | }
253 |
254 | if (isset(Collections::namespacedNameTokens()[$tokens[$i]['code']]) === false) {
255 | // Add the current exception to the result array if one was found.
256 | if ($foundName !== '') {
257 | $exceptions[] = [
258 | 'type' => $foundName,
259 | 'type_token' => $firstToken,
260 | 'type_end_token' => $lastToken,
261 | ];
262 | }
263 |
264 | if ($tokens[$i]['code'] === \T_BITWISE_OR) {
265 | // Multi-catch. Reset and continue.
266 | $foundName = '';
267 | $firstToken = null;
268 | $lastToken = null;
269 | continue;
270 | }
271 |
272 | break;
273 | }
274 |
275 | if (isset($firstToken) === false) {
276 | $firstToken = $i;
277 | }
278 |
279 | $foundName .= $tokens[$i]['content'];
280 | $lastToken = $i;
281 | }
282 |
283 | return $exceptions;
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/FileInfo.php:
--------------------------------------------------------------------------------
1 |
31 | */
32 | private static $bomDefinitions = [
33 | 'UTF-8' => 'efbbbf',
34 | 'UTF-16 (BE)' => 'feff',
35 | 'UTF-16 (LE)' => 'fffe',
36 | ];
37 |
38 | /**
39 | * Determine whether the file under scan has a byte-order mark at the start.
40 | *
41 | * Inspired by similar code being used in a couple of PHPCS native sniffs.
42 | *
43 | * @since 1.1.0
44 | *
45 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
46 | *
47 | * @return string|false Name of the type of BOM found or FALSE when the file does not start with a BOM.
48 | */
49 | public static function hasByteOrderMark(File $phpcsFile)
50 | {
51 | $tokens = $phpcsFile->getTokens();
52 |
53 | if ($tokens[0]['code'] !== \T_INLINE_HTML) {
54 | return false;
55 | }
56 |
57 | foreach (self::$bomDefinitions as $bomName => $expectedBomHex) {
58 | $bomByteLength = (int) (\strlen($expectedBomHex) / 2);
59 | $htmlBomHex = \bin2hex(\substr($tokens[0]['content'], 0, $bomByteLength));
60 | if ($htmlBomHex === $expectedBomHex) {
61 | return $bomName;
62 | }
63 | }
64 |
65 | return false;
66 | }
67 |
68 | /**
69 | * Determine whether the file under scan has a shebang line at the start.
70 | *
71 | * @since 1.1.0
72 | *
73 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
74 | *
75 | * @return bool
76 | */
77 | public static function hasSheBang(File $phpcsFile)
78 | {
79 | $tokens = $phpcsFile->getTokens();
80 | if ($tokens[0]['code'] !== \T_INLINE_HTML) {
81 | return false;
82 | }
83 |
84 | $start = 0;
85 | $hasByteOrderMark = self::hasByteOrderMark($phpcsFile);
86 | if ($hasByteOrderMark !== false) {
87 | $start = (int) (\strlen(self::$bomDefinitions[$hasByteOrderMark]) / 2);
88 | }
89 |
90 | return (\substr($tokens[0]['content'], $start, 2) === '#!');
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/FilePath.php:
--------------------------------------------------------------------------------
1 | getFileName());
48 | if ($fileName !== 'STDIN') {
49 | $fileName = self::normalizeAbsolutePath($fileName);
50 | }
51 |
52 | return \trim($fileName);
53 | }
54 |
55 | /**
56 | * Check whether the input was received via STDIN.
57 | *
58 | * @since 1.1.0
59 | *
60 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
61 | *
62 | * @return bool
63 | */
64 | public static function isStdin(File $phpcsFile)
65 | {
66 | return (self::getName($phpcsFile) === 'STDIN');
67 | }
68 |
69 | /**
70 | * Normalize an absolute path to forward slashes and to include a trailing slash for directories.
71 | *
72 | * @since 1.1.0
73 | *
74 | * @param string $path Absolute file or directory path.
75 | *
76 | * @return string
77 | */
78 | public static function normalizeAbsolutePath($path)
79 | {
80 | return self::trailingSlashIt(self::normalizeDirectorySeparators($path));
81 | }
82 |
83 | /**
84 | * Normalize all directory separators to be a forward slash.
85 | *
86 | * {@internal We cannot rely on the OS on which PHPCS is being run to determine the
87 | * the expected slashes, as the file name could also come from a text string in a
88 | * tokenized file or have been set by an IDE...}
89 | *
90 | * @since 1.1.0
91 | *
92 | * @param string $path File or directory path.
93 | *
94 | * @return string
95 | */
96 | public static function normalizeDirectorySeparators($path)
97 | {
98 | return \strtr((string) $path, '\\', '/');
99 | }
100 |
101 | /**
102 | * Ensure that a directory path ends on a trailing slash.
103 | *
104 | * Includes safeguard against adding a trailing slash to path ending on a file name.
105 | *
106 | * @since 1.1.0
107 | *
108 | * @param string $path File or directory path.
109 | *
110 | * @return string
111 | */
112 | public static function trailingSlashIt($path)
113 | {
114 | if (\is_string($path) === false || $path === '') {
115 | return '';
116 | }
117 |
118 | $extension = '';
119 | $lastChar = \substr($path, -1);
120 | if ($lastChar !== '/' && $lastChar !== '\\') {
121 | // This may be a file, check if it has a file extension.
122 | $extension = \pathinfo($path, \PATHINFO_EXTENSION);
123 | }
124 |
125 | if ($extension !== '') {
126 | return $path;
127 | }
128 |
129 | return \rtrim((string) $path, '/\\') . '/';
130 | }
131 |
132 | /**
133 | * Check whether one file/directory path starts with another path.
134 | *
135 | * Recommended to be used only when both paths are absolute.
136 | *
137 | * Note: this function does not normalize paths prior to comparing them.
138 | * If this is needed, normalization should be done prior to passing
139 | * the `$haystack` and `$needle` parameters to this function.
140 | *
141 | * Also note that this function does a case-sensitive comparison as most OS-es are case-sensitive.
142 | *
143 | * @since 1.1.0
144 | *
145 | * @param string $haystack Path to examine.
146 | * @param string $needle Partial path which the haystack path should start with.
147 | *
148 | * @return bool
149 | */
150 | public static function startsWith($haystack, $needle)
151 | {
152 | return (\strncmp($haystack, $needle, \strlen($needle)) === 0);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/GetTokensAsString.php:
--------------------------------------------------------------------------------
1 | getTokens();
223 |
224 | if (\is_int($start) === false) {
225 | throw TypeError::create(2, '$start', 'integer', $start);
226 | }
227 |
228 | if (isset($tokens[$start]) === false) {
229 | throw OutOfBoundsStackPtr::create(2, '$start', $start);
230 | }
231 |
232 | if (\is_int($end) === false || $end < $start) {
233 | return '';
234 | }
235 |
236 | $str = '';
237 | if ($end >= $phpcsFile->numTokens) {
238 | $end = ($phpcsFile->numTokens - 1);
239 | }
240 |
241 | $lastAdded = null;
242 | for ($i = $start; $i <= $end; $i++) {
243 | if ($stripComments === true && isset(Tokens::$commentTokens[$tokens[$i]['code']])) {
244 | continue;
245 | }
246 |
247 | if ($stripWhitespace === true && $tokens[$i]['code'] === \T_WHITESPACE) {
248 | continue;
249 | }
250 |
251 | if ($compact === true && $tokens[$i]['code'] === \T_WHITESPACE) {
252 | if (isset($lastAdded) === false || $tokens[$lastAdded]['code'] !== \T_WHITESPACE) {
253 | $str .= ' ';
254 | $lastAdded = $i;
255 | }
256 | continue;
257 | }
258 |
259 | // If tabs are being converted to spaces by the tokenizer, the
260 | // original content should be used instead of the converted content.
261 | if ($origContent === true && isset($tokens[$i]['orig_content']) === true) {
262 | $str .= $tokens[$i]['orig_content'];
263 | } else {
264 | $str .= $tokens[$i]['content'];
265 | }
266 |
267 | $lastAdded = $i;
268 | }
269 |
270 | return $str;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/MessageHelper.php:
--------------------------------------------------------------------------------
1 | addError($message, $stackPtr, $code, $data, $severity);
54 | }
55 |
56 | return $phpcsFile->addWarning($message, $stackPtr, $code, $data, $severity);
57 | }
58 |
59 | /**
60 | * Add a PHPCS message to the output stack as either a fixable warning or a fixable error.
61 | *
62 | * @since 1.0.0
63 | *
64 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
65 | * @param string $message The message.
66 | * @param int $stackPtr The position of the token
67 | * the message relates to.
68 | * @param bool $isError Whether to report the message as an
69 | * 'error' or 'warning'.
70 | * Defaults to true (error).
71 | * @param string $code The error code for the message.
72 | * Defaults to 'Found'.
73 | * @param scalar[] $data Optional input for the data replacements.
74 | * @param int $severity Optional. Severity level. Defaults to 0 which will
75 | * translate to the PHPCS default severity level.
76 | *
77 | * @return bool
78 | */
79 | public static function addFixableMessage(
80 | File $phpcsFile,
81 | $message,
82 | $stackPtr,
83 | $isError = true,
84 | $code = 'Found',
85 | array $data = [],
86 | $severity = 0
87 | ) {
88 | if ($isError === true) {
89 | return $phpcsFile->addFixableError($message, $stackPtr, $code, $data, $severity);
90 | }
91 |
92 | return $phpcsFile->addFixableWarning($message, $stackPtr, $code, $data, $severity);
93 | }
94 |
95 | /**
96 | * Convert an arbitrary text string to an alphanumeric string with underscores.
97 | *
98 | * Pre-empt issues in XML and PHP when arbitrary strings are being used as error codes.
99 | *
100 | * @since 1.0.0
101 | *
102 | * @param string $text Arbitrary text string intended to be used in an error code.
103 | * @param bool $strtolower Whether or not to convert the text string to lowercase.
104 | *
105 | * @return string
106 | */
107 | public static function stringToErrorcode($text, $strtolower = false)
108 | {
109 | $text = \preg_replace('`[^a-z0-9_]`i', '_', $text);
110 |
111 | if ($strtolower === true) {
112 | $text = \strtolower($text);
113 | }
114 |
115 | return $text;
116 | }
117 |
118 | /**
119 | * Make the whitespace escape codes used in an arbitrary text string visible.
120 | *
121 | * At times, it is useful to show a code snippet in an error message.
122 | * If such a code snippet contains new lines and/or tab or space characters, those would be
123 | * displayed as-is in the command-line report, often breaking the layout of the report
124 | * or making the report harder to read.
125 | *
126 | * This method will convert these characters to their escape codes, making them visible in the
127 | * display string without impacting the report layout.
128 | *
129 | * @see \PHPCSUtils\Utils\GetTokensToString Methods to retrieve a multi-token code snippet.
130 | * @see \PHP_CodeSniffer\Util\Common\prepareForOutput() Similar PHPCS native method.
131 | *
132 | * @since 1.0.0
133 | *
134 | * @param string $text Arbitrary text string.
135 | *
136 | * @return string
137 | */
138 | public static function showEscapeChars($text)
139 | {
140 | $search = ["\n", "\r", "\t"];
141 | $replace = ['\n', '\r', '\t'];
142 |
143 | return \str_replace($search, $replace, $text);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/NamingConventions.php:
--------------------------------------------------------------------------------
1 | Note: for variable names, the leading dollar sign - `$` - needs to be
67 | * removed prior to passing the name to this method.
68 | *
69 | * @return bool
70 | */
71 | public static function isValidIdentifierName($name)
72 | {
73 | if (\is_string($name) === false || $name === '' || \strpos($name, ' ') !== false) {
74 | return false;
75 | }
76 |
77 | return (\preg_match(self::PHP_LABEL_REGEX, $name) === 1);
78 | }
79 |
80 | /**
81 | * Check if two arbitrary identifier names will be seen as the same in PHP.
82 | *
83 | * This method should not be used for variable or constant names, but *should* be used
84 | * when comparing namespace, class/trait/interface and function names.
85 | *
86 | * Variable and constant names in PHP are case-sensitive, except for constants explicitely
87 | * declared case-insensitive using the third parameter for
88 | * {@link https://www.php.net/function.define `define()`}.
89 | *
90 | * All other names are case-insensitive for the most part, but as it's PHP, not completely.
91 | * Basically ASCII chars used are case-insensitive, but anything from 0x80 up is case-sensitive.
92 | *
93 | * This method takes this case-(in)sensitivity into account when comparing identifier names.
94 | *
95 | * Note: this method does not check whether the passed names would be valid for identifiers!
96 | * The {@see \PHPCSUtils\Utils\NamingConventions::isValidIdentifierName()} method should be used
97 | * to verify that, if necessary.
98 | *
99 | * @since 1.0.0
100 | *
101 | * @param string $nameA The first identifier name.
102 | * @param string $nameB The second identifier name.
103 | *
104 | * @return bool `TRUE` if these names would be considered the same in PHP; `FALSE` otherwise.
105 | */
106 | public static function isEqual($nameA, $nameB)
107 | {
108 | // Simple quick check first.
109 | if ($nameA === $nameB) {
110 | return true;
111 | }
112 |
113 | // Comparing via strcasecmp will only compare ASCII letters case-insensitively.
114 | return (\strcasecmp($nameA, $nameB) === 0);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Numbers.php:
--------------------------------------------------------------------------------
1 | [0-9](?:[0-9_]*[0-9])?)
88 | |
89 | (?P((?:[0-9](?:[0-9_]*[0-9])?)*\.(?P>LNUM)|(?P>LNUM)\.(?:[0-9](?:[0-9_]*[0-9])?)*))
90 | )
91 | [e][+-]?(?P>LNUM)
92 | )
93 | |
94 | (?P>DNUM)
95 | |
96 | (?:0|[1-9](?:[0-9_]*[0-9])?)
97 | )$
98 | `ixD';
99 |
100 | /**
101 | * Retrieve information about a number token.
102 | *
103 | * Helper function to deal with numeric literals, potentially with underscore separators
104 | * and/or explicit octal notation.
105 | *
106 | * @since 1.0.0
107 | *
108 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
109 | * @param int $stackPtr The position of a T_LNUMBER or T_DNUMBER token.
110 | *
111 | * @return array An array with information about the number.
112 | * The format of the array return value is:
113 | * ```php
114 | * array(
115 | * 'orig_content' => string, // The original content of the token(s);
116 | * 'content' => string, // The content, underscore(s) removed;
117 | * 'code' => int, // The token code of the number, either
118 | * // T_LNUMBER or T_DNUMBER.
119 | * 'type' => string, // The token type, either 'T_LNUMBER'
120 | * // or 'T_DNUMBER'.
121 | * 'decimal' => string, // The decimal value of the number;
122 | * 'last_token' => int, // The stackPtr to the last token which was
123 | * // part of the number.
124 | * // At this time, this will be always be the original
125 | * // stackPtr. This may change in the future if
126 | * // new numeric syntaxes would be added to PHP.
127 | * )
128 | * ```
129 | *
130 | * @throws \PHPCSUtils\Exceptions\TypeError If the $stackPtr parameter is not an integer.
131 | * @throws \PHPCSUtils\Exceptions\OutOfBoundsStackPtr If the token passed does not exist in the $phpcsFile.
132 | * @throws \PHPCSUtils\Exceptions\UnexpectedTokenType If the token passed is not a `T_LNUMBER` or `T_DNUMBER` token.
133 | */
134 | public static function getCompleteNumber(File $phpcsFile, $stackPtr)
135 | {
136 | $tokens = $phpcsFile->getTokens();
137 |
138 | if (\is_int($stackPtr) === false) {
139 | throw TypeError::create(2, '$stackPtr', 'integer', $stackPtr);
140 | }
141 |
142 | if (isset($tokens[$stackPtr]) === false) {
143 | throw OutOfBoundsStackPtr::create(2, '$stackPtr', $stackPtr);
144 | }
145 |
146 | if ($tokens[$stackPtr]['code'] !== \T_LNUMBER && $tokens[$stackPtr]['code'] !== \T_DNUMBER) {
147 | throw UnexpectedTokenType::create(2, '$stackPtr', 'T_LNUMBER or T_DNUMBER', $tokens[$stackPtr]['type']);
148 | }
149 |
150 | $content = $tokens[$stackPtr]['content'];
151 | return [
152 | 'orig_content' => $content,
153 | 'content' => \str_replace('_', '', $content),
154 | 'code' => $tokens[$stackPtr]['code'],
155 | 'type' => $tokens[$stackPtr]['type'],
156 | 'decimal' => self::getDecimalValue($content),
157 | 'last_token' => $stackPtr,
158 | ];
159 | }
160 |
161 | /**
162 | * Get the decimal number value of a numeric string.
163 | *
164 | * Takes PHP 7.4 numeric literal separators and explicit octal literals in numbers into account.
165 | *
166 | * @since 1.0.0
167 | *
168 | * @param string $textString Arbitrary text string.
169 | * This text string should be the (combined) token content of
170 | * one or more tokens which together represent a number in PHP.
171 | *
172 | * @return string|false Decimal number as a string or `FALSE` if the passed parameter
173 | * was not a numeric string.
174 | * > Note: floating point numbers with exponent will not be expanded,
175 | * but returned as-is.
176 | */
177 | public static function getDecimalValue($textString)
178 | {
179 | if (\is_string($textString) === false || $textString === '') {
180 | return false;
181 | }
182 |
183 | /*
184 | * Remove potential PHP 7.4 numeric literal separators.
185 | *
186 | * {@internal While the is..() functions also do this, this is still needed
187 | * here to allow the hexdec(), bindec() functions to work correctly and for
188 | * the decimal/float to return a cross-version compatible decimal value.}
189 | */
190 | $textString = \str_replace('_', '', $textString);
191 |
192 | if (self::isDecimalInt($textString) === true) {
193 | return $textString;
194 | }
195 |
196 | if (self::isHexidecimalInt($textString) === true) {
197 | return (string) \hexdec($textString);
198 | }
199 |
200 | if (self::isBinaryInt($textString) === true) {
201 | return (string) \bindec($textString);
202 | }
203 |
204 | if (self::isOctalInt($textString) === true) {
205 | return (string) \octdec($textString);
206 | }
207 |
208 | if (self::isFloat($textString) === true) {
209 | return $textString;
210 | }
211 |
212 | return false;
213 | }
214 |
215 | /**
216 | * Verify whether the contents of an arbitrary string represents a decimal integer.
217 | *
218 | * Takes PHP 7.4 numeric literal separators in numbers into account in the regex.
219 | *
220 | * @since 1.0.0
221 | *
222 | * @param string $textString Arbitrary string.
223 | *
224 | * @return bool
225 | */
226 | public static function isDecimalInt($textString)
227 | {
228 | if (\is_string($textString) === false || $textString === '') {
229 | return false;
230 | }
231 |
232 | return (\preg_match(self::REGEX_DECIMAL_INT, $textString) === 1);
233 | }
234 |
235 | /**
236 | * Verify whether the contents of an arbitrary string represents a hexidecimal integer.
237 | *
238 | * Takes PHP 7.4 numeric literal separators in numbers into account in the regex.
239 | *
240 | * @since 1.0.0
241 | *
242 | * @param string $textString Arbitrary string.
243 | *
244 | * @return bool
245 | */
246 | public static function isHexidecimalInt($textString)
247 | {
248 | if (\is_string($textString) === false || $textString === '') {
249 | return false;
250 | }
251 |
252 | return (\preg_match(self::REGEX_HEX_INT, $textString) === 1);
253 | }
254 |
255 | /**
256 | * Verify whether the contents of an arbitrary string represents a binary integer.
257 | *
258 | * Takes PHP 7.4 numeric literal separators in numbers into account in the regex.
259 | *
260 | * @since 1.0.0
261 | *
262 | * @param string $textString Arbitrary string.
263 | *
264 | * @return bool
265 | */
266 | public static function isBinaryInt($textString)
267 | {
268 | if (\is_string($textString) === false || $textString === '') {
269 | return false;
270 | }
271 |
272 | return (\preg_match(self::REGEX_BINARY_INT, $textString) === 1);
273 | }
274 |
275 | /**
276 | * Verify whether the contents of an arbitrary string represents an octal integer.
277 | *
278 | * Takes PHP 7.4 numeric literal separators and explicit octal literals in numbers into account in the regex.
279 | *
280 | * @since 1.0.0
281 | *
282 | * @param string $textString Arbitrary string.
283 | *
284 | * @return bool
285 | */
286 | public static function isOctalInt($textString)
287 | {
288 | if (\is_string($textString) === false || $textString === '') {
289 | return false;
290 | }
291 |
292 | return (\preg_match(self::REGEX_OCTAL_INT, $textString) === 1);
293 | }
294 |
295 | /**
296 | * Verify whether the contents of an arbitrary string represents a floating point number.
297 | *
298 | * Takes PHP 7.4 numeric literal separators in numbers into account in the regex.
299 | *
300 | * @since 1.0.0
301 | *
302 | * @param string $textString Arbitrary string.
303 | *
304 | * @return bool
305 | */
306 | public static function isFloat($textString)
307 | {
308 | if (\is_string($textString) === false || $textString === '') {
309 | return false;
310 | }
311 |
312 | return (\preg_match(self::REGEX_FLOAT, $textString) === 1);
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Operators.php:
--------------------------------------------------------------------------------
1 | Note: value is irrelevant, only key is used.
39 | */
40 | private static $extraUnaryIndicators = [
41 | \T_STRING_CONCAT => true,
42 | \T_RETURN => true,
43 | \T_EXIT => true,
44 | \T_CONTINUE => true,
45 | \T_BREAK => true,
46 | \T_ECHO => true,
47 | \T_PRINT => true,
48 | \T_YIELD => true,
49 | \T_COMMA => true,
50 | \T_OPEN_PARENTHESIS => true,
51 | \T_OPEN_SQUARE_BRACKET => true,
52 | \T_OPEN_SHORT_ARRAY => true,
53 | \T_OPEN_CURLY_BRACKET => true,
54 | \T_COLON => true,
55 | \T_CASE => true,
56 | \T_FN_ARROW => true,
57 | \T_MATCH_ARROW => true,
58 | ];
59 |
60 | /**
61 | * Determine if the passed token is a reference operator.
62 | *
63 | * Main differences with the PHPCS version:
64 | * - Defensive coding against incorrect calls to this method.
65 | *
66 | * @see \PHP_CodeSniffer\Files\File::isReference() Original source.
67 | * @see \PHPCSUtils\BackCompat\BCFile::isReference() Cross-version compatible version of the original.
68 | *
69 | * @since 1.0.0
70 | *
71 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
72 | * @param int $stackPtr The position of the `T_BITWISE_AND` token.
73 | *
74 | * @return bool `TRUE` if the specified token position represents a reference.
75 | * `FALSE` if the token represents a bitwise operator.
76 | */
77 | public static function isReference(File $phpcsFile, $stackPtr)
78 | {
79 | $tokens = $phpcsFile->getTokens();
80 |
81 | if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_BITWISE_AND) {
82 | return false;
83 | }
84 |
85 | $tokenBefore = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
86 |
87 | if (isset(Collections::functionDeclarationTokens()[$tokens[$tokenBefore]['code']]) === true) {
88 | // Function returns a reference.
89 | return true;
90 | }
91 |
92 | if ($tokens[$tokenBefore]['code'] === \T_DOUBLE_ARROW) {
93 | // Inside a foreach loop or array assignment, this is a reference.
94 | return true;
95 | }
96 |
97 | if ($tokens[$tokenBefore]['code'] === \T_AS) {
98 | // Inside a foreach loop, this is a reference.
99 | return true;
100 | }
101 |
102 | if (isset(Tokens::$assignmentTokens[$tokens[$tokenBefore]['code']]) === true) {
103 | // This is directly after an assignment. It's a reference. Even if
104 | // it is part of an operation, the other tests will handle it.
105 | return true;
106 | }
107 |
108 | $tokenAfter = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
109 |
110 | if ($tokens[$tokenAfter]['code'] === \T_NEW) {
111 | return true;
112 | }
113 |
114 | $lastOpener = Parentheses::getLastOpener($phpcsFile, $stackPtr);
115 | if ($lastOpener !== false) {
116 | $lastOwner = Parentheses::getOwner($phpcsFile, $lastOpener);
117 |
118 | if (isset(Collections::functionDeclarationTokens()[$tokens[$lastOwner]['code']]) === true
119 | // As of PHPCS 4.x, `T_USE` is a parenthesis owner.
120 | || $tokens[$lastOwner]['code'] === \T_USE
121 | ) {
122 | $params = FunctionDeclarations::getParameters($phpcsFile, $lastOwner);
123 | foreach ($params as $param) {
124 | if ($param['reference_token'] === $stackPtr) {
125 | // Function parameter declared to be passed by reference.
126 | return true;
127 | }
128 | }
129 | }
130 | }
131 |
132 | /*
133 | * Pass by reference in function calls, assign by reference in arrays and
134 | * closure use by reference in PHPCS 3.x.
135 | */
136 | if ($tokens[$tokenBefore]['code'] === \T_OPEN_PARENTHESIS
137 | || $tokens[$tokenBefore]['code'] === \T_COMMA
138 | || $tokens[$tokenBefore]['code'] === \T_OPEN_SHORT_ARRAY
139 | ) {
140 | if ($tokens[$tokenAfter]['code'] === \T_VARIABLE) {
141 | return true;
142 | } else {
143 | $skip = Tokens::$emptyTokens;
144 | $skip += Collections::namespacedNameTokens();
145 | $skip += Collections::ooHierarchyKeywords();
146 | $skip[] = \T_DOUBLE_COLON;
147 |
148 | $nextSignificantAfter = $phpcsFile->findNext(
149 | $skip,
150 | ($stackPtr + 1),
151 | null,
152 | true
153 | );
154 | if ($tokens[$nextSignificantAfter]['code'] === \T_VARIABLE) {
155 | return true;
156 | }
157 | }
158 | }
159 |
160 | return false;
161 | }
162 |
163 | /**
164 | * Determine whether a T_MINUS/T_PLUS token is a unary operator.
165 | *
166 | * @since 1.0.0
167 | *
168 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
169 | * @param int $stackPtr The position of the plus/minus token.
170 | *
171 | * @return bool `TRUE` if the token passed is a unary operator.
172 | * `FALSE` otherwise, i.e. if the token is an arithmetic operator,
173 | * or if the token is not a `T_PLUS`/`T_MINUS` token.
174 | */
175 | public static function isUnaryPlusMinus(File $phpcsFile, $stackPtr)
176 | {
177 | $tokens = $phpcsFile->getTokens();
178 |
179 | if (isset($tokens[$stackPtr]) === false
180 | || ($tokens[$stackPtr]['code'] !== \T_PLUS
181 | && $tokens[$stackPtr]['code'] !== \T_MINUS)
182 | ) {
183 | return false;
184 | }
185 |
186 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
187 | if ($next === false) {
188 | // Live coding or parse error.
189 | return false;
190 | }
191 |
192 | if (isset(Tokens::$operators[$tokens[$next]['code']]) === true) {
193 | // Next token is an operator, so this is not a unary.
194 | return false;
195 | }
196 |
197 | $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
198 |
199 | /*
200 | * Check the preceeding token for an indication that this is not an arithmetic operation.
201 | */
202 | if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true
203 | || isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true
204 | || isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true
205 | || isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true
206 | || isset(Tokens::$castTokens[$tokens[$prev]['code']]) === true
207 | || isset(Collections::ternaryOperators()[$tokens[$prev]['code']]) === true
208 | || isset(self::$extraUnaryIndicators[$tokens[$prev]['code']]) === true
209 | ) {
210 | return true;
211 | }
212 |
213 | return false;
214 | }
215 |
216 | /**
217 | * Determine whether a ternary is a short ternary/elvis operator, i.e. without "middle".
218 | *
219 | * @since 1.0.0
220 | *
221 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
222 | * @param int $stackPtr The position of the ternary then/else
223 | * operator in the stack.
224 | *
225 | * @return bool `TRUE` if short ternary; or `FALSE` otherwise.
226 | */
227 | public static function isShortTernary(File $phpcsFile, $stackPtr)
228 | {
229 | $tokens = $phpcsFile->getTokens();
230 | if (isset($tokens[$stackPtr]) === false) {
231 | return false;
232 | }
233 |
234 | if ($tokens[$stackPtr]['code'] === \T_INLINE_THEN) {
235 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
236 | if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_INLINE_ELSE) {
237 | return true;
238 | }
239 | }
240 |
241 | if ($tokens[$stackPtr]['code'] === \T_INLINE_ELSE) {
242 | $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
243 | if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['code'] === \T_INLINE_THEN) {
244 | return true;
245 | }
246 | }
247 |
248 | // Not a ternary operator token.
249 | return false;
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Orthography.php:
--------------------------------------------------------------------------------
1 | An orthography is a set of conventions for writing a language. It includes norms of spelling,
19 | * > hyphenation, capitalization, word breaks, emphasis, and punctuation.
20 | * > Source: https://en.wikipedia.org/wiki/Orthography
21 | *
22 | * @since 1.0.0
23 | */
24 | final class Orthography
25 | {
26 |
27 | /**
28 | * Characters which are considered terminal points for a sentence.
29 | *
30 | * @link https://www.thepunctuationguide.com/terminal-points.html Punctuation guide on terminal points.
31 | *
32 | * @since 1.0.0
33 | *
34 | * @var string
35 | */
36 | const TERMINAL_POINTS = '.?!';
37 |
38 | /**
39 | * Check if the first character of an arbitrary text string is a capital letter.
40 | *
41 | * Letter characters which do not have a concept of lower/uppercase will
42 | * be accepted as correctly capitalized.
43 | *
44 | * @since 1.0.0
45 | *
46 | * @param string $textString The text string to examine.
47 | * This can be the contents of a text string token,
48 | * but also, for instance, a comment text.
49 | * Potential text delimiter quotes should be stripped
50 | * off a text string before passing it to this method.
51 | * Also see: {@see \PHPCSUtils\Utils\TextStrings::stripQuotes()}.
52 | *
53 | * @return bool `TRUE` when the first character is a capital letter or a letter
54 | * which doesn't have a concept of capitalization.
55 | * `FALSE` otherwise, including for non-letter characters.
56 | */
57 | public static function isFirstCharCapitalized($textString)
58 | {
59 | $textString = \ltrim($textString);
60 | return (\preg_match('`^[\p{Lu}\p{Lt}\p{Lo}]`u', $textString) > 0);
61 | }
62 |
63 | /**
64 | * Check if the first character of an arbitrary text string is a lowercase letter.
65 | *
66 | * @since 1.0.0
67 | *
68 | * @param string $textString The text string to examine.
69 | * This can be the contents of a text string token,
70 | * but also, for instance, a comment text.
71 | * Potential text delimiter quotes should be stripped
72 | * off a text string before passing it to this method.
73 | * Also see: {@see \PHPCSUtils\Utils\TextStrings::stripQuotes()}.
74 | *
75 | * @return bool `TRUE` when the first character is a lowercase letter.
76 | * `FALSE` otherwise, including for letters which don't have a concept of
77 | * capitalization and for non-letter characters.
78 | */
79 | public static function isFirstCharLowercase($textString)
80 | {
81 | $textString = \ltrim($textString);
82 | return (\preg_match('`^\p{Ll}`u', $textString) > 0);
83 | }
84 |
85 | /**
86 | * Check if the last character of an arbitrary text string is a valid punctuation character.
87 | *
88 | * @since 1.0.0
89 | *
90 | * @param string $textString The text string to examine.
91 | * This can be the contents of a text string token,
92 | * but also, for instance, a comment text.
93 | * Potential text delimiter quotes should be stripped
94 | * off a text string before passing it to this method.
95 | * Also see: {@see \PHPCSUtils\Utils\TextStrings::stripQuotes()}.
96 | * @param string $allowedChars Characters which are considered valid punctuation
97 | * to end the text string.
98 | * Defaults to `'.?!'`, i.e. a full stop, question mark
99 | * or exclamation mark.
100 | *
101 | * @return bool
102 | */
103 | public static function isLastCharPunctuation($textString, $allowedChars = self::TERMINAL_POINTS)
104 | {
105 | $encoding = Helper::getEncoding();
106 | $textString = \rtrim($textString);
107 |
108 | if (\function_exists('iconv_substr') === true) {
109 | $lastChar = \iconv_substr($textString, -1, 1, $encoding);
110 | } else {
111 | $lastChar = \substr($textString, -1);
112 | }
113 |
114 | if (\function_exists('iconv_strpos') === true) {
115 | return (\iconv_strpos($allowedChars, $lastChar, 0, $encoding) !== false);
116 | } else {
117 | return (\strpos($allowedChars, $lastChar) !== false);
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/Scopes.php:
--------------------------------------------------------------------------------
1 | $validScopes Array of token constants representing
37 | * the scopes considered valid.
38 | *
39 | * @return int|false Integer stack pointer to the valid direct scope; or `FALSE` if
40 | * no valid direct scope was found.
41 | */
42 | public static function validDirectScope(File $phpcsFile, $stackPtr, $validScopes)
43 | {
44 | $ptr = Conditions::getLastCondition($phpcsFile, $stackPtr);
45 |
46 | if ($ptr !== false) {
47 | $tokens = $phpcsFile->getTokens();
48 | $validScopes = (array) $validScopes;
49 |
50 | if (\in_array($tokens[$ptr]['code'], $validScopes, true) === true) {
51 | return $ptr;
52 | }
53 | }
54 |
55 | return false;
56 | }
57 |
58 | /**
59 | * Check whether a T_CONST token is a class/interface/trait/enum constant declaration.
60 | *
61 | * @since 1.0.0
62 | *
63 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
64 | * @param int $stackPtr The position in the stack of the
65 | * `T_CONST` token to verify.
66 | *
67 | * @return bool
68 | */
69 | public static function isOOConstant(File $phpcsFile, $stackPtr)
70 | {
71 | $tokens = $phpcsFile->getTokens();
72 |
73 | if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_CONST) {
74 | return false;
75 | }
76 |
77 | if (self::validDirectScope($phpcsFile, $stackPtr, Collections::ooConstantScopes()) !== false) {
78 | return true;
79 | }
80 |
81 | return false;
82 | }
83 |
84 | /**
85 | * Check whether a T_VARIABLE token is a class/trait property declaration.
86 | *
87 | * @since 1.0.0
88 | *
89 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
90 | * @param int $stackPtr The position in the stack of the
91 | * `T_VARIABLE` token to verify.
92 | *
93 | * @return bool
94 | */
95 | public static function isOOProperty(File $phpcsFile, $stackPtr)
96 | {
97 | $tokens = $phpcsFile->getTokens();
98 |
99 | if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_VARIABLE) {
100 | return false;
101 | }
102 |
103 | $scopePtr = self::validDirectScope($phpcsFile, $stackPtr, Collections::ooPropertyScopes());
104 | if ($scopePtr !== false) {
105 | // Make sure it's not a method parameter.
106 | $deepestOpen = Parentheses::getLastOpener($phpcsFile, $stackPtr);
107 | if ($deepestOpen === false
108 | || $deepestOpen < $scopePtr
109 | || Parentheses::isOwnerIn($phpcsFile, $deepestOpen, \T_FUNCTION) === false
110 | ) {
111 | return true;
112 | }
113 | }
114 |
115 | return false;
116 | }
117 |
118 | /**
119 | * Check whether a T_FUNCTION token is a class/interface/trait/enum method declaration.
120 | *
121 | * @since 1.0.0
122 | *
123 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
124 | * @param int $stackPtr The position in the stack of the
125 | * `T_FUNCTION` token to verify.
126 | *
127 | * @return bool
128 | */
129 | public static function isOOMethod(File $phpcsFile, $stackPtr)
130 | {
131 | $tokens = $phpcsFile->getTokens();
132 |
133 | if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_FUNCTION) {
134 | return false;
135 | }
136 |
137 | if (self::validDirectScope($phpcsFile, $stackPtr, Tokens::$ooScopeTokens) !== false) {
138 | return true;
139 | }
140 |
141 | return false;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/PHPCSUtils/Utils/TextStrings.php:
--------------------------------------------------------------------------------
1 | [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:\??->(?P>varname)|\[[^\]\'"\s]+\])?`';
54 |
55 | /**
56 | * Get the complete contents of a - potentially multi-line - text string.
57 | *
58 | * PHPCS tokenizes multi-line text strings with a single token for each line.
59 | * This method can be used to retrieve the text string as it would be received and
60 | * processed in PHP itself.
61 | *
62 | * This method is particularly useful for sniffs which examine the contents of text strings,
63 | * where the content matching might result in false positives/false negatives if the text
64 | * were to be examined line by line.
65 | *
66 | * @since 1.0.0
67 | *
68 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
69 | * @param int $stackPtr Pointer to the first text string token
70 | * of a - potentially multi-line - text string
71 | * or to a Nowdoc/Heredoc opener.
72 | * @param bool $stripQuotes Optional. Whether to strip text delimiter
73 | * quotes off the resulting text string.
74 | * Defaults to `true`.
75 | *
76 | * @return string The contents of the complete text string.
77 | *
78 | * @throws \PHPCSUtils\Exceptions\TypeError If the $stackPtr parameter is not an integer.
79 | * @throws \PHPCSUtils\Exceptions\OutOfBoundsStackPtr If the token passed does not exist in the $phpcsFile.
80 | * @throws \PHPCSUtils\Exceptions\UnexpectedTokenType If the token passed is not a valid text string token.
81 | * @throws \PHPCSUtils\Exceptions\ValueError If the specified token is not the _first_ token in
82 | * a text string.
83 | */
84 | public static function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQuotes = true)
85 | {
86 | $tokens = $phpcsFile->getTokens();
87 | $end = self::getEndOfCompleteTextString($phpcsFile, $stackPtr);
88 |
89 | $stripNewline = false;
90 | if ($tokens[$stackPtr]['code'] === \T_START_HEREDOC || $tokens[$stackPtr]['code'] === \T_START_NOWDOC) {
91 | $stripQuotes = false;
92 | $stripNewline = true;
93 | $stackPtr = ($stackPtr + 1);
94 | }
95 |
96 | $contents = GetTokensAsString::normal($phpcsFile, $stackPtr, $end);
97 |
98 | if ($stripNewline === true) {
99 | // Heredoc/nowdoc: strip the new line at the end of the string to emulate how PHP sees the string.
100 | $contents = \rtrim($contents, "\r\n");
101 | }
102 |
103 | if ($stripQuotes === true) {
104 | return self::stripQuotes($contents);
105 | }
106 |
107 | return $contents;
108 | }
109 |
110 | /**
111 | * Get the stack pointer to the end of a - potentially multi-line - text string.
112 | *
113 | * @see \PHPCSUtils\Utils\TextStrings::getCompleteTextString() Retrieve the contents of a complete - potentially
114 | * multi-line - text string.
115 | *
116 | * @since 1.0.0
117 | *
118 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
119 | * @param int $stackPtr Pointer to the first text string token
120 | * of a - potentially multi-line - text string
121 | * or to a Nowdoc/Heredoc opener.
122 | *
123 | * @return int Stack pointer to the last token in the text string.
124 | *
125 | * @throws \PHPCSUtils\Exceptions\TypeError If the $stackPtr parameter is not an integer.
126 | * @throws \PHPCSUtils\Exceptions\OutOfBoundsStackPtr If the token passed does not exist in the $phpcsFile.
127 | * @throws \PHPCSUtils\Exceptions\UnexpectedTokenType If the token passed is not a valid text string token.
128 | * @throws \PHPCSUtils\Exceptions\ValueError If the specified token is not the _first_ token in
129 | * a text string.
130 | */
131 | public static function getEndOfCompleteTextString(File $phpcsFile, $stackPtr)
132 | {
133 | $tokens = $phpcsFile->getTokens();
134 |
135 | if (\is_int($stackPtr) === false) {
136 | throw TypeError::create(2, '$stackPtr', 'integer', $stackPtr);
137 | }
138 |
139 | if (isset($tokens[$stackPtr]) === false) {
140 | throw OutOfBoundsStackPtr::create(2, '$stackPtr', $stackPtr);
141 | }
142 |
143 | if (isset(Collections::textStringStartTokens()[$tokens[$stackPtr]['code']]) === false) {
144 | $acceptedTokens = 'T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING or T_DOUBLE_QUOTED_STRING';
145 | throw UnexpectedTokenType::create(2, '$stackPtr', $acceptedTokens, $tokens[$stackPtr]['type']);
146 | }
147 |
148 | // Must be the start of a text string token.
149 | if (isset(Tokens::$stringTokens[$tokens[$stackPtr]['code']]) === true) {
150 | $prev = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
151 | if ($tokens[$stackPtr]['code'] === $tokens[$prev]['code']) {
152 | throw ValueError::create(2, '$stackPtr', 'must be the start of the text string');
153 | }
154 | }
155 |
156 | if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) {
157 | return Cache::get($phpcsFile, __METHOD__, $stackPtr);
158 | }
159 |
160 | switch ($tokens[$stackPtr]['code']) {
161 | case \T_START_HEREDOC:
162 | $targetType = \T_HEREDOC;
163 | $current = ($stackPtr + 1);
164 | break;
165 |
166 | case \T_START_NOWDOC:
167 | $targetType = \T_NOWDOC;
168 | $current = ($stackPtr + 1);
169 | break;
170 |
171 | default:
172 | $targetType = $tokens[$stackPtr]['code'];
173 | $current = $stackPtr;
174 | break;
175 | }
176 |
177 | while (isset($tokens[$current]) && $tokens[$current]['code'] === $targetType) {
178 | ++$current;
179 | }
180 |
181 | $lastPtr = ($current - 1);
182 |
183 | Cache::set($phpcsFile, __METHOD__, $stackPtr, $lastPtr);
184 | return $lastPtr;
185 | }
186 |
187 | /**
188 | * Strip text delimiter quotes from an arbitrary text string.
189 | *
190 | * Intended for use with the "content" of a `T_CONSTANT_ENCAPSED_STRING` / `T_DOUBLE_QUOTED_STRING`.
191 | *
192 | * - Prevents stripping mis-matched quotes.
193 | * - Prevents stripping quotes from the textual content of the text string.
194 | *
195 | * @since 1.0.0
196 | *
197 | * @param string $textString The raw text string.
198 | *
199 | * @return string Text string without quotes around it.
200 | */
201 | public static function stripQuotes($textString)
202 | {
203 | return \preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $textString);
204 | }
205 |
206 | /**
207 | * Get the embedded variables/expressions from an arbitrary string.
208 | *
209 | * Note: this function gets the complete variables/expressions _as they are embedded_,
210 | * i.e. including potential curly brace wrappers, array access, method calls etc.
211 | *
212 | * @since 1.0.0
213 | *
214 | * @param string $text The contents of a T_DOUBLE_QUOTED_STRING or T_HEREDOC token.
215 | *
216 | * @return array Array of encountered variable names/expressions with the offset at which
217 | * the variable/expression was found in the string, as the key.
218 | */
219 | public static function getEmbeds($text)
220 | {
221 | return self::getStripEmbeds($text)['embeds'];
222 | }
223 |
224 | /**
225 | * Strip embedded variables/expressions from an arbitrary string.
226 | *
227 | * @since 1.0.0
228 | *
229 | * @param string $text The contents of a T_DOUBLE_QUOTED_STRING or T_HEREDOC token.
230 | *
231 | * @return string String without variables/expressions in it.
232 | */
233 | public static function stripEmbeds($text)
234 | {
235 | return self::getStripEmbeds($text)['remaining'];
236 | }
237 |
238 | /**
239 | * Split an arbitrary text string into embedded variables/expressions and remaining text.
240 | *
241 | * PHP contains four types of embedding syntaxes:
242 | * 1. Directly embedding variables ("$foo");
243 | * 2. Braces outside the variable ("{$foo}");
244 | * 3. Braces after the dollar sign ("${foo}");
245 | * 4. Variable variables ("${expr}", equivalent to (string) ${expr}).
246 | *
247 | * Type 3 and 4 are deprecated as of PHP 8.2 and will be removed in PHP 9.0.
248 | *
249 | * This method handles all types of embeds, including recognition of whether an embed is escaped or not.
250 | *
251 | * @link https://www.php.net/language.types.string#language.types.string.parsing PHP Manual on string parsing
252 | * @link https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation PHP RFC on deprecating select
253 | * string interpolation syntaxes
254 | *
255 | * @since 1.0.0
256 | *
257 | * @param string $text The contents of a T_DOUBLE_QUOTED_STRING or T_HEREDOC token.
258 | *
259 | * @return array Array containing two values:
260 | * 1. An array containing a string representation of each embed encountered.
261 | * The keys in this array are the integer offset within the original string
262 | * where the embed was found.
263 | * 2. The textual contents, embeds stripped out of it.
264 | * The format of the array return value is:
265 | * ```php
266 | * array(
267 | * 'embeds' => array,
268 | * 'remaining' => string,
269 | * )
270 | * ```
271 | */
272 | public static function getStripEmbeds($text)
273 | {
274 | if (\strpos($text, '$') === false) {
275 | return [
276 | 'embeds' => [],
277 | 'remaining' => $text,
278 | ];
279 | }
280 |
281 | $textHash = \md5($text);
282 | if (NoFileCache::isCached(__METHOD__, $textHash) === true) {
283 | return NoFileCache::get(__METHOD__, $textHash);
284 | }
285 |
286 | $offset = 0;
287 | $strLen = \strlen($text); // Use iconv ?
288 | $stripped = '';
289 | $variables = [];
290 |
291 | while (\preg_match(self::START_OF_EMBED, $text, $matches, \PREG_OFFSET_CAPTURE, $offset) === 1) {
292 | $stripped .= \substr($text, $offset, ($matches[2][1] - $offset));
293 |
294 | $matchedExpr = $matches[2][0];
295 | $matchedOffset = $matches[2][1];
296 | $braces = \substr_count($matchedExpr, '{');
297 | $newOffset = $matchedOffset + \strlen($matchedExpr);
298 |
299 | if ($braces === 0) {
300 | /*
301 | * Type 1: simple variable embed.
302 | * Regex will always return a match due to the look ahead in the above regex.
303 | */
304 | \preg_match(self::TYPE1_EMBED_AFTER_DOLLAR, $text, $endMatch, 0, $newOffset);
305 | $matchedExpr .= $endMatch[0];
306 | $variables[$matchedOffset] = $matchedExpr;
307 | $offset = $newOffset + \strlen($endMatch[0]);
308 | continue;
309 | }
310 |
311 | for (; $newOffset < $strLen; $newOffset++) {
312 | if ($text[$newOffset] === '{') {
313 | ++$braces;
314 | continue;
315 | }
316 |
317 | if ($text[$newOffset] === '}') {
318 | --$braces;
319 | if ($braces === 0) {
320 | $matchedExpr = \substr($text, $matchedOffset, (1 + $newOffset - $matchedOffset));
321 | $variables[$matchedOffset] = $matchedExpr;
322 | $offset = ($newOffset + 1);
323 | break;
324 | }
325 | }
326 | }
327 | }
328 |
329 | if ($offset < $strLen) {
330 | // Add the end of the string.
331 | $stripped .= \substr($text, $offset);
332 | }
333 |
334 | $returnValue = [
335 | 'embeds' => $variables,
336 | 'remaining' => $stripped,
337 | ];
338 |
339 | NoFileCache::set(__METHOD__, $textHash, $returnValue);
340 | return $returnValue;
341 | }
342 | }
343 |
--------------------------------------------------------------------------------
/PHPCSUtils/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Utility methods for external PHPCS standards.
4 |
5 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "phpcsstandards/phpcsutils",
3 | "description" : "A suite of utility functions for use with PHP_CodeSniffer",
4 | "type" : "phpcodesniffer-standard",
5 | "keywords" : [ "phpcs", "phpcbf", "standards", "static analysis", "php_codesniffer", "phpcs3", "tokens", "utility", "phpcodesniffer-standard" ],
6 | "license" : "LGPL-3.0-or-later",
7 | "homepage": "https://phpcsutils.com/",
8 | "authors" : [
9 | {
10 | "name" : "Juliette Reinders Folmer",
11 | "role" : "lead",
12 | "homepage" : "https://github.com/jrfnl"
13 | },
14 | {
15 | "name" : "Contributors",
16 | "homepage" : "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
17 | }
18 | ],
19 | "support" : {
20 | "issues" : "https://github.com/PHPCSStandards/PHPCSUtils/issues",
21 | "source" : "https://github.com/PHPCSStandards/PHPCSUtils",
22 | "docs" : "https://phpcsutils.com/",
23 | "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy"
24 | },
25 | "require" : {
26 | "php" : ">=5.4",
27 | "squizlabs/php_codesniffer" : "^3.13.0 || 4.0.x-dev@dev",
28 | "dealerdirect/phpcodesniffer-composer-installer" : "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0"
29 | },
30 | "require-dev" : {
31 | "ext-filter": "*",
32 | "phpcsstandards/phpcsdevcs": "^1.1.6",
33 | "php-parallel-lint/php-parallel-lint": "^1.4.0",
34 | "php-parallel-lint/php-console-highlighter": "^1.0",
35 | "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
36 | },
37 | "minimum-stability": "dev",
38 | "prefer-stable": true,
39 | "autoload": {
40 | "classmap": ["PHPCSUtils/"]
41 | },
42 | "autoload-dev" : {
43 | "psr-4": {
44 | "PHPCSUtils\\Tests\\": "Tests/",
45 | "PHPCSUtils\\GHPages\\": ".github/GHPages/"
46 | }
47 | },
48 | "extra": {
49 | "branch-alias": {
50 | "dev-stable": "1.x-dev",
51 | "dev-develop": "1.x-dev"
52 | }
53 | },
54 | "scripts" : {
55 | "lint-lt72": [
56 | "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php --exclude vendor --exclude .git --exclude .github/GHPages"
57 | ],
58 | "lint": [
59 | "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git"
60 | ],
61 | "checkcs": [
62 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs"
63 | ],
64 | "fixcs": [
65 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf"
66 | ],
67 | "test": [
68 | "@php ./vendor/phpunit/phpunit/phpunit --no-coverage"
69 | ],
70 | "test10": [
71 | "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist --no-coverage"
72 | ],
73 | "coverage": [
74 | "@php ./vendor/phpunit/phpunit/phpunit"
75 | ],
76 | "coverage10": [
77 | "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist"
78 | ],
79 | "coverage-local": [
80 | "@php ./vendor/phpunit/phpunit/phpunit --coverage-html ./build/coverage-html"
81 | ],
82 | "coverage-local10": [
83 | "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist --coverage-html ./build/coverage-html"
84 | ]
85 | },
86 | "config": {
87 | "allow-plugins": {
88 | "dealerdirect/phpcodesniffer-composer-installer": true
89 | },
90 | "lock": false
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/phpcsutils-autoload.php:
--------------------------------------------------------------------------------
1 | = 3.1.0 and uses the PHPCS
8 | * native unit test framework, this file does not need to be included.
9 | *
10 | * - If an external standard uses its own unit test setup, this file should
11 | * be included from the unit test bootstrap file.
12 | *
13 | * - If an external standard uses the PHPCSUtils {@see PHPCSUtils\TestUtils\UtilityMethodTestCase}
14 | * class to test their own utility methods, this file should be included from
15 | * the unit test bootstrap file.
16 | *
17 | * @package PHPCSUtils
18 | * @copyright 2019-2020 PHPCSUtils Contributors
19 | * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
20 | * @link https://github.com/PHPCSStandards/PHPCSUtils
21 | *
22 | * @since 1.0.0
23 | */
24 |
25 | if (defined('PHPCSUTILS_AUTOLOAD') === false) {
26 | /*
27 | * Register an autoloader.
28 | *
29 | * External PHPCS standards which have their own unit test suite
30 | * should include this file in their test runner bootstrap.
31 | */
32 | spl_autoload_register(function ($fqClassName) {
33 | // Only try & load our own classes.
34 | if (stripos($fqClassName, 'PHPCSUtils\\') !== 0) {
35 | return;
36 | }
37 |
38 | $file = realpath(__DIR__) . DIRECTORY_SEPARATOR . strtr($fqClassName, '\\', DIRECTORY_SEPARATOR) . '.php';
39 |
40 | if (file_exists($file)) {
41 | include_once $file;
42 | }
43 | });
44 |
45 | define('PHPCSUTILS_AUTOLOAD', true);
46 | }
47 |
48 | if (defined('PHPCSUTILS_PHPUNIT_ALIASES_SET') === false) {
49 | /*
50 | * Alias the PHPUnit 4/5 TestCase class to its PHPUnit 6+ name.
51 | *
52 | * This allows both the PHPCSUtils native unit tests as well as the
53 | * `UtilityMethodTestCase` class to work cross-version with PHPUnit
54 | * below 6.x and above.
55 | *
56 | * {@internal The `class_exists` wrappers are needed to play nice with
57 | * PHPUnit bootstrap files of external standards which may be creating
58 | * cross-version compatibility in a similar manner.}}
59 | */
60 | if (class_exists('PHPUnit_Framework_TestCase') === true
61 | && class_exists('PHPUnit\Framework\TestCase') === false
62 | ) {
63 | class_alias('PHPUnit_Framework_TestCase', 'PHPUnit\Framework\TestCase');
64 | }
65 |
66 | define('PHPCSUTILS_PHPUNIT_ALIASES_SET', true);
67 | }
68 |
--------------------------------------------------------------------------------