PageRenderTime 56ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/php/PHP_CodeSniffer/src/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php

http://github.com/jonswar/perl-code-tidyall
PHP | 402 lines | 296 code | 52 blank | 54 comment | 88 complexity | 9b39d420682142b141951952274106dd MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, 0BSD, MIT
  1. <?php
  2. /**
  3. * Checks the indentation of embedded PHP code segments.
  4. *
  5. * @author Greg Sherwood <gsherwood@squiz.net>
  6. * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7. * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8. */
  9. namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\PHP;
  10. use PHP_CodeSniffer\Sniffs\Sniff;
  11. use PHP_CodeSniffer\Files\File;
  12. use PHP_CodeSniffer\Util\Tokens;
  13. class EmbeddedPhpSniff implements Sniff
  14. {
  15. /**
  16. * Returns an array of tokens this test wants to listen for.
  17. *
  18. * @return array
  19. */
  20. public function register()
  21. {
  22. return [T_OPEN_TAG];
  23. }//end register()
  24. /**
  25. * Processes this test, when one of its tokens is encountered.
  26. *
  27. * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  28. * @param int $stackPtr The position of the current token in the
  29. * stack passed in $tokens.
  30. *
  31. * @return void
  32. */
  33. public function process(File $phpcsFile, $stackPtr)
  34. {
  35. $tokens = $phpcsFile->getTokens();
  36. // If the close php tag is on the same line as the opening
  37. // then we have an inline embedded PHP block.
  38. $closeTag = $phpcsFile->findNext(T_CLOSE_TAG, $stackPtr);
  39. if ($closeTag === false || $tokens[$stackPtr]['line'] !== $tokens[$closeTag]['line']) {
  40. $this->validateMultilineEmbeddedPhp($phpcsFile, $stackPtr);
  41. } else {
  42. $this->validateInlineEmbeddedPhp($phpcsFile, $stackPtr);
  43. }
  44. }//end process()
  45. /**
  46. * Validates embedded PHP that exists on multiple lines.
  47. *
  48. * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  49. * @param int $stackPtr The position of the current token in the
  50. * stack passed in $tokens.
  51. *
  52. * @return void
  53. */
  54. private function validateMultilineEmbeddedPhp($phpcsFile, $stackPtr)
  55. {
  56. $tokens = $phpcsFile->getTokens();
  57. $prevTag = $phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1));
  58. if ($prevTag === false) {
  59. // This is the first open tag.
  60. return;
  61. }
  62. $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
  63. $closingTag = $phpcsFile->findNext(T_CLOSE_TAG, $stackPtr);
  64. if ($closingTag !== false) {
  65. $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), $phpcsFile->numTokens, true);
  66. if ($nextContent === false) {
  67. // Final closing tag. It will be handled elsewhere.
  68. return;
  69. }
  70. // We have an opening and a closing tag, that lie within other content.
  71. if ($firstContent === $closingTag) {
  72. $error = 'Empty embedded PHP tag found';
  73. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty');
  74. if ($fix === true) {
  75. $phpcsFile->fixer->beginChangeset();
  76. for ($i = $stackPtr; $i <= $closingTag; $i++) {
  77. $phpcsFile->fixer->replaceToken($i, '');
  78. }
  79. $phpcsFile->fixer->endChangeset();
  80. }
  81. return;
  82. }
  83. }//end if
  84. if ($tokens[$firstContent]['line'] === $tokens[$stackPtr]['line']) {
  85. $error = 'Opening PHP tag must be on a line by itself';
  86. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ContentAfterOpen');
  87. if ($fix === true) {
  88. $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true);
  89. $padding = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])));
  90. $phpcsFile->fixer->beginChangeset();
  91. $phpcsFile->fixer->addNewline($stackPtr);
  92. $phpcsFile->fixer->addContent($stackPtr, str_repeat(' ', $padding));
  93. $phpcsFile->fixer->endChangeset();
  94. }
  95. } else {
  96. // Check the indent of the first line, except if it is a scope closer.
  97. if (isset($tokens[$firstContent]['scope_closer']) === false
  98. || $tokens[$firstContent]['scope_closer'] !== $firstContent
  99. ) {
  100. // Check for a blank line at the top.
  101. if ($tokens[$firstContent]['line'] > ($tokens[$stackPtr]['line'] + 1)) {
  102. // Find a token on the blank line to throw the error on.
  103. $i = $stackPtr;
  104. do {
  105. $i++;
  106. } while ($tokens[$i]['line'] !== ($tokens[$stackPtr]['line'] + 1));
  107. $error = 'Blank line found at start of embedded PHP content';
  108. $fix = $phpcsFile->addFixableError($error, $i, 'SpacingBefore');
  109. if ($fix === true) {
  110. $phpcsFile->fixer->beginChangeset();
  111. for ($i = ($stackPtr + 1); $i < $firstContent; $i++) {
  112. if ($tokens[$i]['line'] === $tokens[$firstContent]['line']
  113. || $tokens[$i]['line'] === $tokens[$stackPtr]['line']
  114. ) {
  115. continue;
  116. }
  117. $phpcsFile->fixer->replaceToken($i, '');
  118. }
  119. $phpcsFile->fixer->endChangeset();
  120. }
  121. }//end if
  122. $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr);
  123. if ($first === false) {
  124. $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr);
  125. $indent = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])));
  126. } else {
  127. $indent = ($tokens[($first + 1)]['column'] - 1);
  128. }
  129. $contentColumn = ($tokens[$firstContent]['column'] - 1);
  130. if ($contentColumn !== $indent) {
  131. $error = 'First line of embedded PHP code must be indented %s spaces; %s found';
  132. $data = [
  133. $indent,
  134. $contentColumn,
  135. ];
  136. $fix = $phpcsFile->addFixableError($error, $firstContent, 'Indent', $data);
  137. if ($fix === true) {
  138. $padding = str_repeat(' ', $indent);
  139. if ($contentColumn === 0) {
  140. $phpcsFile->fixer->addContentBefore($firstContent, $padding);
  141. } else {
  142. $phpcsFile->fixer->replaceToken(($firstContent - 1), $padding);
  143. }
  144. }
  145. }
  146. }//end if
  147. }//end if
  148. $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
  149. if ($tokens[$lastContent]['line'] === $tokens[$stackPtr]['line']
  150. && trim($tokens[$lastContent]['content']) !== ''
  151. ) {
  152. $error = 'Opening PHP tag must be on a line by itself';
  153. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeOpen');
  154. if ($fix === true) {
  155. $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr);
  156. if ($first === false) {
  157. $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr);
  158. $padding = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])));
  159. } else {
  160. $padding = ($tokens[($first + 1)]['column'] - 1);
  161. }
  162. $phpcsFile->fixer->addContentBefore($stackPtr, $phpcsFile->eolChar.str_repeat(' ', $padding));
  163. }
  164. } else {
  165. // Find the first token on the first non-empty line we find.
  166. for ($first = ($stackPtr - 1); $first > 0; $first--) {
  167. if ($tokens[$first]['line'] === $tokens[$stackPtr]['line']) {
  168. continue;
  169. } else if (trim($tokens[$first]['content']) !== '') {
  170. $first = $phpcsFile->findFirstOnLine([], $first, true);
  171. break;
  172. }
  173. }
  174. $expected = 0;
  175. if ($tokens[$first]['code'] === T_INLINE_HTML
  176. && trim($tokens[$first]['content']) !== ''
  177. ) {
  178. $expected = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])));
  179. } else if ($tokens[$first]['code'] === T_WHITESPACE) {
  180. $expected = ($tokens[($first + 1)]['column'] - 1);
  181. }
  182. $expected += 4;
  183. $found = ($tokens[$stackPtr]['column'] - 1);
  184. if ($found > $expected) {
  185. $error = 'Opening PHP tag indent incorrect; expected no more than %s spaces but found %s';
  186. $data = [
  187. $expected,
  188. $found,
  189. ];
  190. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'OpenTagIndent', $data);
  191. if ($fix === true) {
  192. $phpcsFile->fixer->replaceToken(($stackPtr - 1), str_repeat(' ', $expected));
  193. }
  194. }
  195. }//end if
  196. if ($closingTag === false) {
  197. return;
  198. }
  199. $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closingTag - 1), ($stackPtr + 1), true);
  200. $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($closingTag + 1), null, true);
  201. if ($tokens[$lastContent]['line'] === $tokens[$closingTag]['line']) {
  202. $error = 'Closing PHP tag must be on a line by itself';
  203. $fix = $phpcsFile->addFixableError($error, $closingTag, 'ContentBeforeEnd');
  204. if ($fix === true) {
  205. $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true);
  206. $phpcsFile->fixer->beginChangeset();
  207. $phpcsFile->fixer->addContentBefore($closingTag, str_repeat(' ', ($tokens[$first]['column'] - 1)));
  208. $phpcsFile->fixer->addNewlineBefore($closingTag);
  209. $phpcsFile->fixer->endChangeset();
  210. }
  211. } else if ($tokens[$nextContent]['line'] === $tokens[$closingTag]['line']) {
  212. $error = 'Closing PHP tag must be on a line by itself';
  213. $fix = $phpcsFile->addFixableError($error, $closingTag, 'ContentAfterEnd');
  214. if ($fix === true) {
  215. $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $closingTag, true);
  216. $phpcsFile->fixer->beginChangeset();
  217. $phpcsFile->fixer->addNewline($closingTag);
  218. $phpcsFile->fixer->addContent($closingTag, str_repeat(' ', ($tokens[$first]['column'] - 1)));
  219. $phpcsFile->fixer->endChangeset();
  220. }
  221. }//end if
  222. $next = $phpcsFile->findNext(T_OPEN_TAG, ($closingTag + 1));
  223. if ($next === false) {
  224. return;
  225. }
  226. // Check for a blank line at the bottom.
  227. if ((isset($tokens[$lastContent]['scope_closer']) === false
  228. || $tokens[$lastContent]['scope_closer'] !== $lastContent)
  229. && $tokens[$lastContent]['line'] < ($tokens[$closingTag]['line'] - 1)
  230. ) {
  231. // Find a token on the blank line to throw the error on.
  232. $i = $closingTag;
  233. do {
  234. $i--;
  235. } while ($tokens[$i]['line'] !== ($tokens[$closingTag]['line'] - 1));
  236. $error = 'Blank line found at end of embedded PHP content';
  237. $fix = $phpcsFile->addFixableError($error, $i, 'SpacingAfter');
  238. if ($fix === true) {
  239. $phpcsFile->fixer->beginChangeset();
  240. for ($i = ($lastContent + 1); $i < $closingTag; $i++) {
  241. if ($tokens[$i]['line'] === $tokens[$lastContent]['line']
  242. || $tokens[$i]['line'] === $tokens[$closingTag]['line']
  243. ) {
  244. continue;
  245. }
  246. $phpcsFile->fixer->replaceToken($i, '');
  247. }
  248. $phpcsFile->fixer->endChangeset();
  249. }
  250. }//end if
  251. }//end validateMultilineEmbeddedPhp()
  252. /**
  253. * Validates embedded PHP that exists on one line.
  254. *
  255. * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  256. * @param int $stackPtr The position of the current token in the
  257. * stack passed in $tokens.
  258. *
  259. * @return void
  260. */
  261. private function validateInlineEmbeddedPhp($phpcsFile, $stackPtr)
  262. {
  263. $tokens = $phpcsFile->getTokens();
  264. // We only want one line PHP sections, so return if the closing tag is
  265. // on the next line.
  266. $closeTag = $phpcsFile->findNext(T_CLOSE_TAG, $stackPtr, null, false);
  267. if ($tokens[$stackPtr]['line'] !== $tokens[$closeTag]['line']) {
  268. return;
  269. }
  270. // Check that there is one, and only one space at the start of the statement.
  271. $firstContent = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $closeTag, true);
  272. if ($firstContent === false) {
  273. $error = 'Empty embedded PHP tag found';
  274. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty');
  275. if ($fix === true) {
  276. $phpcsFile->fixer->beginChangeset();
  277. for ($i = $stackPtr; $i <= $closeTag; $i++) {
  278. $phpcsFile->fixer->replaceToken($i, '');
  279. }
  280. $phpcsFile->fixer->endChangeset();
  281. }
  282. return;
  283. }
  284. // The open tag token always contains a single space after it.
  285. $leadingSpace = 1;
  286. if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) {
  287. $leadingSpace = ($tokens[($stackPtr + 1)]['length'] + 1);
  288. }
  289. if ($leadingSpace !== 1) {
  290. $error = 'Expected 1 space after opening PHP tag; %s found';
  291. $data = [$leadingSpace];
  292. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfterOpen', $data);
  293. if ($fix === true) {
  294. $phpcsFile->fixer->replaceToken(($stackPtr + 1), '');
  295. }
  296. }
  297. $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($closeTag - 1), $stackPtr, true);
  298. if ($prev !== $stackPtr) {
  299. if ((isset($tokens[$prev]['scope_opener']) === false
  300. || $tokens[$prev]['scope_opener'] !== $prev)
  301. && (isset($tokens[$prev]['scope_closer']) === false
  302. || $tokens[$prev]['scope_closer'] !== $prev)
  303. && $tokens[$prev]['code'] !== T_SEMICOLON
  304. ) {
  305. $error = 'Inline PHP statement must end with a semicolon';
  306. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSemicolon');
  307. if ($fix === true) {
  308. $phpcsFile->fixer->addContent($prev, ';');
  309. }
  310. } else if ($tokens[$prev]['code'] === T_SEMICOLON) {
  311. $statementCount = 1;
  312. for ($i = ($stackPtr + 1); $i < $prev; $i++) {
  313. if ($tokens[$i]['code'] === T_SEMICOLON) {
  314. $statementCount++;
  315. }
  316. }
  317. if ($statementCount > 1) {
  318. $error = 'Inline PHP statement must contain a single statement; %s found';
  319. $data = [$statementCount];
  320. $phpcsFile->addError($error, $stackPtr, 'MultipleStatements', $data);
  321. }
  322. }
  323. }//end if
  324. $trailingSpace = 0;
  325. if ($tokens[($closeTag - 1)]['code'] === T_WHITESPACE) {
  326. $trailingSpace = $tokens[($closeTag - 1)]['length'];
  327. } else if (($tokens[($closeTag - 1)]['code'] === T_COMMENT
  328. || isset(Tokens::$phpcsCommentTokens[$tokens[($closeTag - 1)]['code']]) === true)
  329. && substr($tokens[($closeTag - 1)]['content'], -1) === ' '
  330. ) {
  331. $trailingSpace = (strlen($tokens[($closeTag - 1)]['content']) - strlen(rtrim($tokens[($closeTag - 1)]['content'])));
  332. }
  333. if ($trailingSpace !== 1) {
  334. $error = 'Expected 1 space before closing PHP tag; %s found';
  335. $data = [$trailingSpace];
  336. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBeforeClose', $data);
  337. if ($fix === true) {
  338. if ($trailingSpace === 0) {
  339. $phpcsFile->fixer->addContentBefore($closeTag, ' ');
  340. } else if ($tokens[($closeTag - 1)]['code'] === T_COMMENT
  341. || isset(Tokens::$phpcsCommentTokens[$tokens[($closeTag - 1)]['code']]) === true
  342. ) {
  343. $phpcsFile->fixer->replaceToken(($closeTag - 1), rtrim($tokens[($closeTag - 1)]['content']).' ');
  344. } else {
  345. $phpcsFile->fixer->replaceToken(($closeTag - 1), ' ');
  346. }
  347. }
  348. }
  349. }//end validateInlineEmbeddedPhp()
  350. }//end class