Drupal investigation

AbstractPatternSniff.php 36KB

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