Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * Processes pattern strings and checks that the code conforms to the pattern.
|
Chris@17
|
4 *
|
Chris@17
|
5 * @author Greg Sherwood <gsherwood@squiz.net>
|
Chris@17
|
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
Chris@17
|
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
Chris@17
|
8 */
|
Chris@17
|
9
|
Chris@17
|
10 namespace PHP_CodeSniffer\Sniffs;
|
Chris@17
|
11
|
Chris@17
|
12 use PHP_CodeSniffer\Files\File;
|
Chris@17
|
13 use PHP_CodeSniffer\Util\Tokens;
|
Chris@17
|
14 use PHP_CodeSniffer\Tokenizers\PHP;
|
Chris@17
|
15 use PHP_CodeSniffer\Exceptions\RuntimeException;
|
Chris@17
|
16
|
Chris@17
|
17 abstract class AbstractPatternSniff implements Sniff
|
Chris@17
|
18 {
|
Chris@17
|
19
|
Chris@17
|
20 /**
|
Chris@17
|
21 * If true, comments will be ignored if they are found in the code.
|
Chris@17
|
22 *
|
Chris@17
|
23 * @var boolean
|
Chris@17
|
24 */
|
Chris@17
|
25 public $ignoreComments = false;
|
Chris@17
|
26
|
Chris@17
|
27 /**
|
Chris@17
|
28 * The current file being checked.
|
Chris@17
|
29 *
|
Chris@17
|
30 * @var string
|
Chris@17
|
31 */
|
Chris@17
|
32 protected $currFile = '';
|
Chris@17
|
33
|
Chris@17
|
34 /**
|
Chris@17
|
35 * The parsed patterns array.
|
Chris@17
|
36 *
|
Chris@17
|
37 * @var array
|
Chris@17
|
38 */
|
Chris@17
|
39 private $parsedPatterns = [];
|
Chris@17
|
40
|
Chris@17
|
41 /**
|
Chris@17
|
42 * Tokens that this sniff wishes to process outside of the patterns.
|
Chris@17
|
43 *
|
Chris@17
|
44 * @var int[]
|
Chris@17
|
45 * @see registerSupplementary()
|
Chris@17
|
46 * @see processSupplementary()
|
Chris@17
|
47 */
|
Chris@17
|
48 private $supplementaryTokens = [];
|
Chris@17
|
49
|
Chris@17
|
50 /**
|
Chris@17
|
51 * Positions in the stack where errors have occurred.
|
Chris@17
|
52 *
|
Chris@17
|
53 * @var array<int, bool>
|
Chris@17
|
54 */
|
Chris@17
|
55 private $errorPos = [];
|
Chris@17
|
56
|
Chris@17
|
57
|
Chris@17
|
58 /**
|
Chris@17
|
59 * Constructs a AbstractPatternSniff.
|
Chris@17
|
60 *
|
Chris@17
|
61 * @param boolean $ignoreComments If true, comments will be ignored.
|
Chris@17
|
62 */
|
Chris@17
|
63 public function __construct($ignoreComments=null)
|
Chris@17
|
64 {
|
Chris@17
|
65 // This is here for backwards compatibility.
|
Chris@17
|
66 if ($ignoreComments !== null) {
|
Chris@17
|
67 $this->ignoreComments = $ignoreComments;
|
Chris@17
|
68 }
|
Chris@17
|
69
|
Chris@17
|
70 $this->supplementaryTokens = $this->registerSupplementary();
|
Chris@17
|
71
|
Chris@17
|
72 }//end __construct()
|
Chris@17
|
73
|
Chris@17
|
74
|
Chris@17
|
75 /**
|
Chris@17
|
76 * Registers the tokens to listen to.
|
Chris@17
|
77 *
|
Chris@17
|
78 * Classes extending <i>AbstractPatternTest</i> should implement the
|
Chris@17
|
79 * <i>getPatterns()</i> method to register the patterns they wish to test.
|
Chris@17
|
80 *
|
Chris@17
|
81 * @return int[]
|
Chris@17
|
82 * @see process()
|
Chris@17
|
83 */
|
Chris@17
|
84 final public function register()
|
Chris@17
|
85 {
|
Chris@17
|
86 $listenTypes = [];
|
Chris@17
|
87 $patterns = $this->getPatterns();
|
Chris@17
|
88
|
Chris@17
|
89 foreach ($patterns as $pattern) {
|
Chris@17
|
90 $parsedPattern = $this->parse($pattern);
|
Chris@17
|
91
|
Chris@17
|
92 // Find a token position in the pattern that we can use
|
Chris@17
|
93 // for a listener token.
|
Chris@17
|
94 $pos = $this->getListenerTokenPos($parsedPattern);
|
Chris@17
|
95 $tokenType = $parsedPattern[$pos]['token'];
|
Chris@17
|
96 $listenTypes[] = $tokenType;
|
Chris@17
|
97
|
Chris@17
|
98 $patternArray = [
|
Chris@17
|
99 'listen_pos' => $pos,
|
Chris@17
|
100 'pattern' => $parsedPattern,
|
Chris@17
|
101 'pattern_code' => $pattern,
|
Chris@17
|
102 ];
|
Chris@17
|
103
|
Chris@17
|
104 if (isset($this->parsedPatterns[$tokenType]) === false) {
|
Chris@17
|
105 $this->parsedPatterns[$tokenType] = [];
|
Chris@17
|
106 }
|
Chris@17
|
107
|
Chris@17
|
108 $this->parsedPatterns[$tokenType][] = $patternArray;
|
Chris@17
|
109 }//end foreach
|
Chris@17
|
110
|
Chris@17
|
111 return array_unique(array_merge($listenTypes, $this->supplementaryTokens));
|
Chris@17
|
112
|
Chris@17
|
113 }//end register()
|
Chris@17
|
114
|
Chris@17
|
115
|
Chris@17
|
116 /**
|
Chris@17
|
117 * Returns the token types that the specified pattern is checking for.
|
Chris@17
|
118 *
|
Chris@17
|
119 * Returned array is in the format:
|
Chris@17
|
120 * <code>
|
Chris@17
|
121 * array(
|
Chris@17
|
122 * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
|
Chris@17
|
123 * // should occur in the pattern.
|
Chris@17
|
124 * );
|
Chris@17
|
125 * </code>
|
Chris@17
|
126 *
|
Chris@17
|
127 * @param array $pattern The parsed pattern to find the acquire the token
|
Chris@17
|
128 * types from.
|
Chris@17
|
129 *
|
Chris@17
|
130 * @return array<int, int>
|
Chris@17
|
131 */
|
Chris@17
|
132 private function getPatternTokenTypes($pattern)
|
Chris@17
|
133 {
|
Chris@17
|
134 $tokenTypes = [];
|
Chris@17
|
135 foreach ($pattern as $pos => $patternInfo) {
|
Chris@17
|
136 if ($patternInfo['type'] === 'token') {
|
Chris@17
|
137 if (isset($tokenTypes[$patternInfo['token']]) === false) {
|
Chris@17
|
138 $tokenTypes[$patternInfo['token']] = $pos;
|
Chris@17
|
139 }
|
Chris@17
|
140 }
|
Chris@17
|
141 }
|
Chris@17
|
142
|
Chris@17
|
143 return $tokenTypes;
|
Chris@17
|
144
|
Chris@17
|
145 }//end getPatternTokenTypes()
|
Chris@17
|
146
|
Chris@17
|
147
|
Chris@17
|
148 /**
|
Chris@17
|
149 * Returns the position in the pattern that this test should register as
|
Chris@17
|
150 * a listener for the pattern.
|
Chris@17
|
151 *
|
Chris@17
|
152 * @param array $pattern The pattern to acquire the listener for.
|
Chris@17
|
153 *
|
Chris@17
|
154 * @return int The position in the pattern that this test should register
|
Chris@17
|
155 * as the listener.
|
Chris@17
|
156 * @throws RuntimeException If we could not determine a token to listen for.
|
Chris@17
|
157 */
|
Chris@17
|
158 private function getListenerTokenPos($pattern)
|
Chris@17
|
159 {
|
Chris@17
|
160 $tokenTypes = $this->getPatternTokenTypes($pattern);
|
Chris@17
|
161 $tokenCodes = array_keys($tokenTypes);
|
Chris@17
|
162 $token = Tokens::getHighestWeightedToken($tokenCodes);
|
Chris@17
|
163
|
Chris@17
|
164 // If we could not get a token.
|
Chris@17
|
165 if ($token === false) {
|
Chris@17
|
166 $error = 'Could not determine a token to listen for';
|
Chris@17
|
167 throw new RuntimeException($error);
|
Chris@17
|
168 }
|
Chris@17
|
169
|
Chris@17
|
170 return $tokenTypes[$token];
|
Chris@17
|
171
|
Chris@17
|
172 }//end getListenerTokenPos()
|
Chris@17
|
173
|
Chris@17
|
174
|
Chris@17
|
175 /**
|
Chris@17
|
176 * Processes the test.
|
Chris@17
|
177 *
|
Chris@17
|
178 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
|
Chris@17
|
179 * token occurred.
|
Chris@17
|
180 * @param int $stackPtr The position in the tokens stack
|
Chris@17
|
181 * where the listening token type
|
Chris@17
|
182 * was found.
|
Chris@17
|
183 *
|
Chris@17
|
184 * @return void
|
Chris@17
|
185 * @see register()
|
Chris@17
|
186 */
|
Chris@17
|
187 final public function process(File $phpcsFile, $stackPtr)
|
Chris@17
|
188 {
|
Chris@17
|
189 $file = $phpcsFile->getFilename();
|
Chris@17
|
190 if ($this->currFile !== $file) {
|
Chris@17
|
191 // We have changed files, so clean up.
|
Chris@17
|
192 $this->errorPos = [];
|
Chris@17
|
193 $this->currFile = $file;
|
Chris@17
|
194 }
|
Chris@17
|
195
|
Chris@17
|
196 $tokens = $phpcsFile->getTokens();
|
Chris@17
|
197
|
Chris@17
|
198 if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens) === true) {
|
Chris@17
|
199 $this->processSupplementary($phpcsFile, $stackPtr);
|
Chris@17
|
200 }
|
Chris@17
|
201
|
Chris@17
|
202 $type = $tokens[$stackPtr]['code'];
|
Chris@17
|
203
|
Chris@17
|
204 // If the type is not set, then it must have been a token registered
|
Chris@17
|
205 // with registerSupplementary().
|
Chris@17
|
206 if (isset($this->parsedPatterns[$type]) === false) {
|
Chris@17
|
207 return;
|
Chris@17
|
208 }
|
Chris@17
|
209
|
Chris@17
|
210 $allErrors = [];
|
Chris@17
|
211
|
Chris@17
|
212 // Loop over each pattern that is listening to the current token type
|
Chris@17
|
213 // that we are processing.
|
Chris@17
|
214 foreach ($this->parsedPatterns[$type] as $patternInfo) {
|
Chris@17
|
215 // If processPattern returns false, then the pattern that we are
|
Chris@17
|
216 // checking the code with must not be designed to check that code.
|
Chris@17
|
217 $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr);
|
Chris@17
|
218 if ($errors === false) {
|
Chris@17
|
219 // The pattern didn't match.
|
Chris@17
|
220 continue;
|
Chris@17
|
221 } else if (empty($errors) === true) {
|
Chris@17
|
222 // The pattern matched, but there were no errors.
|
Chris@17
|
223 break;
|
Chris@17
|
224 }
|
Chris@17
|
225
|
Chris@17
|
226 foreach ($errors as $stackPtr => $error) {
|
Chris@17
|
227 if (isset($this->errorPos[$stackPtr]) === false) {
|
Chris@17
|
228 $this->errorPos[$stackPtr] = true;
|
Chris@17
|
229 $allErrors[$stackPtr] = $error;
|
Chris@17
|
230 }
|
Chris@17
|
231 }
|
Chris@17
|
232 }
|
Chris@17
|
233
|
Chris@17
|
234 foreach ($allErrors as $stackPtr => $error) {
|
Chris@17
|
235 $phpcsFile->addError($error, $stackPtr, 'Found');
|
Chris@17
|
236 }
|
Chris@17
|
237
|
Chris@17
|
238 }//end process()
|
Chris@17
|
239
|
Chris@17
|
240
|
Chris@17
|
241 /**
|
Chris@17
|
242 * Processes the pattern and verifies the code at $stackPtr.
|
Chris@17
|
243 *
|
Chris@17
|
244 * @param array $patternInfo Information about the pattern used
|
Chris@17
|
245 * for checking, which includes are
|
Chris@17
|
246 * parsed token representation of the
|
Chris@17
|
247 * pattern.
|
Chris@17
|
248 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
|
Chris@17
|
249 * token occurred.
|
Chris@17
|
250 * @param int $stackPtr The position in the tokens stack where
|
Chris@17
|
251 * the listening token type was found.
|
Chris@17
|
252 *
|
Chris@17
|
253 * @return array
|
Chris@17
|
254 */
|
Chris@17
|
255 protected function processPattern($patternInfo, File $phpcsFile, $stackPtr)
|
Chris@17
|
256 {
|
Chris@17
|
257 $tokens = $phpcsFile->getTokens();
|
Chris@17
|
258 $pattern = $patternInfo['pattern'];
|
Chris@17
|
259 $patternCode = $patternInfo['pattern_code'];
|
Chris@17
|
260 $errors = [];
|
Chris@17
|
261 $found = '';
|
Chris@17
|
262
|
Chris@17
|
263 $ignoreTokens = [T_WHITESPACE];
|
Chris@17
|
264 if ($this->ignoreComments === true) {
|
Chris@17
|
265 $ignoreTokens
|
Chris@17
|
266 = array_merge($ignoreTokens, Tokens::$commentTokens);
|
Chris@17
|
267 }
|
Chris@17
|
268
|
Chris@17
|
269 $origStackPtr = $stackPtr;
|
Chris@17
|
270 $hasError = false;
|
Chris@17
|
271
|
Chris@17
|
272 if ($patternInfo['listen_pos'] > 0) {
|
Chris@17
|
273 $stackPtr--;
|
Chris@17
|
274
|
Chris@17
|
275 for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
|
Chris@17
|
276 if ($pattern[$i]['type'] === 'token') {
|
Chris@17
|
277 if ($pattern[$i]['token'] === T_WHITESPACE) {
|
Chris@17
|
278 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
|
Chris@17
|
279 $found = $tokens[$stackPtr]['content'].$found;
|
Chris@17
|
280 }
|
Chris@17
|
281
|
Chris@17
|
282 // Only check the size of the whitespace if this is not
|
Chris@17
|
283 // the first token. We don't care about the size of
|
Chris@17
|
284 // leading whitespace, just that there is some.
|
Chris@17
|
285 if ($i !== 0) {
|
Chris@17
|
286 if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
|
Chris@17
|
287 $hasError = true;
|
Chris@17
|
288 }
|
Chris@17
|
289 }
|
Chris@17
|
290 } else {
|
Chris@17
|
291 // Check to see if this important token is the same as the
|
Chris@17
|
292 // previous important token in the pattern. If it is not,
|
Chris@17
|
293 // then the pattern cannot be for this piece of code.
|
Chris@17
|
294 $prev = $phpcsFile->findPrevious(
|
Chris@17
|
295 $ignoreTokens,
|
Chris@17
|
296 $stackPtr,
|
Chris@17
|
297 null,
|
Chris@17
|
298 true
|
Chris@17
|
299 );
|
Chris@17
|
300
|
Chris@17
|
301 if ($prev === false
|
Chris@17
|
302 || $tokens[$prev]['code'] !== $pattern[$i]['token']
|
Chris@17
|
303 ) {
|
Chris@17
|
304 return false;
|
Chris@17
|
305 }
|
Chris@17
|
306
|
Chris@17
|
307 // If we skipped past some whitespace tokens, then add them
|
Chris@17
|
308 // to the found string.
|
Chris@17
|
309 $tokenContent = $phpcsFile->getTokensAsString(
|
Chris@17
|
310 ($prev + 1),
|
Chris@17
|
311 ($stackPtr - $prev - 1)
|
Chris@17
|
312 );
|
Chris@17
|
313
|
Chris@17
|
314 $found = $tokens[$prev]['content'].$tokenContent.$found;
|
Chris@17
|
315
|
Chris@17
|
316 if (isset($pattern[($i - 1)]) === true
|
Chris@17
|
317 && $pattern[($i - 1)]['type'] === 'skip'
|
Chris@17
|
318 ) {
|
Chris@17
|
319 $stackPtr = $prev;
|
Chris@17
|
320 } else {
|
Chris@17
|
321 $stackPtr = ($prev - 1);
|
Chris@17
|
322 }
|
Chris@17
|
323 }//end if
|
Chris@17
|
324 } else if ($pattern[$i]['type'] === 'skip') {
|
Chris@17
|
325 // Skip to next piece of relevant code.
|
Chris@17
|
326 if ($pattern[$i]['to'] === 'parenthesis_closer') {
|
Chris@17
|
327 $to = 'parenthesis_opener';
|
Chris@17
|
328 } else {
|
Chris@17
|
329 $to = 'scope_opener';
|
Chris@17
|
330 }
|
Chris@17
|
331
|
Chris@17
|
332 // Find the previous opener.
|
Chris@17
|
333 $next = $phpcsFile->findPrevious(
|
Chris@17
|
334 $ignoreTokens,
|
Chris@17
|
335 $stackPtr,
|
Chris@17
|
336 null,
|
Chris@17
|
337 true
|
Chris@17
|
338 );
|
Chris@17
|
339
|
Chris@17
|
340 if ($next === false || isset($tokens[$next][$to]) === false) {
|
Chris@17
|
341 // If there was not opener, then we must be
|
Chris@17
|
342 // using the wrong pattern.
|
Chris@17
|
343 return false;
|
Chris@17
|
344 }
|
Chris@17
|
345
|
Chris@17
|
346 if ($to === 'parenthesis_opener') {
|
Chris@17
|
347 $found = '{'.$found;
|
Chris@17
|
348 } else {
|
Chris@17
|
349 $found = '('.$found;
|
Chris@17
|
350 }
|
Chris@17
|
351
|
Chris@17
|
352 $found = '...'.$found;
|
Chris@17
|
353
|
Chris@17
|
354 // Skip to the opening token.
|
Chris@17
|
355 $stackPtr = ($tokens[$next][$to] - 1);
|
Chris@17
|
356 } else if ($pattern[$i]['type'] === 'string') {
|
Chris@17
|
357 $found = 'abc';
|
Chris@17
|
358 } else if ($pattern[$i]['type'] === 'newline') {
|
Chris@17
|
359 if ($this->ignoreComments === true
|
Chris@17
|
360 && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
|
Chris@17
|
361 ) {
|
Chris@17
|
362 $startComment = $phpcsFile->findPrevious(
|
Chris@17
|
363 Tokens::$commentTokens,
|
Chris@17
|
364 ($stackPtr - 1),
|
Chris@17
|
365 null,
|
Chris@17
|
366 true
|
Chris@17
|
367 );
|
Chris@17
|
368
|
Chris@17
|
369 if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
|
Chris@17
|
370 $startComment++;
|
Chris@17
|
371 }
|
Chris@17
|
372
|
Chris@17
|
373 $tokenContent = $phpcsFile->getTokensAsString(
|
Chris@17
|
374 $startComment,
|
Chris@17
|
375 ($stackPtr - $startComment + 1)
|
Chris@17
|
376 );
|
Chris@17
|
377
|
Chris@17
|
378 $found = $tokenContent.$found;
|
Chris@17
|
379 $stackPtr = ($startComment - 1);
|
Chris@17
|
380 }
|
Chris@17
|
381
|
Chris@17
|
382 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
|
Chris@17
|
383 if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
|
Chris@17
|
384 $found = $tokens[$stackPtr]['content'].$found;
|
Chris@17
|
385
|
Chris@17
|
386 // This may just be an indent that comes after a newline
|
Chris@17
|
387 // so check the token before to make sure. If it is a newline, we
|
Chris@17
|
388 // can ignore the error here.
|
Chris@17
|
389 if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
|
Chris@17
|
390 && ($this->ignoreComments === true
|
Chris@17
|
391 && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
|
Chris@17
|
392 ) {
|
Chris@17
|
393 $hasError = true;
|
Chris@17
|
394 } else {
|
Chris@17
|
395 $stackPtr--;
|
Chris@17
|
396 }
|
Chris@17
|
397 } else {
|
Chris@17
|
398 $found = 'EOL'.$found;
|
Chris@17
|
399 }
|
Chris@17
|
400 } else {
|
Chris@17
|
401 $found = $tokens[$stackPtr]['content'].$found;
|
Chris@17
|
402 $hasError = true;
|
Chris@17
|
403 }//end if
|
Chris@17
|
404
|
Chris@17
|
405 if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
|
Chris@17
|
406 // Make sure they only have 1 newline.
|
Chris@17
|
407 $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
|
Chris@17
|
408 if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
|
Chris@17
|
409 $hasError = true;
|
Chris@17
|
410 }
|
Chris@17
|
411 }
|
Chris@17
|
412 }//end if
|
Chris@17
|
413 }//end for
|
Chris@17
|
414 }//end if
|
Chris@17
|
415
|
Chris@17
|
416 $stackPtr = $origStackPtr;
|
Chris@17
|
417 $lastAddedStackPtr = null;
|
Chris@17
|
418 $patternLen = count($pattern);
|
Chris@17
|
419
|
Chris@17
|
420 for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
|
Chris@17
|
421 if (isset($tokens[$stackPtr]) === false) {
|
Chris@17
|
422 break;
|
Chris@17
|
423 }
|
Chris@17
|
424
|
Chris@17
|
425 if ($pattern[$i]['type'] === 'token') {
|
Chris@17
|
426 if ($pattern[$i]['token'] === T_WHITESPACE) {
|
Chris@17
|
427 if ($this->ignoreComments === true) {
|
Chris@17
|
428 // If we are ignoring comments, check to see if this current
|
Chris@17
|
429 // token is a comment. If so skip it.
|
Chris@17
|
430 if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
|
Chris@17
|
431 continue;
|
Chris@17
|
432 }
|
Chris@17
|
433
|
Chris@17
|
434 // If the next token is a comment, the we need to skip the
|
Chris@17
|
435 // current token as we should allow a space before a
|
Chris@17
|
436 // comment for readability.
|
Chris@17
|
437 if (isset($tokens[($stackPtr + 1)]) === true
|
Chris@17
|
438 && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
|
Chris@17
|
439 ) {
|
Chris@17
|
440 continue;
|
Chris@17
|
441 }
|
Chris@17
|
442 }
|
Chris@17
|
443
|
Chris@17
|
444 $tokenContent = '';
|
Chris@17
|
445 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
|
Chris@17
|
446 if (isset($pattern[($i + 1)]) === false) {
|
Chris@17
|
447 // This is the last token in the pattern, so just compare
|
Chris@17
|
448 // the next token of content.
|
Chris@17
|
449 $tokenContent = $tokens[$stackPtr]['content'];
|
Chris@17
|
450 } else {
|
Chris@17
|
451 // Get all the whitespace to the next token.
|
Chris@17
|
452 $next = $phpcsFile->findNext(
|
Chris@17
|
453 Tokens::$emptyTokens,
|
Chris@17
|
454 $stackPtr,
|
Chris@17
|
455 null,
|
Chris@17
|
456 true
|
Chris@17
|
457 );
|
Chris@17
|
458
|
Chris@17
|
459 $tokenContent = $phpcsFile->getTokensAsString(
|
Chris@17
|
460 $stackPtr,
|
Chris@17
|
461 ($next - $stackPtr)
|
Chris@17
|
462 );
|
Chris@17
|
463
|
Chris@17
|
464 $lastAddedStackPtr = $stackPtr;
|
Chris@17
|
465 $stackPtr = $next;
|
Chris@17
|
466 }//end if
|
Chris@17
|
467
|
Chris@17
|
468 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
469 $found .= $tokenContent;
|
Chris@17
|
470 }
|
Chris@17
|
471 } else {
|
Chris@17
|
472 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
473 $found .= $tokens[$stackPtr]['content'];
|
Chris@17
|
474 $lastAddedStackPtr = $stackPtr;
|
Chris@17
|
475 }
|
Chris@17
|
476 }//end if
|
Chris@17
|
477
|
Chris@17
|
478 if (isset($pattern[($i + 1)]) === true
|
Chris@17
|
479 && $pattern[($i + 1)]['type'] === 'skip'
|
Chris@17
|
480 ) {
|
Chris@17
|
481 // The next token is a skip token, so we just need to make
|
Chris@17
|
482 // sure the whitespace we found has *at least* the
|
Chris@17
|
483 // whitespace required.
|
Chris@17
|
484 if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
|
Chris@17
|
485 $hasError = true;
|
Chris@17
|
486 }
|
Chris@17
|
487 } else {
|
Chris@17
|
488 if ($tokenContent !== $pattern[$i]['value']) {
|
Chris@17
|
489 $hasError = true;
|
Chris@17
|
490 }
|
Chris@17
|
491 }
|
Chris@17
|
492 } else {
|
Chris@17
|
493 // Check to see if this important token is the same as the
|
Chris@17
|
494 // next important token in the pattern. If it is not, then
|
Chris@17
|
495 // the pattern cannot be for this piece of code.
|
Chris@17
|
496 $next = $phpcsFile->findNext(
|
Chris@17
|
497 $ignoreTokens,
|
Chris@17
|
498 $stackPtr,
|
Chris@17
|
499 null,
|
Chris@17
|
500 true
|
Chris@17
|
501 );
|
Chris@17
|
502
|
Chris@17
|
503 if ($next === false
|
Chris@17
|
504 || $tokens[$next]['code'] !== $pattern[$i]['token']
|
Chris@17
|
505 ) {
|
Chris@17
|
506 // The next important token did not match the pattern.
|
Chris@17
|
507 return false;
|
Chris@17
|
508 }
|
Chris@17
|
509
|
Chris@17
|
510 if ($lastAddedStackPtr !== null) {
|
Chris@17
|
511 if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
|
Chris@17
|
512 || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
|
Chris@17
|
513 && isset($tokens[$next]['scope_condition']) === true
|
Chris@17
|
514 && $tokens[$next]['scope_condition'] > $lastAddedStackPtr
|
Chris@17
|
515 ) {
|
Chris@17
|
516 // This is a brace, but the owner of it is after the current
|
Chris@17
|
517 // token, which means it does not belong to any token in
|
Chris@17
|
518 // our pattern. This means the pattern is not for us.
|
Chris@17
|
519 return false;
|
Chris@17
|
520 }
|
Chris@17
|
521
|
Chris@17
|
522 if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
|
Chris@17
|
523 || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
|
Chris@17
|
524 && isset($tokens[$next]['parenthesis_owner']) === true
|
Chris@17
|
525 && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
|
Chris@17
|
526 ) {
|
Chris@17
|
527 // This is a bracket, but the owner of it is after the current
|
Chris@17
|
528 // token, which means it does not belong to any token in
|
Chris@17
|
529 // our pattern. This means the pattern is not for us.
|
Chris@17
|
530 return false;
|
Chris@17
|
531 }
|
Chris@17
|
532 }//end if
|
Chris@17
|
533
|
Chris@17
|
534 // If we skipped past some whitespace tokens, then add them
|
Chris@17
|
535 // to the found string.
|
Chris@17
|
536 if (($next - $stackPtr) > 0) {
|
Chris@17
|
537 $hasComment = false;
|
Chris@17
|
538 for ($j = $stackPtr; $j < $next; $j++) {
|
Chris@17
|
539 $found .= $tokens[$j]['content'];
|
Chris@17
|
540 if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
|
Chris@17
|
541 $hasComment = true;
|
Chris@17
|
542 }
|
Chris@17
|
543 }
|
Chris@17
|
544
|
Chris@17
|
545 // If we are not ignoring comments, this additional
|
Chris@17
|
546 // whitespace or comment is not allowed. If we are
|
Chris@17
|
547 // ignoring comments, there needs to be at least one
|
Chris@17
|
548 // comment for this to be allowed.
|
Chris@17
|
549 if ($this->ignoreComments === false
|
Chris@17
|
550 || ($this->ignoreComments === true
|
Chris@17
|
551 && $hasComment === false)
|
Chris@17
|
552 ) {
|
Chris@17
|
553 $hasError = true;
|
Chris@17
|
554 }
|
Chris@17
|
555
|
Chris@17
|
556 // Even when ignoring comments, we are not allowed to include
|
Chris@17
|
557 // newlines without the pattern specifying them, so
|
Chris@17
|
558 // everything should be on the same line.
|
Chris@17
|
559 if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
|
Chris@17
|
560 $hasError = true;
|
Chris@17
|
561 }
|
Chris@17
|
562 }//end if
|
Chris@17
|
563
|
Chris@17
|
564 if ($next !== $lastAddedStackPtr) {
|
Chris@17
|
565 $found .= $tokens[$next]['content'];
|
Chris@17
|
566 $lastAddedStackPtr = $next;
|
Chris@17
|
567 }
|
Chris@17
|
568
|
Chris@17
|
569 if (isset($pattern[($i + 1)]) === true
|
Chris@17
|
570 && $pattern[($i + 1)]['type'] === 'skip'
|
Chris@17
|
571 ) {
|
Chris@17
|
572 $stackPtr = $next;
|
Chris@17
|
573 } else {
|
Chris@17
|
574 $stackPtr = ($next + 1);
|
Chris@17
|
575 }
|
Chris@17
|
576 }//end if
|
Chris@17
|
577 } else if ($pattern[$i]['type'] === 'skip') {
|
Chris@17
|
578 if ($pattern[$i]['to'] === 'unknown') {
|
Chris@17
|
579 $next = $phpcsFile->findNext(
|
Chris@17
|
580 $pattern[($i + 1)]['token'],
|
Chris@17
|
581 $stackPtr
|
Chris@17
|
582 );
|
Chris@17
|
583
|
Chris@17
|
584 if ($next === false) {
|
Chris@17
|
585 // Couldn't find the next token, so we must
|
Chris@17
|
586 // be using the wrong pattern.
|
Chris@17
|
587 return false;
|
Chris@17
|
588 }
|
Chris@17
|
589
|
Chris@17
|
590 $found .= '...';
|
Chris@17
|
591 $stackPtr = $next;
|
Chris@17
|
592 } else {
|
Chris@17
|
593 // Find the previous opener.
|
Chris@17
|
594 $next = $phpcsFile->findPrevious(
|
Chris@17
|
595 Tokens::$blockOpeners,
|
Chris@17
|
596 $stackPtr
|
Chris@17
|
597 );
|
Chris@17
|
598
|
Chris@17
|
599 if ($next === false
|
Chris@17
|
600 || isset($tokens[$next][$pattern[$i]['to']]) === false
|
Chris@17
|
601 ) {
|
Chris@17
|
602 // If there was not opener, then we must
|
Chris@17
|
603 // be using the wrong pattern.
|
Chris@17
|
604 return false;
|
Chris@17
|
605 }
|
Chris@17
|
606
|
Chris@17
|
607 $found .= '...';
|
Chris@17
|
608 if ($pattern[$i]['to'] === 'parenthesis_closer') {
|
Chris@17
|
609 $found .= ')';
|
Chris@17
|
610 } else {
|
Chris@17
|
611 $found .= '}';
|
Chris@17
|
612 }
|
Chris@17
|
613
|
Chris@17
|
614 // Skip to the closing token.
|
Chris@17
|
615 $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
|
Chris@17
|
616 }//end if
|
Chris@17
|
617 } else if ($pattern[$i]['type'] === 'string') {
|
Chris@17
|
618 if ($tokens[$stackPtr]['code'] !== T_STRING) {
|
Chris@17
|
619 $hasError = true;
|
Chris@17
|
620 }
|
Chris@17
|
621
|
Chris@17
|
622 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
623 $found .= 'abc';
|
Chris@17
|
624 $lastAddedStackPtr = $stackPtr;
|
Chris@17
|
625 }
|
Chris@17
|
626
|
Chris@17
|
627 $stackPtr++;
|
Chris@17
|
628 } else if ($pattern[$i]['type'] === 'newline') {
|
Chris@17
|
629 // Find the next token that contains a newline character.
|
Chris@17
|
630 $newline = 0;
|
Chris@17
|
631 for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
|
Chris@17
|
632 if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
|
Chris@17
|
633 $newline = $j;
|
Chris@17
|
634 break;
|
Chris@17
|
635 }
|
Chris@17
|
636 }
|
Chris@17
|
637
|
Chris@17
|
638 if ($newline === 0) {
|
Chris@17
|
639 // We didn't find a newline character in the rest of the file.
|
Chris@17
|
640 $next = ($phpcsFile->numTokens - 1);
|
Chris@17
|
641 $hasError = true;
|
Chris@17
|
642 } else {
|
Chris@17
|
643 if ($this->ignoreComments === false) {
|
Chris@17
|
644 // The newline character cannot be part of a comment.
|
Chris@17
|
645 if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
|
Chris@17
|
646 $hasError = true;
|
Chris@17
|
647 }
|
Chris@17
|
648 }
|
Chris@17
|
649
|
Chris@17
|
650 if ($newline === $stackPtr) {
|
Chris@17
|
651 $next = ($stackPtr + 1);
|
Chris@17
|
652 } else {
|
Chris@17
|
653 // Check that there were no significant tokens that we
|
Chris@17
|
654 // skipped over to find our newline character.
|
Chris@17
|
655 $next = $phpcsFile->findNext(
|
Chris@17
|
656 $ignoreTokens,
|
Chris@17
|
657 $stackPtr,
|
Chris@17
|
658 null,
|
Chris@17
|
659 true
|
Chris@17
|
660 );
|
Chris@17
|
661
|
Chris@17
|
662 if ($next < $newline) {
|
Chris@17
|
663 // We skipped a non-ignored token.
|
Chris@17
|
664 $hasError = true;
|
Chris@17
|
665 } else {
|
Chris@17
|
666 $next = ($newline + 1);
|
Chris@17
|
667 }
|
Chris@17
|
668 }
|
Chris@17
|
669 }//end if
|
Chris@17
|
670
|
Chris@17
|
671 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
672 $found .= $phpcsFile->getTokensAsString(
|
Chris@17
|
673 $stackPtr,
|
Chris@17
|
674 ($next - $stackPtr)
|
Chris@17
|
675 );
|
Chris@17
|
676
|
Chris@17
|
677 $lastAddedStackPtr = ($next - 1);
|
Chris@17
|
678 }
|
Chris@17
|
679
|
Chris@17
|
680 $stackPtr = $next;
|
Chris@17
|
681 }//end if
|
Chris@17
|
682 }//end for
|
Chris@17
|
683
|
Chris@17
|
684 if ($hasError === true) {
|
Chris@17
|
685 $error = $this->prepareError($found, $patternCode);
|
Chris@17
|
686 $errors[$origStackPtr] = $error;
|
Chris@17
|
687 }
|
Chris@17
|
688
|
Chris@17
|
689 return $errors;
|
Chris@17
|
690
|
Chris@17
|
691 }//end processPattern()
|
Chris@17
|
692
|
Chris@17
|
693
|
Chris@17
|
694 /**
|
Chris@17
|
695 * Prepares an error for the specified patternCode.
|
Chris@17
|
696 *
|
Chris@17
|
697 * @param string $found The actual found string in the code.
|
Chris@17
|
698 * @param string $patternCode The expected pattern code.
|
Chris@17
|
699 *
|
Chris@17
|
700 * @return string The error message.
|
Chris@17
|
701 */
|
Chris@17
|
702 protected function prepareError($found, $patternCode)
|
Chris@17
|
703 {
|
Chris@17
|
704 $found = str_replace("\r\n", '\n', $found);
|
Chris@17
|
705 $found = str_replace("\n", '\n', $found);
|
Chris@17
|
706 $found = str_replace("\r", '\n', $found);
|
Chris@17
|
707 $found = str_replace("\t", '\t', $found);
|
Chris@17
|
708 $found = str_replace('EOL', '\n', $found);
|
Chris@17
|
709 $expected = str_replace('EOL', '\n', $patternCode);
|
Chris@17
|
710
|
Chris@17
|
711 $error = "Expected \"$expected\"; found \"$found\"";
|
Chris@17
|
712
|
Chris@17
|
713 return $error;
|
Chris@17
|
714
|
Chris@17
|
715 }//end prepareError()
|
Chris@17
|
716
|
Chris@17
|
717
|
Chris@17
|
718 /**
|
Chris@17
|
719 * Returns the patterns that should be checked.
|
Chris@17
|
720 *
|
Chris@17
|
721 * @return string[]
|
Chris@17
|
722 */
|
Chris@17
|
723 abstract protected function getPatterns();
|
Chris@17
|
724
|
Chris@17
|
725
|
Chris@17
|
726 /**
|
Chris@17
|
727 * Registers any supplementary tokens that this test might wish to process.
|
Chris@17
|
728 *
|
Chris@17
|
729 * A sniff may wish to register supplementary tests when it wishes to group
|
Chris@17
|
730 * an arbitrary validation that cannot be performed using a pattern, with
|
Chris@17
|
731 * other pattern tests.
|
Chris@17
|
732 *
|
Chris@17
|
733 * @return int[]
|
Chris@17
|
734 * @see processSupplementary()
|
Chris@17
|
735 */
|
Chris@17
|
736 protected function registerSupplementary()
|
Chris@17
|
737 {
|
Chris@17
|
738 return [];
|
Chris@17
|
739
|
Chris@17
|
740 }//end registerSupplementary()
|
Chris@17
|
741
|
Chris@17
|
742
|
Chris@17
|
743 /**
|
Chris@17
|
744 * Processes any tokens registered with registerSupplementary().
|
Chris@17
|
745 *
|
Chris@17
|
746 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to
|
Chris@17
|
747 * process the skip.
|
Chris@17
|
748 * @param int $stackPtr The position in the tokens stack to
|
Chris@17
|
749 * process.
|
Chris@17
|
750 *
|
Chris@17
|
751 * @return void
|
Chris@17
|
752 * @see registerSupplementary()
|
Chris@17
|
753 */
|
Chris@17
|
754 protected function processSupplementary(File $phpcsFile, $stackPtr)
|
Chris@17
|
755 {
|
Chris@17
|
756
|
Chris@17
|
757 }//end processSupplementary()
|
Chris@17
|
758
|
Chris@17
|
759
|
Chris@17
|
760 /**
|
Chris@17
|
761 * Parses a pattern string into an array of pattern steps.
|
Chris@17
|
762 *
|
Chris@17
|
763 * @param string $pattern The pattern to parse.
|
Chris@17
|
764 *
|
Chris@17
|
765 * @return array The parsed pattern array.
|
Chris@17
|
766 * @see createSkipPattern()
|
Chris@17
|
767 * @see createTokenPattern()
|
Chris@17
|
768 */
|
Chris@17
|
769 private function parse($pattern)
|
Chris@17
|
770 {
|
Chris@17
|
771 $patterns = [];
|
Chris@17
|
772 $length = strlen($pattern);
|
Chris@17
|
773 $lastToken = 0;
|
Chris@17
|
774 $firstToken = 0;
|
Chris@17
|
775
|
Chris@17
|
776 for ($i = 0; $i < $length; $i++) {
|
Chris@17
|
777 $specialPattern = false;
|
Chris@17
|
778 $isLastChar = ($i === ($length - 1));
|
Chris@17
|
779 $oldFirstToken = $firstToken;
|
Chris@17
|
780
|
Chris@17
|
781 if (substr($pattern, $i, 3) === '...') {
|
Chris@17
|
782 // It's a skip pattern. The skip pattern requires the
|
Chris@17
|
783 // content of the token in the "from" position and the token
|
Chris@17
|
784 // to skip to.
|
Chris@17
|
785 $specialPattern = $this->createSkipPattern($pattern, ($i - 1));
|
Chris@17
|
786 $lastToken = ($i - $firstToken);
|
Chris@17
|
787 $firstToken = ($i + 3);
|
Chris@17
|
788 $i += 2;
|
Chris@17
|
789
|
Chris@17
|
790 if ($specialPattern['to'] !== 'unknown') {
|
Chris@17
|
791 $firstToken++;
|
Chris@17
|
792 }
|
Chris@17
|
793 } else if (substr($pattern, $i, 3) === 'abc') {
|
Chris@17
|
794 $specialPattern = ['type' => 'string'];
|
Chris@17
|
795 $lastToken = ($i - $firstToken);
|
Chris@17
|
796 $firstToken = ($i + 3);
|
Chris@17
|
797 $i += 2;
|
Chris@17
|
798 } else if (substr($pattern, $i, 3) === 'EOL') {
|
Chris@17
|
799 $specialPattern = ['type' => 'newline'];
|
Chris@17
|
800 $lastToken = ($i - $firstToken);
|
Chris@17
|
801 $firstToken = ($i + 3);
|
Chris@17
|
802 $i += 2;
|
Chris@17
|
803 }//end if
|
Chris@17
|
804
|
Chris@17
|
805 if ($specialPattern !== false || $isLastChar === true) {
|
Chris@17
|
806 // If we are at the end of the string, don't worry about a limit.
|
Chris@17
|
807 if ($isLastChar === true) {
|
Chris@17
|
808 // Get the string from the end of the last skip pattern, if any,
|
Chris@17
|
809 // to the end of the pattern string.
|
Chris@17
|
810 $str = substr($pattern, $oldFirstToken);
|
Chris@17
|
811 } else {
|
Chris@17
|
812 // Get the string from the end of the last special pattern,
|
Chris@17
|
813 // if any, to the start of this special pattern.
|
Chris@17
|
814 if ($lastToken === 0) {
|
Chris@17
|
815 // Note that if the last special token was zero characters ago,
|
Chris@17
|
816 // there will be nothing to process so we can skip this bit.
|
Chris@17
|
817 // This happens if you have something like: EOL... in your pattern.
|
Chris@17
|
818 $str = '';
|
Chris@17
|
819 } else {
|
Chris@17
|
820 $str = substr($pattern, $oldFirstToken, $lastToken);
|
Chris@17
|
821 }
|
Chris@17
|
822 }
|
Chris@17
|
823
|
Chris@17
|
824 if ($str !== '') {
|
Chris@17
|
825 $tokenPatterns = $this->createTokenPattern($str);
|
Chris@17
|
826 foreach ($tokenPatterns as $tokenPattern) {
|
Chris@17
|
827 $patterns[] = $tokenPattern;
|
Chris@17
|
828 }
|
Chris@17
|
829 }
|
Chris@17
|
830
|
Chris@17
|
831 // Make sure we don't skip the last token.
|
Chris@17
|
832 if ($isLastChar === false && $i === ($length - 1)) {
|
Chris@17
|
833 $i--;
|
Chris@17
|
834 }
|
Chris@17
|
835 }//end if
|
Chris@17
|
836
|
Chris@17
|
837 // Add the skip pattern *after* we have processed
|
Chris@17
|
838 // all the tokens from the end of the last skip pattern
|
Chris@17
|
839 // to the start of this skip pattern.
|
Chris@17
|
840 if ($specialPattern !== false) {
|
Chris@17
|
841 $patterns[] = $specialPattern;
|
Chris@17
|
842 }
|
Chris@17
|
843 }//end for
|
Chris@17
|
844
|
Chris@17
|
845 return $patterns;
|
Chris@17
|
846
|
Chris@17
|
847 }//end parse()
|
Chris@17
|
848
|
Chris@17
|
849
|
Chris@17
|
850 /**
|
Chris@17
|
851 * Creates a skip pattern.
|
Chris@17
|
852 *
|
Chris@17
|
853 * @param string $pattern The pattern being parsed.
|
Chris@17
|
854 * @param string $from The token content that the skip pattern starts from.
|
Chris@17
|
855 *
|
Chris@17
|
856 * @return array The pattern step.
|
Chris@17
|
857 * @see createTokenPattern()
|
Chris@17
|
858 * @see parse()
|
Chris@17
|
859 */
|
Chris@17
|
860 private function createSkipPattern($pattern, $from)
|
Chris@17
|
861 {
|
Chris@17
|
862 $skip = ['type' => 'skip'];
|
Chris@17
|
863
|
Chris@17
|
864 $nestedParenthesis = 0;
|
Chris@17
|
865 $nestedBraces = 0;
|
Chris@17
|
866 for ($start = $from; $start >= 0; $start--) {
|
Chris@17
|
867 switch ($pattern[$start]) {
|
Chris@17
|
868 case '(':
|
Chris@17
|
869 if ($nestedParenthesis === 0) {
|
Chris@17
|
870 $skip['to'] = 'parenthesis_closer';
|
Chris@17
|
871 }
|
Chris@17
|
872
|
Chris@17
|
873 $nestedParenthesis--;
|
Chris@17
|
874 break;
|
Chris@17
|
875 case '{':
|
Chris@17
|
876 if ($nestedBraces === 0) {
|
Chris@17
|
877 $skip['to'] = 'scope_closer';
|
Chris@17
|
878 }
|
Chris@17
|
879
|
Chris@17
|
880 $nestedBraces--;
|
Chris@17
|
881 break;
|
Chris@17
|
882 case '}':
|
Chris@17
|
883 $nestedBraces++;
|
Chris@17
|
884 break;
|
Chris@17
|
885 case ')':
|
Chris@17
|
886 $nestedParenthesis++;
|
Chris@17
|
887 break;
|
Chris@17
|
888 }//end switch
|
Chris@17
|
889
|
Chris@17
|
890 if (isset($skip['to']) === true) {
|
Chris@17
|
891 break;
|
Chris@17
|
892 }
|
Chris@17
|
893 }//end for
|
Chris@17
|
894
|
Chris@17
|
895 if (isset($skip['to']) === false) {
|
Chris@17
|
896 $skip['to'] = 'unknown';
|
Chris@17
|
897 }
|
Chris@17
|
898
|
Chris@17
|
899 return $skip;
|
Chris@17
|
900
|
Chris@17
|
901 }//end createSkipPattern()
|
Chris@17
|
902
|
Chris@17
|
903
|
Chris@17
|
904 /**
|
Chris@17
|
905 * Creates a token pattern.
|
Chris@17
|
906 *
|
Chris@17
|
907 * @param string $str The tokens string that the pattern should match.
|
Chris@17
|
908 *
|
Chris@17
|
909 * @return array The pattern step.
|
Chris@17
|
910 * @see createSkipPattern()
|
Chris@17
|
911 * @see parse()
|
Chris@17
|
912 */
|
Chris@17
|
913 private function createTokenPattern($str)
|
Chris@17
|
914 {
|
Chris@17
|
915 // Don't add a space after the closing php tag as it will add a new
|
Chris@17
|
916 // whitespace token.
|
Chris@17
|
917 $tokenizer = new PHP('<?php '.$str.'?>', null);
|
Chris@17
|
918
|
Chris@17
|
919 // Remove the <?php tag from the front and the end php tag from the back.
|
Chris@17
|
920 $tokens = $tokenizer->getTokens();
|
Chris@17
|
921 $tokens = array_slice($tokens, 1, (count($tokens) - 2));
|
Chris@17
|
922
|
Chris@17
|
923 $patterns = [];
|
Chris@17
|
924 foreach ($tokens as $patternInfo) {
|
Chris@17
|
925 $patterns[] = [
|
Chris@17
|
926 'type' => 'token',
|
Chris@17
|
927 'token' => $patternInfo['code'],
|
Chris@17
|
928 'value' => $patternInfo['content'],
|
Chris@17
|
929 ];
|
Chris@17
|
930 }
|
Chris@17
|
931
|
Chris@17
|
932 return $patterns;
|
Chris@17
|
933
|
Chris@17
|
934 }//end createTokenPattern()
|
Chris@17
|
935
|
Chris@17
|
936
|
Chris@17
|
937 }//end class
|