PageRenderTime 45ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/fabpot/php-cs-fixer/Symfony/CS/Fixer/PSR2/BracesFixer.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 608 lines | 530 code | 44 blank | 34 comment | 26 complexity | f9bf828775ff628dd6c089e1456fb93f MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the PHP CS utility.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Symfony\CS\Fixer\PSR2;
  11. use Symfony\CS\AbstractFixer;
  12. use Symfony\CS\Tokenizer\Token;
  13. use Symfony\CS\Tokenizer\Tokens;
  14. /**
  15. * Fixer for rules defined in PSR2 ¶4.1, ¶4.4, ¶5.
  16. *
  17. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  18. */
  19. class BracesFixer extends AbstractFixer
  20. {
  21. /**
  22. * {@inheritdoc}
  23. */
  24. public function fix(\SplFileInfo $file, $content)
  25. {
  26. $tokens = Tokens::fromCode($content);
  27. $this->fixCommentBeforeBrace($tokens);
  28. $this->fixMissingControlBraces($tokens);
  29. $this->fixIndents($tokens);
  30. $this->fixControlContinuationBraces($tokens);
  31. $this->fixSpaceAroundToken($tokens);
  32. $this->fixDoWhile($tokens);
  33. $this->fixLambdas($tokens);
  34. // Set code to itself to redo tokenizer work, that will guard as against token collection corruption.
  35. // TODO: This MUST be removed on 2.0-dev version, where we add more transformers (and lack of them causes corruption on 1.x line).
  36. $code = $tokens->generateCode();
  37. $tokens->setCode($code);
  38. return $code;
  39. }
  40. /**
  41. * {@inheritdoc}
  42. */
  43. public function getDescription()
  44. {
  45. return 'The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.';
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. public function getPriority()
  51. {
  52. // should be run after the ElseIfFixer and DuplicateSemicolonFixer
  53. return -25;
  54. }
  55. private function fixCommentBeforeBrace(Tokens $tokens)
  56. {
  57. $controlTokens = $this->getControlTokens();
  58. for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
  59. $token = $tokens[$index];
  60. if (!$token->isGivenKind($controlTokens)) {
  61. continue;
  62. }
  63. $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
  64. $afterParenthesisIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex);
  65. $afterParenthesisToken = $tokens[$afterParenthesisIndex];
  66. if (!$afterParenthesisToken->isComment()) {
  67. continue;
  68. }
  69. $afterCommentIndex = $tokens->getNextMeaningfulToken($afterParenthesisIndex);
  70. $afterCommentToken = $tokens[$afterCommentIndex];
  71. if (!$afterCommentToken->equals('{')) {
  72. continue;
  73. }
  74. $tokenTmp = $tokens[$afterCommentIndex];
  75. $tokens[$afterCommentIndex - 1]->setContent(rtrim($tokens[$afterCommentIndex - 1]->getContent()));
  76. for ($i = $afterCommentIndex; $i > $afterParenthesisIndex; --$i) {
  77. $tokens[$i] = $tokens[$i - 1];
  78. }
  79. $tokens[$afterParenthesisIndex] = $tokenTmp;
  80. }
  81. }
  82. private function fixControlContinuationBraces(Tokens $tokens)
  83. {
  84. $controlContinuationTokens = $this->getControlContinuationTokens();
  85. for ($index = count($tokens) - 1; 0 <= $index; --$index) {
  86. $token = $tokens[$index];
  87. if (!$token->isGivenKind($controlContinuationTokens)) {
  88. continue;
  89. }
  90. $prevIndex = $tokens->getPrevNonWhitespace($index);
  91. $prevToken = $tokens[$prevIndex];
  92. if (!$prevToken->equals('}')) {
  93. continue;
  94. }
  95. $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' ');
  96. }
  97. }
  98. private function fixDoWhile(Tokens $tokens)
  99. {
  100. for ($index = count($tokens) - 1; 0 <= $index; --$index) {
  101. $token = $tokens[$index];
  102. if (!$token->isGivenKind(T_DO)) {
  103. continue;
  104. }
  105. $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
  106. $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex);
  107. $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex);
  108. $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($endBraceIndex);
  109. $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex];
  110. if (!$nextNonWhitespaceToken->isGivenKind(T_WHILE)) {
  111. continue;
  112. }
  113. $tokens->ensureWhitespaceAtIndex($nextNonWhitespaceIndex - 1, 1, ' ');
  114. }
  115. }
  116. private function fixIndents(Tokens $tokens)
  117. {
  118. $classyTokens = $this->getClassyTokens();
  119. $classyAndFunctionTokens = array_merge(array(T_FUNCTION), $classyTokens);
  120. $controlTokens = $this->getControlTokens();
  121. $indentTokens = array_filter(
  122. array_merge($classyAndFunctionTokens, $controlTokens),
  123. function ($item) {
  124. return T_SWITCH !== $item;
  125. }
  126. );
  127. for ($index = 0, $limit = count($tokens); $index < $limit; ++$index) {
  128. $token = $tokens[$index];
  129. // if token is not a structure element - continue
  130. if (!$token->isGivenKind($indentTokens)) {
  131. continue;
  132. }
  133. // do not change indent for lambda functions
  134. if ($token->isGivenKind(T_FUNCTION) && $tokens->isLambda($index)) {
  135. continue;
  136. }
  137. if ($token->isGivenKind($classyAndFunctionTokens)) {
  138. $startBraceIndex = $tokens->getNextTokenOfKind($index, array(';', '{'));
  139. $startBraceToken = $tokens[$startBraceIndex];
  140. } else {
  141. $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
  142. $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex);
  143. $startBraceToken = $tokens[$startBraceIndex];
  144. }
  145. // structure without braces block - nothing to do, e.g. do { } while (true);
  146. if (!$startBraceToken->equals('{')) {
  147. continue;
  148. }
  149. $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex);
  150. $indent = $this->detectIndent($tokens, $index);
  151. // fix indent near closing brace
  152. $tokens->ensureWhitespaceAtIndex($endBraceIndex - 1, 1, "\n".$indent);
  153. // fix indent between braces
  154. $lastCommaIndex = $tokens->getPrevTokenOfKind($endBraceIndex - 1, array(';', '}'));
  155. $nestLevel = 1;
  156. for ($nestIndex = $lastCommaIndex; $nestIndex >= $startBraceIndex; --$nestIndex) {
  157. $nestToken = $tokens[$nestIndex];
  158. if ($nestToken->equals(')')) {
  159. $nestIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nestIndex, false);
  160. continue;
  161. }
  162. if (1 === $nestLevel && $nestToken->equalsAny(array(';', '}'))) {
  163. $nextNonWhitespaceNestToken = $tokens[$tokens->getNextNonWhitespace($nestIndex)];
  164. if (
  165. // next Token is not a comment
  166. !$nextNonWhitespaceNestToken->isComment() &&
  167. // and it is not a `$foo = function () {};` situation
  168. !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equalsAny(array(';', ',', ']'))) &&
  169. // and it is not a `Foo::{bar}()` situation
  170. !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equals('(')) &&
  171. // and it is not a `${"a"}->...` and `${"b{$foo}"}->...` situation
  172. !($nestToken->equals('}') && $tokens[$nestIndex - 1]->equalsAny(array('"', "'", array(T_CONSTANT_ENCAPSED_STRING))))
  173. ) {
  174. if ($nextNonWhitespaceNestToken->isGivenKind($this->getControlContinuationTokens())) {
  175. $whitespace = ' ';
  176. } else {
  177. $nextToken = $tokens[$nestIndex + 1];
  178. $nextWhitespace = '';
  179. if ($nextToken->isWhitespace()) {
  180. $nextWhitespace = rtrim($nextToken->getContent(), " \t");
  181. if (strlen($nextWhitespace) && "\n" === $nextWhitespace[strlen($nextWhitespace) - 1]) {
  182. $nextWhitespace = substr($nextWhitespace, 0, -1);
  183. }
  184. }
  185. $whitespace = $nextWhitespace."\n".$indent;
  186. if (!$nextNonWhitespaceNestToken->equals('}')) {
  187. $whitespace .= ' ';
  188. }
  189. }
  190. $tokens->ensureWhitespaceAtIndex($nestIndex + 1, 0, $whitespace);
  191. }
  192. }
  193. if ($nestToken->equals('}')) {
  194. ++$nestLevel;
  195. continue;
  196. }
  197. if ($nestToken->equals('{')) {
  198. --$nestLevel;
  199. continue;
  200. }
  201. }
  202. // fix indent near opening brace
  203. if (isset($tokens[$startBraceIndex + 2]) && $tokens[$startBraceIndex + 2]->equals('}')) {
  204. $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, "\n".$indent);
  205. } elseif (!$tokens[$index]->isClassy()) {
  206. $nextToken = $tokens[$startBraceIndex + 1];
  207. $nextNonWhitespaceToken = $tokens[$tokens->getNextNonWhitespace($startBraceIndex)];
  208. // set indent only if it is not a case, when comment is following { in same line
  209. if (
  210. !$nextNonWhitespaceToken->isComment()
  211. || !($nextToken->isWhitespace() && $nextToken->isWhitespace(array('whitespaces' => " \t")))
  212. && substr_count($nextToken->getContent(), "\n") === 1 // preserve blank lines
  213. ) {
  214. $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, "\n".$indent.' ');
  215. }
  216. } else {
  217. $nextToken = $tokens[$startBraceIndex + 1];
  218. if (!$nextToken->isWhitespace()) {
  219. $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, "\n".$indent.' ');
  220. } else {
  221. $tmpIndent = trim($nextToken->getContent(), " \t").$indent.' ';
  222. if (!isset($tmpIndent[0]) || "\n" !== $tmpIndent[0]) {
  223. $tmpIndent = "\n".$tmpIndent;
  224. }
  225. $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, $tmpIndent);
  226. }
  227. }
  228. if ($token->isGivenKind($classyTokens)) {
  229. $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, "\n".$indent);
  230. } elseif ($token->isGivenKind(T_FUNCTION)) {
  231. $closingParenthesisIndex = $tokens->getPrevTokenOfKind($startBraceIndex, array(')'));
  232. $prevToken = $tokens[$closingParenthesisIndex - 1];
  233. if ($prevToken->isWhitespace() && false !== strpos($prevToken->getContent(), "\n")) {
  234. $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' ');
  235. } else {
  236. $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, "\n".$indent);
  237. }
  238. } else {
  239. $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' ');
  240. }
  241. // reset loop limit due to collection change
  242. $limit = count($tokens);
  243. }
  244. }
  245. private function fixLambdas(Tokens $tokens)
  246. {
  247. for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
  248. $token = $tokens[$index];
  249. if (!$token->isGivenKind(T_FUNCTION) || !$tokens->isLambda($index)) {
  250. continue;
  251. }
  252. $nextIndex = $tokens->getNextTokenOfKind($index, array('{'));
  253. $tokens->ensureWhitespaceAtIndex($nextIndex - 1, 1, ' ');
  254. }
  255. }
  256. private function fixMissingControlBraces(Tokens $tokens)
  257. {
  258. $controlTokens = $this->getControlTokens();
  259. for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
  260. $token = $tokens[$index];
  261. if (!$token->isGivenKind($controlTokens)) {
  262. continue;
  263. }
  264. $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
  265. $tokenAfterParenthesis = $tokens[$tokens->getNextMeaningfulToken($parenthesisEndIndex)];
  266. // if Token after parenthesis is { then we do not need to insert brace, but to fix whitespace before it
  267. if ($tokenAfterParenthesis->equals('{')) {
  268. $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' ');
  269. continue;
  270. }
  271. // do not add braces for cases:
  272. // - structure without block, e.g. while ($iter->next());
  273. // - structure with block, e.g. while ($i) {...}, while ($i) : {...} endwhile;
  274. if ($tokenAfterParenthesis->equalsAny(array(';', '{', ':'))) {
  275. continue;
  276. }
  277. $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
  278. // insert closing brace
  279. $tokens->insertAt($statementEndIndex + 1, array(new Token(array(T_WHITESPACE, ' ')), new Token('}')));
  280. // insert missing `;` if needed
  281. if (!$tokens[$statementEndIndex]->equalsAny(array(';', '}'))) {
  282. $tokens->insertAt($statementEndIndex + 1, new Token(';'));
  283. }
  284. // insert opening brace
  285. $tokens->insertAt($parenthesisEndIndex + 1, new Token('{'));
  286. $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' ');
  287. }
  288. }
  289. private function fixSpaceAroundToken(Tokens $tokens)
  290. {
  291. $controlTokens = $this->getControlTokens();
  292. for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
  293. $token = $tokens[$index];
  294. if ($token->isGivenKind($controlTokens) || $token->isGivenKind(T_USE)) {
  295. $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index);
  296. if (!$tokens[$nextNonWhitespaceIndex]->equals(':')) {
  297. $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' ');
  298. }
  299. $prevToken = $tokens[$index - 1];
  300. if (!$prevToken->isWhitespace() && !$prevToken->isComment() && !$prevToken->isGivenKind(T_OPEN_TAG)) {
  301. $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' ');
  302. }
  303. }
  304. }
  305. }
  306. /**
  307. * @param Tokens $tokens
  308. * @param int $index
  309. *
  310. * @return string
  311. */
  312. private function detectIndent(Tokens $tokens, $index)
  313. {
  314. static $goBackTokens = array(T_ABSTRACT, T_FINAL, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC);
  315. $token = $tokens[$index];
  316. if ($token->isGivenKind($goBackTokens) || $token->isClassy() || $token->isGivenKind(T_FUNCTION)) {
  317. $prevIndex = $tokens->getPrevNonWhitespace($index);
  318. $prevToken = $tokens[$prevIndex];
  319. if ($prevToken->isGivenKind($goBackTokens)) {
  320. return $this->detectIndent($tokens, $prevIndex);
  321. }
  322. }
  323. $prevIndex = $index - 1;
  324. $prevToken = $tokens[$prevIndex];
  325. if ($prevToken->equals('}')) {
  326. return $this->detectIndent($tokens, $prevIndex);
  327. }
  328. // if can not detect indent:
  329. if (!$prevToken->isWhitespace()) {
  330. return '';
  331. }
  332. $explodedContent = explode("\n", $prevToken->getContent());
  333. // proper decect indent for code: ` } else {`
  334. if (1 === count($explodedContent)) {
  335. if ($tokens[$index - 2]->equals('}')) {
  336. return $this->detectIndent($tokens, $index - 2);
  337. }
  338. }
  339. return end($explodedContent);
  340. }
  341. /**
  342. * @param Tokens $tokens
  343. * @param int $structureTokenIndex
  344. *
  345. * @return int
  346. */
  347. private function findParenthesisEnd(Tokens $tokens, $structureTokenIndex)
  348. {
  349. $nextIndex = $tokens->getNextNonWhitespace($structureTokenIndex);
  350. $nextToken = $tokens[$nextIndex];
  351. // return if next token is not opening parenthesis
  352. if (!$nextToken->equals('(')) {
  353. return $structureTokenIndex;
  354. }
  355. return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex);
  356. }
  357. private function findStatementEnd(Tokens $tokens, $parenthesisEndIndex)
  358. {
  359. $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
  360. $nextToken = $tokens[$nextIndex];
  361. if (!$nextToken) {
  362. return $parenthesisEndIndex;
  363. }
  364. if ($nextToken->equals('{')) {
  365. return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex);
  366. }
  367. if ($nextToken->isGivenKind($this->getControlTokens())) {
  368. $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex);
  369. $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
  370. if ($nextToken->isGivenKind(array(T_IF, T_TRY))) {
  371. $openingTokenKind = $nextToken->getId();
  372. while (true) {
  373. $nextIndex = $tokens->getNextMeaningfulToken($endIndex);
  374. $nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null;
  375. if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) {
  376. $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex);
  377. $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
  378. if ($nextToken->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) {
  379. return $endIndex;
  380. }
  381. } else {
  382. break;
  383. }
  384. }
  385. }
  386. return $endIndex;
  387. }
  388. $index = $parenthesisEndIndex;
  389. while (true) {
  390. $token = $tokens[++$index];
  391. // if there is some block in statement (eg lambda function) we need to skip it
  392. if ($token->equals('{')) {
  393. $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
  394. continue;
  395. }
  396. if ($token->equals(';')) {
  397. return $index;
  398. }
  399. if ($token->isGivenKind(T_CLOSE_TAG)) {
  400. return $tokens->getPrevNonWhitespace($index);
  401. }
  402. }
  403. throw new \RuntimeException('Statement end not found');
  404. }
  405. private function getClassyTokens()
  406. {
  407. static $tokens = null;
  408. if (null === $tokens) {
  409. $tokens = array(T_CLASS, T_INTERFACE);
  410. if (defined('T_TRAIT')) {
  411. $tokens[] = T_TRAIT;
  412. }
  413. }
  414. return $tokens;
  415. }
  416. private function getControlTokens()
  417. {
  418. static $tokens = null;
  419. if (null === $tokens) {
  420. $tokens = array(
  421. T_DECLARE,
  422. T_DO,
  423. T_ELSE,
  424. T_ELSEIF,
  425. T_FOR,
  426. T_FOREACH,
  427. T_IF,
  428. T_WHILE,
  429. T_TRY,
  430. T_CATCH,
  431. T_SWITCH,
  432. );
  433. if (defined('T_FINALLY')) {
  434. $tokens[] = T_FINALLY;
  435. }
  436. }
  437. return $tokens;
  438. }
  439. private function getControlContinuationTokens()
  440. {
  441. static $tokens = null;
  442. if (null === $tokens) {
  443. $tokens = array(
  444. T_ELSE,
  445. T_ELSEIF,
  446. T_CATCH,
  447. );
  448. if (defined('T_FINALLY')) {
  449. $tokens[] = T_FINALLY;
  450. }
  451. }
  452. return $tokens;
  453. }
  454. private function getControlContinuationTokensForOpeningToken($openingTokenKind)
  455. {
  456. if ($openingTokenKind === T_IF) {
  457. return array(
  458. T_ELSE,
  459. T_ELSEIF,
  460. );
  461. }
  462. if ($openingTokenKind === T_TRY) {
  463. $tokens = array(T_CATCH);
  464. if (defined('T_FINALLY')) {
  465. $tokens[] = T_FINALLY;
  466. }
  467. return $tokens;
  468. }
  469. return array();
  470. }
  471. private function getFinalControlContinuationTokensForOpeningToken($openingTokenKind)
  472. {
  473. if ($openingTokenKind === T_IF) {
  474. return array(T_ELSE);
  475. }
  476. if ($openingTokenKind === T_TRY && defined('T_FINALLY')) {
  477. return array(T_FINALLY);
  478. }
  479. return array();
  480. }
  481. }