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@18
|
156 * @throws \PHP_CodeSniffer\Exceptions\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@18
|
198 if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === 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@18
|
263 $ignoreTokens = [T_WHITESPACE => T_WHITESPACE];
|
Chris@17
|
264 if ($this->ignoreComments === true) {
|
Chris@18
|
265 $ignoreTokens += Tokens::$commentTokens;
|
Chris@17
|
266 }
|
Chris@17
|
267
|
Chris@17
|
268 $origStackPtr = $stackPtr;
|
Chris@17
|
269 $hasError = false;
|
Chris@17
|
270
|
Chris@17
|
271 if ($patternInfo['listen_pos'] > 0) {
|
Chris@17
|
272 $stackPtr--;
|
Chris@17
|
273
|
Chris@17
|
274 for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
|
Chris@17
|
275 if ($pattern[$i]['type'] === 'token') {
|
Chris@17
|
276 if ($pattern[$i]['token'] === T_WHITESPACE) {
|
Chris@17
|
277 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
|
Chris@17
|
278 $found = $tokens[$stackPtr]['content'].$found;
|
Chris@17
|
279 }
|
Chris@17
|
280
|
Chris@17
|
281 // Only check the size of the whitespace if this is not
|
Chris@17
|
282 // the first token. We don't care about the size of
|
Chris@17
|
283 // leading whitespace, just that there is some.
|
Chris@17
|
284 if ($i !== 0) {
|
Chris@17
|
285 if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
|
Chris@17
|
286 $hasError = true;
|
Chris@17
|
287 }
|
Chris@17
|
288 }
|
Chris@17
|
289 } else {
|
Chris@17
|
290 // Check to see if this important token is the same as the
|
Chris@17
|
291 // previous important token in the pattern. If it is not,
|
Chris@17
|
292 // then the pattern cannot be for this piece of code.
|
Chris@17
|
293 $prev = $phpcsFile->findPrevious(
|
Chris@17
|
294 $ignoreTokens,
|
Chris@17
|
295 $stackPtr,
|
Chris@17
|
296 null,
|
Chris@17
|
297 true
|
Chris@17
|
298 );
|
Chris@17
|
299
|
Chris@17
|
300 if ($prev === false
|
Chris@17
|
301 || $tokens[$prev]['code'] !== $pattern[$i]['token']
|
Chris@17
|
302 ) {
|
Chris@17
|
303 return false;
|
Chris@17
|
304 }
|
Chris@17
|
305
|
Chris@17
|
306 // If we skipped past some whitespace tokens, then add them
|
Chris@17
|
307 // to the found string.
|
Chris@17
|
308 $tokenContent = $phpcsFile->getTokensAsString(
|
Chris@17
|
309 ($prev + 1),
|
Chris@17
|
310 ($stackPtr - $prev - 1)
|
Chris@17
|
311 );
|
Chris@17
|
312
|
Chris@17
|
313 $found = $tokens[$prev]['content'].$tokenContent.$found;
|
Chris@17
|
314
|
Chris@17
|
315 if (isset($pattern[($i - 1)]) === true
|
Chris@17
|
316 && $pattern[($i - 1)]['type'] === 'skip'
|
Chris@17
|
317 ) {
|
Chris@17
|
318 $stackPtr = $prev;
|
Chris@17
|
319 } else {
|
Chris@17
|
320 $stackPtr = ($prev - 1);
|
Chris@17
|
321 }
|
Chris@17
|
322 }//end if
|
Chris@17
|
323 } else if ($pattern[$i]['type'] === 'skip') {
|
Chris@17
|
324 // Skip to next piece of relevant code.
|
Chris@17
|
325 if ($pattern[$i]['to'] === 'parenthesis_closer') {
|
Chris@17
|
326 $to = 'parenthesis_opener';
|
Chris@17
|
327 } else {
|
Chris@17
|
328 $to = 'scope_opener';
|
Chris@17
|
329 }
|
Chris@17
|
330
|
Chris@17
|
331 // Find the previous opener.
|
Chris@17
|
332 $next = $phpcsFile->findPrevious(
|
Chris@17
|
333 $ignoreTokens,
|
Chris@17
|
334 $stackPtr,
|
Chris@17
|
335 null,
|
Chris@17
|
336 true
|
Chris@17
|
337 );
|
Chris@17
|
338
|
Chris@17
|
339 if ($next === false || isset($tokens[$next][$to]) === false) {
|
Chris@17
|
340 // If there was not opener, then we must be
|
Chris@17
|
341 // using the wrong pattern.
|
Chris@17
|
342 return false;
|
Chris@17
|
343 }
|
Chris@17
|
344
|
Chris@17
|
345 if ($to === 'parenthesis_opener') {
|
Chris@17
|
346 $found = '{'.$found;
|
Chris@17
|
347 } else {
|
Chris@17
|
348 $found = '('.$found;
|
Chris@17
|
349 }
|
Chris@17
|
350
|
Chris@17
|
351 $found = '...'.$found;
|
Chris@17
|
352
|
Chris@17
|
353 // Skip to the opening token.
|
Chris@17
|
354 $stackPtr = ($tokens[$next][$to] - 1);
|
Chris@17
|
355 } else if ($pattern[$i]['type'] === 'string') {
|
Chris@17
|
356 $found = 'abc';
|
Chris@17
|
357 } else if ($pattern[$i]['type'] === 'newline') {
|
Chris@17
|
358 if ($this->ignoreComments === true
|
Chris@17
|
359 && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
|
Chris@17
|
360 ) {
|
Chris@17
|
361 $startComment = $phpcsFile->findPrevious(
|
Chris@17
|
362 Tokens::$commentTokens,
|
Chris@17
|
363 ($stackPtr - 1),
|
Chris@17
|
364 null,
|
Chris@17
|
365 true
|
Chris@17
|
366 );
|
Chris@17
|
367
|
Chris@17
|
368 if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
|
Chris@17
|
369 $startComment++;
|
Chris@17
|
370 }
|
Chris@17
|
371
|
Chris@17
|
372 $tokenContent = $phpcsFile->getTokensAsString(
|
Chris@17
|
373 $startComment,
|
Chris@17
|
374 ($stackPtr - $startComment + 1)
|
Chris@17
|
375 );
|
Chris@17
|
376
|
Chris@17
|
377 $found = $tokenContent.$found;
|
Chris@17
|
378 $stackPtr = ($startComment - 1);
|
Chris@17
|
379 }
|
Chris@17
|
380
|
Chris@17
|
381 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
|
Chris@17
|
382 if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
|
Chris@17
|
383 $found = $tokens[$stackPtr]['content'].$found;
|
Chris@17
|
384
|
Chris@17
|
385 // This may just be an indent that comes after a newline
|
Chris@17
|
386 // so check the token before to make sure. If it is a newline, we
|
Chris@17
|
387 // can ignore the error here.
|
Chris@17
|
388 if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
|
Chris@17
|
389 && ($this->ignoreComments === true
|
Chris@17
|
390 && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
|
Chris@17
|
391 ) {
|
Chris@17
|
392 $hasError = true;
|
Chris@17
|
393 } else {
|
Chris@17
|
394 $stackPtr--;
|
Chris@17
|
395 }
|
Chris@17
|
396 } else {
|
Chris@17
|
397 $found = 'EOL'.$found;
|
Chris@17
|
398 }
|
Chris@17
|
399 } else {
|
Chris@17
|
400 $found = $tokens[$stackPtr]['content'].$found;
|
Chris@17
|
401 $hasError = true;
|
Chris@17
|
402 }//end if
|
Chris@17
|
403
|
Chris@17
|
404 if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
|
Chris@17
|
405 // Make sure they only have 1 newline.
|
Chris@17
|
406 $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
|
Chris@17
|
407 if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
|
Chris@17
|
408 $hasError = true;
|
Chris@17
|
409 }
|
Chris@17
|
410 }
|
Chris@17
|
411 }//end if
|
Chris@17
|
412 }//end for
|
Chris@17
|
413 }//end if
|
Chris@17
|
414
|
Chris@17
|
415 $stackPtr = $origStackPtr;
|
Chris@17
|
416 $lastAddedStackPtr = null;
|
Chris@17
|
417 $patternLen = count($pattern);
|
Chris@17
|
418
|
Chris@17
|
419 for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
|
Chris@17
|
420 if (isset($tokens[$stackPtr]) === false) {
|
Chris@17
|
421 break;
|
Chris@17
|
422 }
|
Chris@17
|
423
|
Chris@17
|
424 if ($pattern[$i]['type'] === 'token') {
|
Chris@17
|
425 if ($pattern[$i]['token'] === T_WHITESPACE) {
|
Chris@17
|
426 if ($this->ignoreComments === true) {
|
Chris@17
|
427 // If we are ignoring comments, check to see if this current
|
Chris@17
|
428 // token is a comment. If so skip it.
|
Chris@17
|
429 if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
|
Chris@17
|
430 continue;
|
Chris@17
|
431 }
|
Chris@17
|
432
|
Chris@17
|
433 // If the next token is a comment, the we need to skip the
|
Chris@17
|
434 // current token as we should allow a space before a
|
Chris@17
|
435 // comment for readability.
|
Chris@17
|
436 if (isset($tokens[($stackPtr + 1)]) === true
|
Chris@17
|
437 && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
|
Chris@17
|
438 ) {
|
Chris@17
|
439 continue;
|
Chris@17
|
440 }
|
Chris@17
|
441 }
|
Chris@17
|
442
|
Chris@17
|
443 $tokenContent = '';
|
Chris@17
|
444 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
|
Chris@17
|
445 if (isset($pattern[($i + 1)]) === false) {
|
Chris@17
|
446 // This is the last token in the pattern, so just compare
|
Chris@17
|
447 // the next token of content.
|
Chris@17
|
448 $tokenContent = $tokens[$stackPtr]['content'];
|
Chris@17
|
449 } else {
|
Chris@17
|
450 // Get all the whitespace to the next token.
|
Chris@17
|
451 $next = $phpcsFile->findNext(
|
Chris@17
|
452 Tokens::$emptyTokens,
|
Chris@17
|
453 $stackPtr,
|
Chris@17
|
454 null,
|
Chris@17
|
455 true
|
Chris@17
|
456 );
|
Chris@17
|
457
|
Chris@17
|
458 $tokenContent = $phpcsFile->getTokensAsString(
|
Chris@17
|
459 $stackPtr,
|
Chris@17
|
460 ($next - $stackPtr)
|
Chris@17
|
461 );
|
Chris@17
|
462
|
Chris@17
|
463 $lastAddedStackPtr = $stackPtr;
|
Chris@17
|
464 $stackPtr = $next;
|
Chris@17
|
465 }//end if
|
Chris@17
|
466
|
Chris@17
|
467 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
468 $found .= $tokenContent;
|
Chris@17
|
469 }
|
Chris@17
|
470 } else {
|
Chris@17
|
471 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
472 $found .= $tokens[$stackPtr]['content'];
|
Chris@17
|
473 $lastAddedStackPtr = $stackPtr;
|
Chris@17
|
474 }
|
Chris@17
|
475 }//end if
|
Chris@17
|
476
|
Chris@17
|
477 if (isset($pattern[($i + 1)]) === true
|
Chris@17
|
478 && $pattern[($i + 1)]['type'] === 'skip'
|
Chris@17
|
479 ) {
|
Chris@17
|
480 // The next token is a skip token, so we just need to make
|
Chris@17
|
481 // sure the whitespace we found has *at least* the
|
Chris@17
|
482 // whitespace required.
|
Chris@17
|
483 if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
|
Chris@17
|
484 $hasError = true;
|
Chris@17
|
485 }
|
Chris@17
|
486 } else {
|
Chris@17
|
487 if ($tokenContent !== $pattern[$i]['value']) {
|
Chris@17
|
488 $hasError = true;
|
Chris@17
|
489 }
|
Chris@17
|
490 }
|
Chris@17
|
491 } else {
|
Chris@17
|
492 // Check to see if this important token is the same as the
|
Chris@17
|
493 // next important token in the pattern. If it is not, then
|
Chris@17
|
494 // the pattern cannot be for this piece of code.
|
Chris@17
|
495 $next = $phpcsFile->findNext(
|
Chris@17
|
496 $ignoreTokens,
|
Chris@17
|
497 $stackPtr,
|
Chris@17
|
498 null,
|
Chris@17
|
499 true
|
Chris@17
|
500 );
|
Chris@17
|
501
|
Chris@17
|
502 if ($next === false
|
Chris@17
|
503 || $tokens[$next]['code'] !== $pattern[$i]['token']
|
Chris@17
|
504 ) {
|
Chris@17
|
505 // The next important token did not match the pattern.
|
Chris@17
|
506 return false;
|
Chris@17
|
507 }
|
Chris@17
|
508
|
Chris@17
|
509 if ($lastAddedStackPtr !== null) {
|
Chris@17
|
510 if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
|
Chris@17
|
511 || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
|
Chris@17
|
512 && isset($tokens[$next]['scope_condition']) === true
|
Chris@17
|
513 && $tokens[$next]['scope_condition'] > $lastAddedStackPtr
|
Chris@17
|
514 ) {
|
Chris@17
|
515 // This is a brace, but the owner of it is after the current
|
Chris@17
|
516 // token, which means it does not belong to any token in
|
Chris@17
|
517 // our pattern. This means the pattern is not for us.
|
Chris@17
|
518 return false;
|
Chris@17
|
519 }
|
Chris@17
|
520
|
Chris@17
|
521 if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
|
Chris@17
|
522 || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
|
Chris@17
|
523 && isset($tokens[$next]['parenthesis_owner']) === true
|
Chris@17
|
524 && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
|
Chris@17
|
525 ) {
|
Chris@17
|
526 // This is a bracket, but the owner of it is after the current
|
Chris@17
|
527 // token, which means it does not belong to any token in
|
Chris@17
|
528 // our pattern. This means the pattern is not for us.
|
Chris@17
|
529 return false;
|
Chris@17
|
530 }
|
Chris@17
|
531 }//end if
|
Chris@17
|
532
|
Chris@17
|
533 // If we skipped past some whitespace tokens, then add them
|
Chris@17
|
534 // to the found string.
|
Chris@17
|
535 if (($next - $stackPtr) > 0) {
|
Chris@17
|
536 $hasComment = false;
|
Chris@17
|
537 for ($j = $stackPtr; $j < $next; $j++) {
|
Chris@17
|
538 $found .= $tokens[$j]['content'];
|
Chris@17
|
539 if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
|
Chris@17
|
540 $hasComment = true;
|
Chris@17
|
541 }
|
Chris@17
|
542 }
|
Chris@17
|
543
|
Chris@17
|
544 // If we are not ignoring comments, this additional
|
Chris@17
|
545 // whitespace or comment is not allowed. If we are
|
Chris@17
|
546 // ignoring comments, there needs to be at least one
|
Chris@17
|
547 // comment for this to be allowed.
|
Chris@17
|
548 if ($this->ignoreComments === false
|
Chris@17
|
549 || ($this->ignoreComments === true
|
Chris@17
|
550 && $hasComment === false)
|
Chris@17
|
551 ) {
|
Chris@17
|
552 $hasError = true;
|
Chris@17
|
553 }
|
Chris@17
|
554
|
Chris@17
|
555 // Even when ignoring comments, we are not allowed to include
|
Chris@17
|
556 // newlines without the pattern specifying them, so
|
Chris@17
|
557 // everything should be on the same line.
|
Chris@17
|
558 if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
|
Chris@17
|
559 $hasError = true;
|
Chris@17
|
560 }
|
Chris@17
|
561 }//end if
|
Chris@17
|
562
|
Chris@17
|
563 if ($next !== $lastAddedStackPtr) {
|
Chris@17
|
564 $found .= $tokens[$next]['content'];
|
Chris@17
|
565 $lastAddedStackPtr = $next;
|
Chris@17
|
566 }
|
Chris@17
|
567
|
Chris@17
|
568 if (isset($pattern[($i + 1)]) === true
|
Chris@17
|
569 && $pattern[($i + 1)]['type'] === 'skip'
|
Chris@17
|
570 ) {
|
Chris@17
|
571 $stackPtr = $next;
|
Chris@17
|
572 } else {
|
Chris@17
|
573 $stackPtr = ($next + 1);
|
Chris@17
|
574 }
|
Chris@17
|
575 }//end if
|
Chris@17
|
576 } else if ($pattern[$i]['type'] === 'skip') {
|
Chris@17
|
577 if ($pattern[$i]['to'] === 'unknown') {
|
Chris@17
|
578 $next = $phpcsFile->findNext(
|
Chris@17
|
579 $pattern[($i + 1)]['token'],
|
Chris@17
|
580 $stackPtr
|
Chris@17
|
581 );
|
Chris@17
|
582
|
Chris@17
|
583 if ($next === false) {
|
Chris@17
|
584 // Couldn't find the next token, so we must
|
Chris@17
|
585 // be using the wrong pattern.
|
Chris@17
|
586 return false;
|
Chris@17
|
587 }
|
Chris@17
|
588
|
Chris@17
|
589 $found .= '...';
|
Chris@17
|
590 $stackPtr = $next;
|
Chris@17
|
591 } else {
|
Chris@17
|
592 // Find the previous opener.
|
Chris@17
|
593 $next = $phpcsFile->findPrevious(
|
Chris@17
|
594 Tokens::$blockOpeners,
|
Chris@17
|
595 $stackPtr
|
Chris@17
|
596 );
|
Chris@17
|
597
|
Chris@17
|
598 if ($next === false
|
Chris@17
|
599 || isset($tokens[$next][$pattern[$i]['to']]) === false
|
Chris@17
|
600 ) {
|
Chris@17
|
601 // If there was not opener, then we must
|
Chris@17
|
602 // be using the wrong pattern.
|
Chris@17
|
603 return false;
|
Chris@17
|
604 }
|
Chris@17
|
605
|
Chris@17
|
606 $found .= '...';
|
Chris@17
|
607 if ($pattern[$i]['to'] === 'parenthesis_closer') {
|
Chris@17
|
608 $found .= ')';
|
Chris@17
|
609 } else {
|
Chris@17
|
610 $found .= '}';
|
Chris@17
|
611 }
|
Chris@17
|
612
|
Chris@17
|
613 // Skip to the closing token.
|
Chris@17
|
614 $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
|
Chris@17
|
615 }//end if
|
Chris@17
|
616 } else if ($pattern[$i]['type'] === 'string') {
|
Chris@17
|
617 if ($tokens[$stackPtr]['code'] !== T_STRING) {
|
Chris@17
|
618 $hasError = true;
|
Chris@17
|
619 }
|
Chris@17
|
620
|
Chris@17
|
621 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
622 $found .= 'abc';
|
Chris@17
|
623 $lastAddedStackPtr = $stackPtr;
|
Chris@17
|
624 }
|
Chris@17
|
625
|
Chris@17
|
626 $stackPtr++;
|
Chris@17
|
627 } else if ($pattern[$i]['type'] === 'newline') {
|
Chris@17
|
628 // Find the next token that contains a newline character.
|
Chris@17
|
629 $newline = 0;
|
Chris@17
|
630 for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
|
Chris@17
|
631 if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
|
Chris@17
|
632 $newline = $j;
|
Chris@17
|
633 break;
|
Chris@17
|
634 }
|
Chris@17
|
635 }
|
Chris@17
|
636
|
Chris@17
|
637 if ($newline === 0) {
|
Chris@17
|
638 // We didn't find a newline character in the rest of the file.
|
Chris@17
|
639 $next = ($phpcsFile->numTokens - 1);
|
Chris@17
|
640 $hasError = true;
|
Chris@17
|
641 } else {
|
Chris@17
|
642 if ($this->ignoreComments === false) {
|
Chris@17
|
643 // The newline character cannot be part of a comment.
|
Chris@17
|
644 if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
|
Chris@17
|
645 $hasError = true;
|
Chris@17
|
646 }
|
Chris@17
|
647 }
|
Chris@17
|
648
|
Chris@17
|
649 if ($newline === $stackPtr) {
|
Chris@17
|
650 $next = ($stackPtr + 1);
|
Chris@17
|
651 } else {
|
Chris@17
|
652 // Check that there were no significant tokens that we
|
Chris@17
|
653 // skipped over to find our newline character.
|
Chris@17
|
654 $next = $phpcsFile->findNext(
|
Chris@17
|
655 $ignoreTokens,
|
Chris@17
|
656 $stackPtr,
|
Chris@17
|
657 null,
|
Chris@17
|
658 true
|
Chris@17
|
659 );
|
Chris@17
|
660
|
Chris@17
|
661 if ($next < $newline) {
|
Chris@17
|
662 // We skipped a non-ignored token.
|
Chris@17
|
663 $hasError = true;
|
Chris@17
|
664 } else {
|
Chris@17
|
665 $next = ($newline + 1);
|
Chris@17
|
666 }
|
Chris@17
|
667 }
|
Chris@17
|
668 }//end if
|
Chris@17
|
669
|
Chris@17
|
670 if ($stackPtr !== $lastAddedStackPtr) {
|
Chris@17
|
671 $found .= $phpcsFile->getTokensAsString(
|
Chris@17
|
672 $stackPtr,
|
Chris@17
|
673 ($next - $stackPtr)
|
Chris@17
|
674 );
|
Chris@17
|
675
|
Chris@17
|
676 $lastAddedStackPtr = ($next - 1);
|
Chris@17
|
677 }
|
Chris@17
|
678
|
Chris@17
|
679 $stackPtr = $next;
|
Chris@17
|
680 }//end if
|
Chris@17
|
681 }//end for
|
Chris@17
|
682
|
Chris@17
|
683 if ($hasError === true) {
|
Chris@17
|
684 $error = $this->prepareError($found, $patternCode);
|
Chris@17
|
685 $errors[$origStackPtr] = $error;
|
Chris@17
|
686 }
|
Chris@17
|
687
|
Chris@17
|
688 return $errors;
|
Chris@17
|
689
|
Chris@17
|
690 }//end processPattern()
|
Chris@17
|
691
|
Chris@17
|
692
|
Chris@17
|
693 /**
|
Chris@17
|
694 * Prepares an error for the specified patternCode.
|
Chris@17
|
695 *
|
Chris@17
|
696 * @param string $found The actual found string in the code.
|
Chris@17
|
697 * @param string $patternCode The expected pattern code.
|
Chris@17
|
698 *
|
Chris@17
|
699 * @return string The error message.
|
Chris@17
|
700 */
|
Chris@17
|
701 protected function prepareError($found, $patternCode)
|
Chris@17
|
702 {
|
Chris@17
|
703 $found = str_replace("\r\n", '\n', $found);
|
Chris@17
|
704 $found = str_replace("\n", '\n', $found);
|
Chris@17
|
705 $found = str_replace("\r", '\n', $found);
|
Chris@17
|
706 $found = str_replace("\t", '\t', $found);
|
Chris@17
|
707 $found = str_replace('EOL', '\n', $found);
|
Chris@17
|
708 $expected = str_replace('EOL', '\n', $patternCode);
|
Chris@17
|
709
|
Chris@17
|
710 $error = "Expected \"$expected\"; found \"$found\"";
|
Chris@17
|
711
|
Chris@17
|
712 return $error;
|
Chris@17
|
713
|
Chris@17
|
714 }//end prepareError()
|
Chris@17
|
715
|
Chris@17
|
716
|
Chris@17
|
717 /**
|
Chris@17
|
718 * Returns the patterns that should be checked.
|
Chris@17
|
719 *
|
Chris@17
|
720 * @return string[]
|
Chris@17
|
721 */
|
Chris@17
|
722 abstract protected function getPatterns();
|
Chris@17
|
723
|
Chris@17
|
724
|
Chris@17
|
725 /**
|
Chris@17
|
726 * Registers any supplementary tokens that this test might wish to process.
|
Chris@17
|
727 *
|
Chris@17
|
728 * A sniff may wish to register supplementary tests when it wishes to group
|
Chris@17
|
729 * an arbitrary validation that cannot be performed using a pattern, with
|
Chris@17
|
730 * other pattern tests.
|
Chris@17
|
731 *
|
Chris@17
|
732 * @return int[]
|
Chris@17
|
733 * @see processSupplementary()
|
Chris@17
|
734 */
|
Chris@17
|
735 protected function registerSupplementary()
|
Chris@17
|
736 {
|
Chris@17
|
737 return [];
|
Chris@17
|
738
|
Chris@17
|
739 }//end registerSupplementary()
|
Chris@17
|
740
|
Chris@17
|
741
|
Chris@17
|
742 /**
|
Chris@17
|
743 * Processes any tokens registered with registerSupplementary().
|
Chris@17
|
744 *
|
Chris@17
|
745 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to
|
Chris@17
|
746 * process the skip.
|
Chris@17
|
747 * @param int $stackPtr The position in the tokens stack to
|
Chris@17
|
748 * process.
|
Chris@17
|
749 *
|
Chris@17
|
750 * @return void
|
Chris@17
|
751 * @see registerSupplementary()
|
Chris@17
|
752 */
|
Chris@17
|
753 protected function processSupplementary(File $phpcsFile, $stackPtr)
|
Chris@17
|
754 {
|
Chris@17
|
755
|
Chris@17
|
756 }//end processSupplementary()
|
Chris@17
|
757
|
Chris@17
|
758
|
Chris@17
|
759 /**
|
Chris@17
|
760 * Parses a pattern string into an array of pattern steps.
|
Chris@17
|
761 *
|
Chris@17
|
762 * @param string $pattern The pattern to parse.
|
Chris@17
|
763 *
|
Chris@17
|
764 * @return array The parsed pattern array.
|
Chris@17
|
765 * @see createSkipPattern()
|
Chris@17
|
766 * @see createTokenPattern()
|
Chris@17
|
767 */
|
Chris@17
|
768 private function parse($pattern)
|
Chris@17
|
769 {
|
Chris@17
|
770 $patterns = [];
|
Chris@17
|
771 $length = strlen($pattern);
|
Chris@17
|
772 $lastToken = 0;
|
Chris@17
|
773 $firstToken = 0;
|
Chris@17
|
774
|
Chris@17
|
775 for ($i = 0; $i < $length; $i++) {
|
Chris@17
|
776 $specialPattern = false;
|
Chris@17
|
777 $isLastChar = ($i === ($length - 1));
|
Chris@17
|
778 $oldFirstToken = $firstToken;
|
Chris@17
|
779
|
Chris@17
|
780 if (substr($pattern, $i, 3) === '...') {
|
Chris@17
|
781 // It's a skip pattern. The skip pattern requires the
|
Chris@17
|
782 // content of the token in the "from" position and the token
|
Chris@17
|
783 // to skip to.
|
Chris@17
|
784 $specialPattern = $this->createSkipPattern($pattern, ($i - 1));
|
Chris@17
|
785 $lastToken = ($i - $firstToken);
|
Chris@17
|
786 $firstToken = ($i + 3);
|
Chris@17
|
787 $i += 2;
|
Chris@17
|
788
|
Chris@17
|
789 if ($specialPattern['to'] !== 'unknown') {
|
Chris@17
|
790 $firstToken++;
|
Chris@17
|
791 }
|
Chris@17
|
792 } else if (substr($pattern, $i, 3) === 'abc') {
|
Chris@17
|
793 $specialPattern = ['type' => 'string'];
|
Chris@17
|
794 $lastToken = ($i - $firstToken);
|
Chris@17
|
795 $firstToken = ($i + 3);
|
Chris@17
|
796 $i += 2;
|
Chris@17
|
797 } else if (substr($pattern, $i, 3) === 'EOL') {
|
Chris@17
|
798 $specialPattern = ['type' => 'newline'];
|
Chris@17
|
799 $lastToken = ($i - $firstToken);
|
Chris@17
|
800 $firstToken = ($i + 3);
|
Chris@17
|
801 $i += 2;
|
Chris@17
|
802 }//end if
|
Chris@17
|
803
|
Chris@17
|
804 if ($specialPattern !== false || $isLastChar === true) {
|
Chris@17
|
805 // If we are at the end of the string, don't worry about a limit.
|
Chris@17
|
806 if ($isLastChar === true) {
|
Chris@17
|
807 // Get the string from the end of the last skip pattern, if any,
|
Chris@17
|
808 // to the end of the pattern string.
|
Chris@17
|
809 $str = substr($pattern, $oldFirstToken);
|
Chris@17
|
810 } else {
|
Chris@17
|
811 // Get the string from the end of the last special pattern,
|
Chris@17
|
812 // if any, to the start of this special pattern.
|
Chris@17
|
813 if ($lastToken === 0) {
|
Chris@17
|
814 // Note that if the last special token was zero characters ago,
|
Chris@17
|
815 // there will be nothing to process so we can skip this bit.
|
Chris@17
|
816 // This happens if you have something like: EOL... in your pattern.
|
Chris@17
|
817 $str = '';
|
Chris@17
|
818 } else {
|
Chris@17
|
819 $str = substr($pattern, $oldFirstToken, $lastToken);
|
Chris@17
|
820 }
|
Chris@17
|
821 }
|
Chris@17
|
822
|
Chris@17
|
823 if ($str !== '') {
|
Chris@17
|
824 $tokenPatterns = $this->createTokenPattern($str);
|
Chris@17
|
825 foreach ($tokenPatterns as $tokenPattern) {
|
Chris@17
|
826 $patterns[] = $tokenPattern;
|
Chris@17
|
827 }
|
Chris@17
|
828 }
|
Chris@17
|
829
|
Chris@17
|
830 // Make sure we don't skip the last token.
|
Chris@17
|
831 if ($isLastChar === false && $i === ($length - 1)) {
|
Chris@17
|
832 $i--;
|
Chris@17
|
833 }
|
Chris@17
|
834 }//end if
|
Chris@17
|
835
|
Chris@17
|
836 // Add the skip pattern *after* we have processed
|
Chris@17
|
837 // all the tokens from the end of the last skip pattern
|
Chris@17
|
838 // to the start of this skip pattern.
|
Chris@17
|
839 if ($specialPattern !== false) {
|
Chris@17
|
840 $patterns[] = $specialPattern;
|
Chris@17
|
841 }
|
Chris@17
|
842 }//end for
|
Chris@17
|
843
|
Chris@17
|
844 return $patterns;
|
Chris@17
|
845
|
Chris@17
|
846 }//end parse()
|
Chris@17
|
847
|
Chris@17
|
848
|
Chris@17
|
849 /**
|
Chris@17
|
850 * Creates a skip pattern.
|
Chris@17
|
851 *
|
Chris@17
|
852 * @param string $pattern The pattern being parsed.
|
Chris@17
|
853 * @param string $from The token content that the skip pattern starts from.
|
Chris@17
|
854 *
|
Chris@17
|
855 * @return array The pattern step.
|
Chris@17
|
856 * @see createTokenPattern()
|
Chris@17
|
857 * @see parse()
|
Chris@17
|
858 */
|
Chris@17
|
859 private function createSkipPattern($pattern, $from)
|
Chris@17
|
860 {
|
Chris@17
|
861 $skip = ['type' => 'skip'];
|
Chris@17
|
862
|
Chris@17
|
863 $nestedParenthesis = 0;
|
Chris@17
|
864 $nestedBraces = 0;
|
Chris@17
|
865 for ($start = $from; $start >= 0; $start--) {
|
Chris@17
|
866 switch ($pattern[$start]) {
|
Chris@17
|
867 case '(':
|
Chris@17
|
868 if ($nestedParenthesis === 0) {
|
Chris@17
|
869 $skip['to'] = 'parenthesis_closer';
|
Chris@17
|
870 }
|
Chris@17
|
871
|
Chris@17
|
872 $nestedParenthesis--;
|
Chris@17
|
873 break;
|
Chris@17
|
874 case '{':
|
Chris@17
|
875 if ($nestedBraces === 0) {
|
Chris@17
|
876 $skip['to'] = 'scope_closer';
|
Chris@17
|
877 }
|
Chris@17
|
878
|
Chris@17
|
879 $nestedBraces--;
|
Chris@17
|
880 break;
|
Chris@17
|
881 case '}':
|
Chris@17
|
882 $nestedBraces++;
|
Chris@17
|
883 break;
|
Chris@17
|
884 case ')':
|
Chris@17
|
885 $nestedParenthesis++;
|
Chris@17
|
886 break;
|
Chris@17
|
887 }//end switch
|
Chris@17
|
888
|
Chris@17
|
889 if (isset($skip['to']) === true) {
|
Chris@17
|
890 break;
|
Chris@17
|
891 }
|
Chris@17
|
892 }//end for
|
Chris@17
|
893
|
Chris@17
|
894 if (isset($skip['to']) === false) {
|
Chris@17
|
895 $skip['to'] = 'unknown';
|
Chris@17
|
896 }
|
Chris@17
|
897
|
Chris@17
|
898 return $skip;
|
Chris@17
|
899
|
Chris@17
|
900 }//end createSkipPattern()
|
Chris@17
|
901
|
Chris@17
|
902
|
Chris@17
|
903 /**
|
Chris@17
|
904 * Creates a token pattern.
|
Chris@17
|
905 *
|
Chris@17
|
906 * @param string $str The tokens string that the pattern should match.
|
Chris@17
|
907 *
|
Chris@17
|
908 * @return array The pattern step.
|
Chris@17
|
909 * @see createSkipPattern()
|
Chris@17
|
910 * @see parse()
|
Chris@17
|
911 */
|
Chris@17
|
912 private function createTokenPattern($str)
|
Chris@17
|
913 {
|
Chris@17
|
914 // Don't add a space after the closing php tag as it will add a new
|
Chris@17
|
915 // whitespace token.
|
Chris@17
|
916 $tokenizer = new PHP('<?php '.$str.'?>', null);
|
Chris@17
|
917
|
Chris@17
|
918 // Remove the <?php tag from the front and the end php tag from the back.
|
Chris@17
|
919 $tokens = $tokenizer->getTokens();
|
Chris@17
|
920 $tokens = array_slice($tokens, 1, (count($tokens) - 2));
|
Chris@17
|
921
|
Chris@17
|
922 $patterns = [];
|
Chris@17
|
923 foreach ($tokens as $patternInfo) {
|
Chris@17
|
924 $patterns[] = [
|
Chris@17
|
925 'type' => 'token',
|
Chris@17
|
926 'token' => $patternInfo['code'],
|
Chris@17
|
927 'value' => $patternInfo['content'],
|
Chris@17
|
928 ];
|
Chris@17
|
929 }
|
Chris@17
|
930
|
Chris@17
|
931 return $patterns;
|
Chris@17
|
932
|
Chris@17
|
933 }//end createTokenPattern()
|
Chris@17
|
934
|
Chris@17
|
935
|
Chris@17
|
936 }//end class
|