PageRenderTime 27ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/script/lib/PHP/CodeSniffer/Standards/AbstractPatternSniff.php

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