├── .no-sublime-package ├── Main.sublime-menu ├── README.md ├── codeFormatter.php ├── install.txt ├── messages.json ├── phpfmt.py └── phpfmt.sublime-settings /.no-sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boeserwolf/sublime-phpfmt/adac16a4adb5f7b978e73b530e440287fb1f4598/.no-sublime-package -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "mnemonic": "n", 4 | "caption": "Preferences", 5 | "id": "preferences", 6 | "children": [ 7 | { 8 | "mnemonic": "P", 9 | "caption": "Package Settings", 10 | "id": "package-settings", 11 | "children": [ 12 | { 13 | "caption": "phpfmt", 14 | "children": [ 15 | { 16 | "caption": "Settings – Default", 17 | "args": { 18 | "file": "${packages}/phpfmt/phpfmt.sublime-settings" 19 | }, 20 | "command": "open_file" 21 | }, 22 | { 23 | "caption": "Settings – User", 24 | "args": { 25 | "file": "${packages}/User/phpfmt.sublime-settings" 26 | }, 27 | "command": "open_file" 28 | }, 29 | { 30 | "caption": "-" 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [php.fmt](https://github.com/dericofilho/php.tools) support for Sublime Text 2/3 2 | 3 | 4 | php.fmt and php.tools aim to help PHP development. One of the features, code formatting, now is embeded too in ST3. For now it formats automatically when you save the PHP file. 5 | 6 | 7 | ### Installation 8 | Install this plugin through Package Manager. 9 | 10 | It runs better with PHP 5.5 or newer installed in the machine running the plugin. Although there are reports saying it works seemingly correctly with PHP 5.3.24, however slow. 11 | 12 | 13 | ### Settings 14 | ``` 15 | // This option enabled the experimental formatting which complies with PSR1 and PSR2 standards 16 | "psr1_and_2":false, 17 | "php_bin":"/usr/local/bin/php", 18 | "indent_with_space":false 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /codeFormatter.php: -------------------------------------------------------------------------------- 1 | "); 27 | define("ST_IS_SMALLER", "<"); 28 | define("ST_MINUS", "-"); 29 | define("ST_MODULUS", "%"); 30 | define("ST_PARENTHESES_CLOSE", ")"); 31 | define("ST_PARENTHESES_OPEN", "("); 32 | define("ST_PLUS", "+"); 33 | define("ST_QUESTION", "?"); 34 | define("ST_QUOTE", '"'); 35 | define("ST_REFERENCE", "&"); 36 | define("ST_SEMI_COLON", ";"); 37 | define("ST_TIMES", "*"); 38 | 39 | final class AddMissingCurlyBraces extends FormatterPass { 40 | public function format($source) { 41 | $tmp = $this->addBraces($source); 42 | while (true) { 43 | $source = $this->addBraces($tmp); 44 | if ($source == $tmp) { 45 | break; 46 | } 47 | $tmp = $source; 48 | } 49 | return $source; 50 | } 51 | private function addBraces($source) { 52 | $this->tkns = token_get_all($source); 53 | $this->code = ''; 54 | while (list($index, $token) = each($this->tkns)) { 55 | list($id, $text) = $this->get_token($token); 56 | $this->ptr = $index; 57 | switch ($id) { 58 | case T_FOR: 59 | $this->append_code($text, false); 60 | $paren_count = null; 61 | while (list($index, $token) = each($this->tkns)) { 62 | list($id, $text) = $this->get_token($token); 63 | $this->ptr = $index; 64 | if (ST_PARENTHESES_OPEN == $id) { 65 | $paren_count++; 66 | } elseif (ST_PARENTHESES_CLOSE == $id) { 67 | $paren_count--; 68 | } 69 | $this->append_code($text, false); 70 | if (0 === $paren_count && !$this->is_token(array(T_COMMENT, T_DOC_COMMENT))) { 71 | break; 72 | } 73 | } 74 | if (!$this->is_token(ST_CURLY_OPEN) && !$this->is_token(ST_COLON)) { 75 | $ignore_count = 0; 76 | if (!$this->is_token(array(T_COMMENT, T_DOC_COMMENT), true)) { 77 | $this->append_code($this->new_line.'{'); 78 | } else { 79 | $this->append_code('{'); 80 | } 81 | while (list($index, $token) = each($this->tkns)) { 82 | list($id, $text) = $this->get_token($token); 83 | $this->ptr = $index; 84 | if (ST_PARENTHESES_OPEN == $id || ST_CURLY_OPEN == $id || ST_BRACKET_OPEN == $id) { 85 | $ignore_count++; 86 | } elseif (ST_PARENTHESES_CLOSE == $id || ST_CURLY_CLOSE == $id || ST_BRACKET_CLOSE == $id) { 87 | $ignore_count--; 88 | } 89 | $this->append_code($text, false); 90 | if ($ignore_count <= 0 && !($this->is_token(ST_CURLY_CLOSE) || $this->is_token(ST_SEMI_COLON) || $this->is_token(array(T_WHILE))) && (ST_CURLY_CLOSE == $id || ST_SEMI_COLON == $id || T_ELSE == $id || T_ELSEIF == $id)) { 91 | break; 92 | } 93 | } 94 | $this->append_code($this->get_crlf_indent().'}'.$this->get_crlf_indent(), false); 95 | break 2; 96 | } 97 | break; 98 | case T_IF: 99 | case T_ELSEIF: 100 | $this->append_code($text, false); 101 | $paren_count = null; 102 | while (list($index, $token) = each($this->tkns)) { 103 | list($id, $text) = $this->get_token($token); 104 | $this->ptr = $index; 105 | if (ST_PARENTHESES_OPEN == $id) { 106 | $paren_count++; 107 | } elseif (ST_PARENTHESES_CLOSE == $id) { 108 | $paren_count--; 109 | } 110 | $this->append_code($text, false); 111 | if (0 === $paren_count && !$this->is_token(array(T_COMMENT, T_DOC_COMMENT))) { 112 | break; 113 | } 114 | } 115 | if (!$this->is_token(ST_CURLY_OPEN) && !$this->is_token(ST_COLON)) { 116 | $ignore_count = 0; 117 | if (!$this->is_token(array(T_COMMENT, T_DOC_COMMENT), true)) { 118 | // $this->append_code($this->new_line.'{'.$this->new_line); 119 | $this->append_code($this->new_line.'{'); 120 | } else { 121 | // $this->append_code('{'.$this->new_line); 122 | $this->append_code('{'); 123 | } 124 | while (list($index, $token) = each($this->tkns)) { 125 | list($id, $text) = $this->get_token($token); 126 | $this->ptr = $index; 127 | if (ST_PARENTHESES_OPEN == $id || ST_CURLY_OPEN == $id || ST_BRACKET_OPEN == $id) { 128 | $ignore_count++; 129 | } elseif (ST_PARENTHESES_CLOSE == $id || ST_CURLY_CLOSE == $id || ST_BRACKET_CLOSE == $id) { 130 | $ignore_count--; 131 | } 132 | $this->append_code($text, false); 133 | if ($ignore_count <= 0 && !($this->is_token(ST_CURLY_CLOSE) || $this->is_token(ST_SEMI_COLON) || $this->is_token(array(T_WHILE))) && (ST_CURLY_CLOSE == $id || ST_SEMI_COLON == $id || T_ELSE == $id || T_ELSEIF == $id)) { 134 | break; 135 | } 136 | } 137 | $this->append_code($this->get_crlf_indent().'}'.$this->get_crlf_indent(), false); 138 | break 2; 139 | } 140 | break; 141 | case T_ELSE: 142 | $this->append_code($text, false); 143 | if (!$this->is_token(ST_CURLY_OPEN) && !$this->is_token(ST_COLON) && !$this->is_token(array(T_IF))) { 144 | $ignore_count = 0; 145 | $this->append_code('{'.$this->new_line); 146 | while (list($index, $token) = each($this->tkns)) { 147 | list($id, $text) = $this->get_token($token); 148 | $this->ptr = $index; 149 | if (ST_PARENTHESES_OPEN == $id || ST_CURLY_OPEN == $id || ST_BRACKET_OPEN == $id) { 150 | $ignore_count++; 151 | } elseif (ST_PARENTHESES_CLOSE == $id || ST_CURLY_CLOSE == $id || ST_BRACKET_CLOSE == $id) { 152 | $ignore_count--; 153 | } 154 | $this->append_code($text, false); 155 | if ($ignore_count <= 0 && !($this->is_token(ST_CURLY_CLOSE) || $this->is_token(ST_SEMI_COLON) || $this->is_token(array(T_WHILE))) && (ST_CURLY_CLOSE == $id || ST_SEMI_COLON == $id || T_ELSE == $id || T_ELSEIF == $id)) { 156 | break; 157 | } 158 | } 159 | $this->append_code($this->get_crlf_indent().'}'.$this->get_crlf_indent(), false); 160 | break 2; 161 | } 162 | break; 163 | default: 164 | $this->append_code($text, false); 165 | break; 166 | } 167 | } 168 | while (list($index, $token) = each($this->tkns)) { 169 | list($id, $text) = $this->get_token($token); 170 | $this->ptr = $index; 171 | $this->append_code($text, false); 172 | } 173 | 174 | return $this->code; 175 | } 176 | } 177 | 178 | final class AlignDoubleArrow extends FormatterPass { 179 | const ALIGNABLE_EQUAL = "\x2 EQUAL%d \x3"; 180 | public function format($source) { 181 | $this->tkns = token_get_all($source); 182 | $this->code = ''; 183 | 184 | while (list($index, $token) = each($this->tkns)) { 185 | list($id, $text) = $this->get_token($token); 186 | $this->ptr = $index; 187 | switch ($id) { 188 | 189 | case T_DOUBLE_ARROW: 190 | 191 | $this->append_code(self::ALIGNABLE_EQUAL.$text, false); 192 | break; 193 | 194 | default: 195 | $this->append_code($text, false); 196 | break; 197 | } 198 | } 199 | 200 | $lines = explode($this->new_line, $this->code); 201 | $lines_with_objop = []; 202 | $block_count = 0; 203 | 204 | foreach ($lines as $idx => $line) { 205 | if (substr_count($line, self::ALIGNABLE_EQUAL) > 0) { 206 | $lines_with_objop[$block_count][] = $idx; 207 | } else { 208 | $block_count++; 209 | } 210 | } 211 | 212 | $i = 0; 213 | foreach ($lines_with_objop as $group) { 214 | if (1 == sizeof($group)) { 215 | continue; 216 | } 217 | $i++; 218 | $farthest_objop = 0; 219 | foreach ($group as $idx) { 220 | $farthest_objop = max($farthest_objop, strpos($lines[$idx], self::ALIGNABLE_EQUAL)); 221 | } 222 | foreach ($group as $idx) { 223 | $line = $lines[$idx]; 224 | $current_objop = strpos($line, self::ALIGNABLE_EQUAL); 225 | $delta = abs($farthest_objop-$current_objop); 226 | if ($delta > 0) { 227 | $line = str_replace(self::ALIGNABLE_EQUAL, str_repeat(' ', $delta).self::ALIGNABLE_EQUAL, $line); 228 | $lines[$idx] = $line; 229 | } 230 | } 231 | } 232 | 233 | $this->code = str_replace(self::ALIGNABLE_EQUAL, '', implode($this->new_line, $lines)); 234 | 235 | return $this->code; 236 | } 237 | } 238 | 239 | final class AlignEquals extends FormatterPass { 240 | const ALIGNABLE_EQUAL = "\x2 EQUAL%d \x3"; 241 | public function format($source) { 242 | $this->tkns = token_get_all($source); 243 | $this->code = ''; 244 | $paren_count = 0; 245 | $bracket_count = 0; 246 | while (list($index, $token) = each($this->tkns)) { 247 | list($id, $text) = $this->get_token($token); 248 | $this->ptr = $index; 249 | switch ($id) { 250 | case ST_PARENTHESES_OPEN: 251 | $paren_count++; 252 | $this->append_code($text, false); 253 | break; 254 | case ST_PARENTHESES_CLOSE: 255 | $paren_count--; 256 | $this->append_code($text, false); 257 | break; 258 | case ST_BRACKET_OPEN: 259 | $bracket_count++; 260 | $this->append_code($text, false); 261 | break; 262 | case ST_BRACKET_CLOSE: 263 | $bracket_count--; 264 | $this->append_code($text, false); 265 | break; 266 | case ST_EQUAL: 267 | if (!$paren_count && !$bracket_count) { 268 | $this->append_code(self::ALIGNABLE_EQUAL.$text, false); 269 | break; 270 | } 271 | 272 | default: 273 | $this->append_code($text, false); 274 | break; 275 | } 276 | } 277 | 278 | $lines = explode($this->new_line, $this->code); 279 | $lines_with_objop = []; 280 | $block_count = 0; 281 | 282 | foreach ($lines as $idx => $line) { 283 | if (substr_count($line, self::ALIGNABLE_EQUAL) > 0) { 284 | $lines_with_objop[$block_count][] = $idx; 285 | } else { 286 | $block_count++; 287 | } 288 | } 289 | 290 | $i = 0; 291 | foreach ($lines_with_objop as $group) { 292 | if (1 == sizeof($group)) { 293 | continue; 294 | } 295 | $i++; 296 | $farthest_objop = 0; 297 | foreach ($group as $idx) { 298 | $farthest_objop = max($farthest_objop, strpos($lines[$idx], self::ALIGNABLE_EQUAL)); 299 | } 300 | foreach ($group as $idx) { 301 | $line = $lines[$idx]; 302 | $current_objop = strpos($line, self::ALIGNABLE_EQUAL); 303 | $delta = abs($farthest_objop-$current_objop); 304 | if ($delta > 0) { 305 | $line = str_replace(self::ALIGNABLE_EQUAL, str_repeat(' ', $delta).self::ALIGNABLE_EQUAL, $line); 306 | $lines[$idx] = $line; 307 | } 308 | } 309 | } 310 | 311 | $this->code = str_replace(self::ALIGNABLE_EQUAL, '', implode($this->new_line, $lines)); 312 | 313 | return $this->code; 314 | } 315 | } 316 | 317 | final class CodeFormatter { 318 | private $passes = []; 319 | 320 | public function addPass(FormatterPass $pass) { 321 | $this->passes[] = $pass; 322 | } 323 | 324 | public function formatCode($source = '') { 325 | foreach ($this->passes as $pass) { 326 | $source = $pass->format($source); 327 | } 328 | return $source; 329 | } 330 | } 331 | 332 | final class EliminateDuplicatedEmptyLines extends FormatterPass { 333 | const ALIGNABLE_EQUAL = "\x2 EQUAL%d \x3"; 334 | public function format($source) { 335 | $this->tkns = token_get_all($source); 336 | $this->code = ''; 337 | $paren_count = 0; 338 | $bracket_count = 0; 339 | while (list($index, $token) = each($this->tkns)) { 340 | list($id, $text) = $this->get_token($token); 341 | $this->ptr = $index; 342 | switch ($id) { 343 | case T_WHITESPACE: 344 | $text = str_replace($this->new_line, self::ALIGNABLE_EQUAL.$this->new_line, $text); 345 | $this->append_code($text, false); 346 | break; 347 | default: 348 | $this->append_code($text, false); 349 | break; 350 | } 351 | } 352 | 353 | $lines = explode($this->new_line, $this->code); 354 | $lines_with_objop = []; 355 | $block_count = 0; 356 | 357 | foreach ($lines as $idx => $line) { 358 | if (trim($line) == self::ALIGNABLE_EQUAL) { 359 | //if (substr_count($line, self::ALIGNABLE_EQUAL) > 0) { 360 | $lines_with_objop[$block_count][] = $idx; 361 | } else { 362 | $block_count++; 363 | } 364 | } 365 | 366 | $i = 0; 367 | foreach ($lines_with_objop as $group) { 368 | if (1 == sizeof($group)) { 369 | continue; 370 | } 371 | array_pop($group); 372 | foreach ($group as $line_number) { 373 | unset($lines[$line_number]); 374 | } 375 | } 376 | 377 | $this->code = str_replace(self::ALIGNABLE_EQUAL, '', implode($this->new_line, $lines)); 378 | 379 | $tkns = token_get_all($this->code); 380 | list($id, $text) = $this->get_token(array_pop($tkns)); 381 | if (T_WHITESPACE == $id && '' == trim($text)) { 382 | $this->code = rtrim($this->code).$this->new_line; 383 | } 384 | 385 | return $this->code; 386 | } 387 | } 388 | 389 | final class ExtraCommaInArray extends FormatterPass { 390 | public function format($source) { 391 | $this->tkns = token_get_all($source); 392 | $this->code = ''; 393 | 394 | $context_stack = []; 395 | while (list($index, $token) = each($this->tkns)) { 396 | list($id, $text) = $this->get_token($token); 397 | $this->ptr = $index; 398 | switch ($id) { 399 | case T_STRING: 400 | if ($this->is_token(ST_PARENTHESES_OPEN)) { 401 | array_unshift($context_stack, T_STRING); 402 | } 403 | $this->append_code($text, false); 404 | break; 405 | case T_ARRAY: 406 | if ($this->is_token(ST_PARENTHESES_OPEN)) { 407 | array_unshift($context_stack, T_ARRAY); 408 | } 409 | $this->append_code($text, false); 410 | break; 411 | case ST_PARENTHESES_OPEN: 412 | if (isset($context_stack[0]) && $this->is_token(ST_PARENTHESES_CLOSE)) { 413 | array_shift($context_stack); 414 | } 415 | $this->append_code($text, false); 416 | break; 417 | case ST_PARENTHESES_CLOSE: 418 | if (isset($context_stack[0])) { 419 | array_shift($context_stack); 420 | } 421 | $this->append_code($text, false); 422 | break; 423 | default: 424 | if (isset($context_stack[0]) && T_ARRAY == $context_stack[0] && $this->is_token(ST_PARENTHESES_CLOSE)) { 425 | array_shift($context_stack); 426 | if (ST_COMMA == $id || T_COMMENT == $id || T_DOC_COMMENT == $id || !$this->has_ln_after()) { 427 | $this->append_code($text, false); 428 | } else { 429 | $this->append_code($text.',', false); 430 | } 431 | break; 432 | } else { 433 | $this->append_code($text, false); 434 | } 435 | break; 436 | } 437 | } 438 | return $this->code; 439 | } 440 | } 441 | 442 | abstract class FormatterPass { 443 | protected $indent_size = 1; 444 | protected $indent_char = "\t"; 445 | protected $block_size = 1; 446 | protected $new_line = "\n"; 447 | protected $indent = 0; 448 | protected $for_idx = 0; 449 | protected $code = ''; 450 | protected $ptr = 0; 451 | protected $tkns = 0; 452 | abstract public function format($source); 453 | protected function get_token($token) { 454 | if (is_string($token)) { 455 | return array($token, $token); 456 | } else { 457 | return $token; 458 | } 459 | } 460 | protected function append_code($code = "", $trim = true) { 461 | if ($trim) { 462 | $this->code = rtrim($this->code).$code; 463 | } else { 464 | $this->code .= $code; 465 | } 466 | } 467 | protected function get_crlf_indent($in_for = false, $increment = 0) { 468 | if ($in_for) { 469 | $this->for_idx++; 470 | if ($this->for_idx > 2) { 471 | $this->for_idx = 0; 472 | } 473 | } 474 | if ($this->for_idx == 0 || !$in_for) { 475 | return $this->get_crlf().$this->get_indent($increment); 476 | } else { 477 | return $this->get_space(false); 478 | } 479 | } 480 | protected function get_crlf($true = true) { 481 | return $true?$this->new_line:""; 482 | } 483 | protected function get_space($true = true) { 484 | return $true?" ":""; 485 | } 486 | protected function get_indent($increment = 0) { 487 | return str_repeat($this->indent_char, ($this->indent+$increment)*$this->indent_size); 488 | } 489 | protected function set_indent($increment) { 490 | $this->indent += $increment; 491 | if ($this->indent < 0) { 492 | $this->indent = 0; 493 | } 494 | } 495 | protected function inspect_token($delta = 1) { 496 | if (!isset($this->tkns[$this->ptr+$delta])) { 497 | return [null, null]; 498 | } 499 | return $this->get_token($this->tkns[$this->ptr+$delta]); 500 | } 501 | protected function is_token($token, $prev = false, $i = 99999, $idx = false) { 502 | if ($i == 99999) { 503 | $i = $this->ptr; 504 | } 505 | if ($prev) { 506 | while (--$i >= 0 && is_array($this->tkns[$i]) && $this->tkns[$i][0] == T_WHITESPACE); 507 | } else { 508 | while (++$i < sizeof($this->tkns)-1 && is_array($this->tkns[$i]) && $this->tkns[$i][0] == T_WHITESPACE); 509 | } 510 | if (isset($this->tkns[$i]) && is_string($this->tkns[$i]) && $this->tkns[$i] == $token) { 511 | return $idx?$i:true; 512 | } elseif (is_array($token) && isset($this->tkns[$i]) && is_array($this->tkns[$i])) { 513 | if (in_array($this->tkns[$i][0], $token)) { 514 | return $idx?$i:true; 515 | } elseif ($prev && $this->tkns[$i][0] == T_OPEN_TAG) { 516 | return $idx?$i:true; 517 | } 518 | } 519 | return false; 520 | } 521 | protected function prev_token() { 522 | $i = $this->ptr; 523 | while (--$i >= 0 && is_array($this->tkns[$i]) && $this->tkns[$i][0] == T_WHITESPACE); 524 | return $this->tkns[$i]; 525 | } 526 | protected function has_ln_after() { 527 | $id = null; 528 | $text = null; 529 | list($id, $text) = $this->inspect_token(); 530 | return T_WHITESPACE == $id && substr_count($text, PHP_EOL) > 0; 531 | } 532 | protected function has_ln_before() { 533 | $id = null; 534 | $text = null; 535 | list($id, $text) = $this->inspect_token(-1); 536 | return T_WHITESPACE == $id && substr_count($text, PHP_EOL) > 0; 537 | } 538 | protected function has_ln_prev_token() { 539 | list($id, $text) = $this->get_token($this->prev_token()); 540 | return substr_count($text, PHP_EOL) > 0; 541 | } 542 | protected function substr_count_trailing($haystack, $needle) { 543 | $cnt = 0; 544 | $i = strlen($haystack)-1; 545 | for ($i = $i; $i >= 0; $i--) { 546 | $char = substr($haystack, $i, 1); 547 | if ($needle == $char) { 548 | $cnt++; 549 | } elseif (' ' != $char && "\t" != $char) { 550 | break; 551 | } 552 | } 553 | return $cnt; 554 | } 555 | } 556 | 557 | final class MergeCurlyCloseAndDoWhile extends FormatterPass { 558 | public function format($source) { 559 | $this->tkns = token_get_all($source); 560 | $this->code = ''; 561 | $in_do_while_context = 0; 562 | while (list($index, $token) = each($this->tkns)) { 563 | list($id, $text) = $this->get_token($token); 564 | $this->ptr = $index; 565 | switch ($id) { 566 | case T_DO: 567 | $in_do_while_context++; 568 | $this->append_code($text, false); 569 | break; 570 | case T_WHILE: 571 | if ($in_do_while_context > 0 && $this->is_token(ST_CURLY_CLOSE, true)) { 572 | $in_do_while_context--; 573 | $this->append_code($text); 574 | break; 575 | } 576 | default: 577 | $this->append_code($text, false); 578 | break; 579 | } 580 | } 581 | return $this->code; 582 | } 583 | } 584 | 585 | final class MergeDoubleArrowAndArray extends FormatterPass { 586 | public function format($source) { 587 | $this->tkns = token_get_all($source); 588 | $this->code = ''; 589 | $in_do_while_context = 0; 590 | while (list($index, $token) = each($this->tkns)) { 591 | list($id, $text) = $this->get_token($token); 592 | $this->ptr = $index; 593 | switch ($id) { 594 | case T_ARRAY: 595 | if ($this->is_token(array(T_DOUBLE_ARROW), true)) { 596 | $in_do_while_context--; 597 | $this->append_code($text); 598 | break; 599 | } 600 | default: 601 | $this->append_code($text, false); 602 | break; 603 | } 604 | } 605 | return $this->code; 606 | } 607 | } 608 | 609 | final class MergeParenCloseWithCurlyOpen extends FormatterPass { 610 | public function format($source) { 611 | $this->tkns = token_get_all($source); 612 | $this->code = ''; 613 | while (list($index, $token) = each($this->tkns)) { 614 | list($id, $text) = $this->get_token($token); 615 | $this->ptr = $index; 616 | switch ($id) { 617 | case ST_CURLY_OPEN: 618 | if ($this->is_token(ST_PARENTHESES_CLOSE, true)) { 619 | $this->append_code($text, true); 620 | } elseif ($this->is_token(array(T_ELSE, T_STRING), true)) { 621 | $this->append_code($text, true); 622 | } else { 623 | $this->append_code($text, false); 624 | } 625 | break; 626 | case T_ELSE: 627 | case T_ELSEIF: 628 | if ($this->is_token(ST_CURLY_CLOSE, true)) { 629 | $this->append_code($text, true); 630 | } else { 631 | $this->append_code($text, false); 632 | } 633 | break; 634 | default: 635 | $this->append_code($text, false); 636 | break; 637 | } 638 | } 639 | return $this->code; 640 | } 641 | } 642 | 643 | final class NormalizeLnAndLtrimLines extends FormatterPass { 644 | public function format($source) { 645 | $this->tkns = token_get_all($source); 646 | $this->code = ''; 647 | while (list($index, $token) = each($this->tkns)) { 648 | list($id, $text) = $this->get_token($token); 649 | $this->ptr = $index; 650 | $this->ptr = $index; 651 | switch ($id) { 652 | case T_COMMENT: 653 | case T_DOC_COMMENT: 654 | if (substr_count($text, "\r\n")) { 655 | $text = str_replace("\r\n", $this->new_line, $text); 656 | } 657 | if (substr_count($text, "\n\r")) { 658 | $text = str_replace("\n\r", $this->new_line, $text); 659 | } 660 | if (substr_count($text, "\r")) { 661 | $text = str_replace("\r", $this->new_line, $text); 662 | } 663 | if (substr_count($text, "\n")) { 664 | $text = str_replace("\n", $this->new_line, $text); 665 | } 666 | $lines = explode($this->new_line, $text); 667 | $lines = array_map(function ($v) { 668 | $v = ltrim($v); 669 | if ('*' == substr($v, 0, 1)) { 670 | $v = ' '.$v; 671 | } 672 | return $v; 673 | }, $lines); 674 | $this->append_code(implode($this->new_line, $lines), false); 675 | break; 676 | case T_CONSTANT_ENCAPSED_STRING: 677 | $this->append_code($text, false); 678 | break; 679 | default: 680 | if (substr_count($text, "\r\n")) { 681 | $text = str_replace("\r\n", $this->new_line, $text); 682 | } 683 | if (substr_count($text, "\n\r")) { 684 | $text = str_replace("\n\r", $this->new_line, $text); 685 | } 686 | if (substr_count($text, "\r")) { 687 | $text = str_replace("\r", $this->new_line, $text); 688 | } 689 | if (substr_count($text, "\n")) { 690 | $text = str_replace("\n", $this->new_line, $text); 691 | } 692 | 693 | if ($this->substr_count_trailing($text, $this->new_line) > 0) { 694 | $text = trim($text).str_repeat($this->new_line, $this->substr_count_trailing($text, $this->new_line)); 695 | } elseif (0 == $this->substr_count_trailing($text, $this->new_line) && T_WHITESPACE == $id) { 696 | $text = $this->get_space().ltrim($text).str_repeat($this->new_line, $this->substr_count_trailing($text, $this->new_line)); 697 | } 698 | $this->append_code($text, false); 699 | break; 700 | } 701 | } 702 | 703 | return $this->code; 704 | } 705 | } 706 | 707 | final class OrderUseClauses extends FormatterPass { 708 | public function format($source = '') { 709 | $use_stack = []; 710 | $tokens = token_get_all($source); 711 | $new_tokens = []; 712 | $next_tokens = []; 713 | while (list(, $pop_token) = each($tokens)) { 714 | $next_tokens[] = $pop_token; 715 | while (($token = array_shift($next_tokens))) { 716 | list($id, $text) = $this->get_token($token); 717 | if (T_USE == $id) { 718 | $use_item = $text; 719 | while (list(, $token) = each($tokens)) { 720 | list($id, $text) = $this->get_token($token); 721 | if (ST_SEMI_COLON == $id) { 722 | $use_item .= $text; 723 | break; 724 | } elseif (ST_COMMA == $id) { 725 | $use_item .= ST_SEMI_COLON; 726 | $next_tokens[] = [T_USE, 'use', ]; 727 | break; 728 | } else { 729 | $use_item .= $text; 730 | } 731 | } 732 | $use_stack[] = $use_item; 733 | $token = new SurrogateToken(); 734 | } 735 | if (T_CLASS == $id || T_FUNCTION == $id) { 736 | if (sizeof($use_stack) > 0) { 737 | $new_tokens[] = $this->new_line; 738 | } 739 | $new_tokens[] = $token; 740 | break 2; 741 | } 742 | $new_tokens[] = $token; 743 | } 744 | } 745 | 746 | natcasesort($use_stack); 747 | $alias_list = []; 748 | $alias_count = []; 749 | foreach ($use_stack as $use) { 750 | if (false !== stripos($use, ' as ')) { 751 | $alias = substr(strstr($use, ' as '), strlen(' as '), -1); 752 | } else { 753 | $alias = basename(str_replace('\\', '/', trim(substr($use, strlen('use'), -1)))); 754 | } 755 | $alias = strtolower($alias); 756 | $alias_list[$alias] = strtolower($use); 757 | $alias_count[$alias] = 0; 758 | } 759 | $return = ''; 760 | foreach ($new_tokens as $token) { 761 | if ($token instanceof SurrogateToken) { 762 | $return .= array_shift($use_stack); 763 | } else { 764 | list($id, $text) = $this->get_token($token); 765 | $lower_text = strtolower($text); 766 | if (T_STRING == $id && isset($alias_list[$lower_text])) { 767 | $alias_count[$lower_text]++; 768 | } 769 | $return .= $text; 770 | } 771 | } 772 | 773 | while (list(, $token) = each($tokens)) { 774 | list($id, $text) = $this->get_token($token); 775 | $lower_text = strtolower($text); 776 | if (T_STRING == $id && isset($alias_list[$lower_text])) { 777 | $alias_count[$lower_text]++; 778 | } 779 | $return .= $text; 780 | } 781 | $unused_import = array_keys( 782 | array_filter( 783 | $alias_count, function ($v) { 784 | return 0 == $v; 785 | } 786 | ) 787 | ); 788 | foreach ($unused_import as $v) { 789 | $return = str_ireplace($alias_list[$v], null, $return); 790 | } 791 | 792 | return $return; 793 | } 794 | } 795 | 796 | final class Reindent extends FormatterPass { 797 | public function format($source) { 798 | $this->tkns = token_get_all($source); 799 | $this->code = ''; 800 | while (list($index, $token) = each($this->tkns)) { 801 | list($id, $text) = $this->get_token($token); 802 | $this->ptr = $index; 803 | switch ($id) { 804 | case T_ENCAPSED_AND_WHITESPACE: 805 | $tmp = str_replace(' ', '', $text); 806 | if ('=<<<' == substr($tmp, 0, 4)) { 807 | $initial = strpos($text, $this->new_line); 808 | $heredoc_tag = trim(substr($text, strpos($text, '<<<')+3, strpos($text, $this->new_line)-(strpos($text, '<<<')+3))); 809 | 810 | $this->append_code(substr($text, 0, $initial), false); 811 | $text = rtrim(substr($text, $initial)); 812 | $text = substr($text, 0, strlen($text)-1).$this->new_line.ST_SEMI_COLON.$this->new_line; 813 | } 814 | $this->append_code($text); 815 | break; 816 | case T_START_HEREDOC: 817 | $this->append_code($text, false); 818 | $heredoc_tag = trim(str_replace('<<<', '', $text)); 819 | while (list($index, $token) = each($this->tkns)) { 820 | list($id, $text) = $this->get_token($token); 821 | $this->ptr = $index; 822 | if (ST_SEMI_COLON == substr(rtrim($text), -1)) { 823 | $this->append_code( 824 | substr( 825 | rtrim($text), 826 | 0, 827 | strlen(rtrim($text))-1 828 | ).$this->new_line.ST_SEMI_COLON.$this->new_line, 829 | false 830 | ); 831 | break; 832 | } else { 833 | $this->append_code($text, false); 834 | } 835 | } 836 | break; 837 | default: 838 | $this->append_code($text, false); 839 | break; 840 | } 841 | } 842 | 843 | $this->tkns = token_get_all($this->code); 844 | $this->code = ''; 845 | while (list($index, $token) = each($this->tkns)) { 846 | list($id, $text) = $this->get_token($token); 847 | $this->ptr = $index; 848 | switch ($id) { 849 | case T_START_HEREDOC: 850 | $this->append_code(rtrim($text).$this->get_crlf(), false); 851 | break; 852 | case T_CONSTANT_ENCAPSED_STRING: 853 | case T_ENCAPSED_AND_WHITESPACE: 854 | case T_STRING_VARNAME: 855 | case T_NUM_STRING: 856 | $this->append_code($text, false); 857 | break; 858 | case T_CURLY_OPEN: 859 | case ST_CURLY_OPEN: 860 | case ST_PARENTHESES_OPEN: 861 | case ST_BRACKET_OPEN: 862 | $this->set_indent(+1); 863 | $this->append_code($text, false); 864 | break; 865 | case ST_CURLY_CLOSE: 866 | case ST_PARENTHESES_CLOSE: 867 | case ST_BRACKET_CLOSE: 868 | $this->set_indent(-1); 869 | $this->append_code($text, false); 870 | break; 871 | default: 872 | if (substr_count($text, $this->new_line) > 0 && !$this->is_token(ST_CURLY_CLOSE) && !$this->is_token(ST_PARENTHESES_CLOSE) && !$this->is_token(ST_BRACKET_CLOSE)) { 873 | $text = str_replace($this->new_line, $this->new_line.$this->get_indent(), $text); 874 | } elseif (substr_count($text, $this->new_line) > 0 && ($this->is_token(ST_CURLY_CLOSE) || $this->is_token(ST_PARENTHESES_CLOSE) || $this->is_token(ST_BRACKET_CLOSE))) { 875 | $this->set_indent(-1); 876 | $text = str_replace($this->new_line, $this->new_line.$this->get_indent(), $text); 877 | $this->set_indent(+1); 878 | } 879 | $this->append_code($text, false); 880 | break; 881 | } 882 | } 883 | return $this->code; 884 | } 885 | } 886 | 887 | final class ReindentColonBlocks extends FormatterPass { 888 | public function format($source) { 889 | $this->tkns = token_get_all($source); 890 | $this->code = ''; 891 | 892 | $switch_level = 0; 893 | $switch_curly_count = []; 894 | $switch_curly_count[$switch_level] = 0; 895 | while (list($index, $token) = each($this->tkns)) { 896 | list($id, $text) = $this->get_token($token); 897 | $this->ptr = $index; 898 | switch ($id) { 899 | case T_SWITCH: 900 | $switch_level++; 901 | $switch_curly_count[$switch_level] = 0; 902 | $this->append_code($text, false); 903 | break; 904 | case ST_CURLY_OPEN: 905 | $switch_curly_count[$switch_level]++; 906 | $this->append_code($text, false); 907 | break; 908 | case ST_CURLY_CLOSE: 909 | $switch_curly_count[$switch_level]--; 910 | if (0 == $switch_curly_count[$switch_level] && $switch_level > 0) { 911 | $switch_level--; 912 | } 913 | $this->append_code($this->get_indent($switch_level).$text, false); 914 | break; 915 | case T_DEFAULT: 916 | case T_CASE: 917 | $this->append_code($text, false); 918 | break; 919 | default: 920 | if (substr_count($text, $this->new_line) > 0 && !$this->is_token(array(T_CASE, T_DEFAULT)) && !$this->is_token(ST_CURLY_CLOSE)) { 921 | $this->append_code($text.$this->get_indent($switch_level), false); 922 | } elseif (substr_count($text, $this->new_line) > 0 && $this->is_token(array(T_CASE, T_DEFAULT))) { 923 | $this->append_code($text, false); 924 | } else { 925 | $this->append_code($text, false); 926 | } 927 | break; 928 | } 929 | } 930 | return $this->code; 931 | } 932 | } 933 | 934 | final class ReindentObjOps extends FormatterPass { 935 | const ALIGNABLE_OBJOP = "\x2 OBJOP%d \x3"; 936 | public function format($source) { 937 | $this->tkns = token_get_all($source); 938 | $this->code = ''; 939 | $in_objop_context = 0;// 1 - indent, 2 - don't indent, so future auto-align takes place 940 | $alignable_objop_counter = 0; 941 | $printed_placeholder = false; 942 | $paren_count = 0; 943 | $bracket_count = 0; 944 | while (list($index, $token) = each($this->tkns)) { 945 | list($id, $text) = $this->get_token($token); 946 | $this->ptr = $index; 947 | switch ($id) { 948 | case ST_PARENTHESES_OPEN: 949 | $paren_count++; 950 | $this->append_code($text, false); 951 | break; 952 | case ST_PARENTHESES_CLOSE: 953 | $paren_count--; 954 | $this->append_code($text, false); 955 | break; 956 | case ST_BRACKET_OPEN: 957 | $bracket_count++; 958 | $this->append_code($text, false); 959 | break; 960 | case ST_BRACKET_CLOSE: 961 | $bracket_count--; 962 | $this->append_code($text, false); 963 | break; 964 | case T_OBJECT_OPERATOR: 965 | if (0 === $in_objop_context && ($this->has_ln_before() || $this->has_ln_prev_token())) { 966 | $in_objop_context = 1; 967 | } elseif (0 === $in_objop_context && !($this->has_ln_before() || $this->has_ln_prev_token())) { 968 | $alignable_objop_counter++; 969 | $in_objop_context = 2; 970 | } elseif ($paren_count > 0) { 971 | $in_objop_context = 0; 972 | } 973 | if (1 == $in_objop_context) { 974 | $this->set_indent(+1); 975 | $this->append_code($this->get_indent().$text, false); 976 | $this->set_indent(-1); 977 | } elseif (2 === $in_objop_context) { 978 | $placeholder = ''; 979 | if (!$printed_placeholder) { 980 | $placeholder = sprintf(self::ALIGNABLE_OBJOP, $alignable_objop_counter); 981 | $printed_placeholder = true; 982 | } 983 | $this->append_code($placeholder.$text, false); 984 | } else { 985 | $this->append_code($text, false); 986 | } 987 | break; 988 | case T_VARIABLE: 989 | if (0 === $paren_count && 0 === $bracket_count && 0 !== $in_objop_context) { 990 | $in_objop_context = 0; 991 | } 992 | $this->append_code($text, false); 993 | break; 994 | case T_DOUBLE_ARROW: 995 | case ST_SEMI_COLON: 996 | if (0 !== $in_objop_context) { 997 | $in_objop_context = 0; 998 | } 999 | $this->append_code($text, false); 1000 | break; 1001 | default: 1002 | $this->append_code($text, false); 1003 | break; 1004 | } 1005 | if (substr_count($text, $this->new_line) > 0) { 1006 | $printed_placeholder = false; 1007 | } 1008 | } 1009 | 1010 | for ($j = $alignable_objop_counter; $j > 0; $j--) { 1011 | $current_align_objop = sprintf(self::ALIGNABLE_OBJOP, $j); 1012 | if (substr_count($this->code, $current_align_objop) <= 1) { 1013 | $this->code = str_replace($current_align_objop, '', $this->code); 1014 | continue; 1015 | } 1016 | 1017 | $lines = explode($this->new_line, $this->code); 1018 | $lines_with_objop = []; 1019 | $block_count = 0; 1020 | 1021 | foreach ($lines as $idx => $line) { 1022 | if (substr_count($line, $current_align_objop) > 0) { 1023 | $lines_with_objop[$block_count][] = $idx; 1024 | } else { 1025 | $block_count++; 1026 | } 1027 | } 1028 | 1029 | $i = 0; 1030 | foreach ($lines_with_objop as $group) { 1031 | if (1 == sizeof($group)) { 1032 | continue; 1033 | } 1034 | $i++; 1035 | $farthest_objop = 0; 1036 | foreach ($group as $idx) { 1037 | $farthest_objop = max($farthest_objop, strpos($lines[$idx], $current_align_objop)); 1038 | } 1039 | foreach ($group as $idx) { 1040 | $line = $lines[$idx]; 1041 | $current_objop = strpos($line, $current_align_objop); 1042 | $delta = abs($farthest_objop-$current_objop); 1043 | if ($delta > 0) { 1044 | $line = str_replace($current_align_objop, str_repeat(' ', $delta).$current_align_objop, $line); 1045 | $lines[$idx] = $line; 1046 | } 1047 | } 1048 | } 1049 | 1050 | $this->code = str_replace($current_align_objop, '', implode($this->new_line, $lines)); 1051 | } 1052 | 1053 | return $this->code; 1054 | } 1055 | } 1056 | 1057 | final class ResizeSpaces extends FormatterPass { 1058 | public function format($source) { 1059 | $new_tokens = []; 1060 | $this->tkns = token_get_all($source); 1061 | $this->code = ''; 1062 | while (list($index, $token) = each($this->tkns)) { 1063 | list($id, $text) = $this->get_token($token); 1064 | $this->ptr = $index; 1065 | switch ($id) { 1066 | case T_WHITESPACE: 1067 | if (0 == substr_count($text, $this->new_line)) { 1068 | break; 1069 | } 1070 | default: 1071 | $new_tokens[] = $token; 1072 | } 1073 | } 1074 | 1075 | $this->tkns = $new_tokens; 1076 | $this->code = ''; 1077 | while (list($index, $token) = each($this->tkns)) { 1078 | list($id, $text) = $this->get_token($token); 1079 | $this->ptr = $index; 1080 | switch ($id) { 1081 | case T_ARRAY: 1082 | if ($this->is_token(array(T_VARIABLE))) { 1083 | $this->append_code($text.$this->get_space(), false); 1084 | break; 1085 | } elseif ($this->is_token(ST_PARENTHESES_OPEN)) { 1086 | $this->append_code($text, false); 1087 | break; 1088 | } 1089 | case T_STRING: 1090 | if ($this->is_token(array(T_VARIABLE, T_DOUBLE_ARROW))) { 1091 | $this->append_code($text.$this->get_space(), false); 1092 | break; 1093 | //} elseif ($this->is_token(array(T_DOUBLE_COLON)) || $this->is_token(ST_PARENTHESES_OPEN, true) || $this->is_token(ST_CONCAT) || $this->is_token(ST_COMMA)) { 1094 | } else { 1095 | $this->append_code($text, false); 1096 | break; 1097 | } 1098 | case ST_CURLY_OPEN: 1099 | if ($this->is_token(array(T_STRING, T_DO), true) || $this->is_token(ST_PARENTHESES_CLOSE, true)) { 1100 | $this->append_code($this->get_space().$text, false); 1101 | break; 1102 | } elseif ($this->is_token(ST_CURLY_CLOSE)) { 1103 | $this->append_code($text, false); 1104 | break; 1105 | } 1106 | case ST_SEMI_COLON: 1107 | if ($this->is_token(array(T_VARIABLE, T_INC))) { 1108 | $this->append_code($text.$this->get_space(), false); 1109 | break; 1110 | } 1111 | case ST_PARENTHESES_OPEN: 1112 | case ST_PARENTHESES_CLOSE: 1113 | $this->append_code($text, false); 1114 | break; 1115 | case T_USE: 1116 | if ($this->is_token(ST_PARENTHESES_CLOSE, true)) { 1117 | $this->append_code($this->get_space().$text.$this->get_space(), false); 1118 | } elseif ($this->is_token(ST_SEMI_COLON)) { 1119 | $this->append_code($text, false); 1120 | } else { 1121 | $this->append_code($text.$this->get_space(), false); 1122 | } 1123 | break; 1124 | case T_RETURN: 1125 | case T_YIELD: 1126 | case T_ECHO: 1127 | case T_NAMESPACE: 1128 | case T_VAR: 1129 | case T_NEW: 1130 | case T_CONST: 1131 | case T_FINAL: 1132 | case T_CASE: 1133 | case T_BREAK: 1134 | if ($this->is_token(ST_SEMI_COLON)) { 1135 | $this->append_code($text, false); 1136 | } else { 1137 | $this->append_code($text.$this->get_space(), false); 1138 | } 1139 | break; 1140 | case T_WHILE: 1141 | if ($this->is_token(ST_SEMI_COLON)) { 1142 | $this->append_code($text.$this->get_space(), false); 1143 | break; 1144 | } elseif ($this->is_token(ST_CURLY_CLOSE, true) && !$this->has_ln_before()) { 1145 | $this->append_code($this->get_space().$text.$this->get_space(), false); 1146 | break; 1147 | } 1148 | case T_DOUBLE_ARROW: 1149 | if ($this->is_token(array(T_CONSTANT_ENCAPSED_STRING, T_STRING, T_VARIABLE, T_LNUMBER, T_DNUMBER), true)) { 1150 | $this->append_code($this->get_space().$text.$this->get_space()); 1151 | break; 1152 | } 1153 | case T_PUBLIC: 1154 | case T_PRIVATE: 1155 | case T_PROTECTED: 1156 | case T_STATIC: 1157 | case T_CLASS: 1158 | case T_TRAIT: 1159 | case T_INTERFACE: 1160 | case T_THROW: 1161 | case T_GLOBAL: 1162 | case T_ABSTRACT: 1163 | case T_INCLUDE: 1164 | case T_REQUIRE: 1165 | case T_INCLUDE_ONCE: 1166 | case T_REQUIRE_ONCE: 1167 | case T_FUNCTION: 1168 | case T_IF: 1169 | case T_FOR: 1170 | case T_FOREACH: 1171 | case T_SWITCH: 1172 | case T_TRY: 1173 | case ST_COMMA: 1174 | $this->append_code($text.$this->get_space(), false); 1175 | break; 1176 | case T_EXTENDS: 1177 | case T_IMPLEMENTS: 1178 | case T_INSTANCEOF: 1179 | case T_LOGICAL_AND: 1180 | case T_LOGICAL_OR: 1181 | case T_LOGICAL_XOR: 1182 | case T_AND_EQUAL: 1183 | case T_BOOLEAN_AND: 1184 | case T_BOOLEAN_OR: 1185 | case T_CONCAT_EQUAL: 1186 | case T_DIV_EQUAL: 1187 | case T_IS_EQUAL: 1188 | case T_IS_GREATER_OR_EQUAL: 1189 | case T_IS_IDENTICAL: 1190 | case T_IS_NOT_EQUAL: 1191 | case T_IS_NOT_IDENTICAL: 1192 | case T_IS_SMALLER_OR_EQUAL: 1193 | case T_MINUS_EQUAL: 1194 | case T_MOD_EQUAL: 1195 | case T_MUL_EQUAL: 1196 | case T_OR_EQUAL: 1197 | case T_PLUS_EQUAL: 1198 | case T_SL: 1199 | case T_SL_EQUAL: 1200 | case T_SR: 1201 | case T_SR_EQUAL: 1202 | case T_XOR_EQUAL: 1203 | case ST_IS_GREATER: 1204 | case ST_IS_SMALLER: 1205 | case T_AS: 1206 | case T_ELSE: 1207 | case T_ELSEIF: 1208 | case ST_EQUAL: 1209 | case T_CATCH: 1210 | $this->append_code($this->get_space().$text.$this->get_space(), false); 1211 | break; 1212 | case T_ARRAY_CAST: 1213 | case T_BOOL_CAST: 1214 | case T_DOUBLE_CAST: 1215 | case T_INT_CAST: 1216 | case T_OBJECT_CAST: 1217 | case T_STRING_CAST: 1218 | case T_UNSET_CAST: 1219 | $this->append_code($text.$this->get_space(), false); 1220 | break; 1221 | case ST_CONCAT: 1222 | if ( 1223 | !$this->is_token(ST_PARENTHESES_CLOSE, true) && 1224 | !$this->is_token(ST_BRACKET_CLOSE, true) && 1225 | !$this->is_token(array(T_VARIABLE, T_STRING, T_CONSTANT_ENCAPSED_STRING, T_WHITESPACE), true) 1226 | ) { 1227 | $this->append_code($this->get_space().$text, false); 1228 | } else { 1229 | $this->append_code($text, false); 1230 | } 1231 | break; 1232 | default: 1233 | $this->append_code($text, false); 1234 | break; 1235 | } 1236 | } 1237 | 1238 | return $this->code; 1239 | } 1240 | } 1241 | 1242 | final class RTrim extends FormatterPass { 1243 | 1244 | public function format($source) { 1245 | return implode( 1246 | $this->new_line, 1247 | array_map( 1248 | function ($v) { 1249 | return rtrim($v); 1250 | }, 1251 | explode($this->new_line, $source) 1252 | ) 1253 | ); 1254 | } 1255 | 1256 | } 1257 | 1258 | final class SurrogateToken { 1259 | } 1260 | 1261 | final class TwoCommandsInSameLine extends FormatterPass { 1262 | public function format($source) { 1263 | $lines = explode($this->new_line, $source); 1264 | foreach ($lines as $idx => $line) { 1265 | if (substr_count($line, ';') <= 1) { 1266 | continue; 1267 | } 1268 | $new_line = ''; 1269 | $ignore_stack = 0; 1270 | $double_quote_state = false; 1271 | $single_quote_state = false; 1272 | $len = strlen($line); 1273 | for ($i = 0; $i < $len; $i++) { 1274 | $char = substr($line, $i, 1); 1275 | if (ST_PARENTHESES_OPEN == $char || ST_PARENTHESES_OPEN == $char || ST_CURLY_OPEN == $char || ST_BRACKET_OPEN == $char) { 1276 | $ignore_stack++; 1277 | } 1278 | if (ST_PARENTHESES_CLOSE == $char || ST_CURLY_CLOSE == $char || ST_BRACKET_CLOSE == $char) { 1279 | $ignore_stack--; 1280 | } 1281 | if ('"' == $char) { 1282 | $double_quote_state = !$double_quote_state; 1283 | } 1284 | if ("'" == $char) { 1285 | $single_quote_state = !$single_quote_state; 1286 | } 1287 | $new_line .= $char; 1288 | if (!$single_quote_state && !$double_quote_state && 0 == $ignore_stack && ST_SEMI_COLON == $char && $i+1 < $len) { 1289 | $new_line .= $this->new_line; 1290 | } 1291 | } 1292 | $lines[$idx] = $new_line; 1293 | } 1294 | return implode($this->new_line, $lines); 1295 | } 1296 | } 1297 | 1298 | final class PSR1OpenTags extends FormatterPass { 1299 | public function format($source) { 1300 | $this->tkns = token_get_all($source); 1301 | $this->code = ''; 1302 | while (list($index, $token) = each($this->tkns)) { 1303 | list($id, $text) = $this->get_token($token); 1304 | $this->ptr = $index; 1305 | switch ($id) { 1306 | case T_OPEN_TAG: 1307 | if ('append_code('new_line, false); 1309 | break; 1310 | } 1311 | default: 1312 | $this->append_code($text, false); 1313 | break; 1314 | } 1315 | } 1316 | return $this->code; 1317 | } 1318 | } 1319 | 1320 | final class PSR1BOMMark extends FormatterPass { 1321 | public function format($source) { 1322 | $bom = "\xef\xbb\xbf"; 1323 | if ($bom == substr($source, 0, 3)) { 1324 | return substr($source, 3); 1325 | } 1326 | return $source; 1327 | } 1328 | } 1329 | 1330 | final class PSR1ClassNames extends FormatterPass { 1331 | public function format($source) { 1332 | $this->tkns = token_get_all($source); 1333 | $this->code = ''; 1334 | $found_class = false; 1335 | while (list($index, $token) = each($this->tkns)) { 1336 | list($id, $text) = $this->get_token($token); 1337 | $this->ptr = $index; 1338 | switch ($id) { 1339 | case T_CLASS: 1340 | $found_class = true; 1341 | $this->append_code($text, false); 1342 | break; 1343 | case T_STRING: 1344 | if ($found_class) { 1345 | $count = 0; 1346 | $tmp = ucwords(str_replace(array('-', '_'), ' ', strtolower($text), $count)); 1347 | if ($count > 0) { 1348 | $text = str_replace(' ', '', $tmp); 1349 | } 1350 | $this->append_code($text, false); 1351 | 1352 | $found_class = false; 1353 | break; 1354 | } 1355 | default: 1356 | $this->append_code($text, false); 1357 | break; 1358 | } 1359 | } 1360 | return $this->code; 1361 | } 1362 | } 1363 | 1364 | final class PSR1ClassConstants extends FormatterPass { 1365 | public function format($source) { 1366 | $this->tkns = token_get_all($source); 1367 | $this->code = ''; 1368 | $uc_const = false; 1369 | while (list($index, $token) = each($this->tkns)) { 1370 | list($id, $text) = $this->get_token($token); 1371 | $this->ptr = $index; 1372 | switch ($id) { 1373 | case T_CONST: 1374 | $uc_const = true; 1375 | $this->append_code($text, false); 1376 | break; 1377 | case T_STRING: 1378 | if ($uc_const) { 1379 | $text = strtoupper($text); 1380 | $uc_const = false; 1381 | } 1382 | $this->append_code($text, false); 1383 | break; 1384 | default: 1385 | $this->append_code($text, false); 1386 | break; 1387 | } 1388 | } 1389 | return $this->code; 1390 | } 1391 | } 1392 | final class PSR1MethodNames extends FormatterPass { 1393 | public function format($source) { 1394 | $this->tkns = token_get_all($source); 1395 | $this->code = ''; 1396 | $found_method = false; 1397 | while (list($index, $token) = each($this->tkns)) { 1398 | list($id, $text) = $this->get_token($token); 1399 | $this->ptr = $index; 1400 | switch ($id) { 1401 | case T_FUNCTION: 1402 | $found_method = true; 1403 | $this->append_code($text, false); 1404 | break; 1405 | case T_STRING: 1406 | if ($found_method) { 1407 | $count = 0; 1408 | $tmp = ucwords(str_replace(array('-', '_'), ' ', strtolower($text), $count)); 1409 | if ($count > 0 && '' != trim($tmp) && '_' != substr($text, 0, 1)) { 1410 | $text = lcfirst(str_replace(' ', '', $tmp)); 1411 | } 1412 | $this->append_code($text, false); 1413 | 1414 | $found_method = false; 1415 | break; 1416 | } 1417 | default: 1418 | $this->append_code($text, false); 1419 | break; 1420 | } 1421 | } 1422 | return $this->code; 1423 | } 1424 | } 1425 | 1426 | final class PSR2IndentWithSpace extends FormatterPass { 1427 | private $indent_spaces = ' '; 1428 | 1429 | public function format($source) { 1430 | $this->tkns = token_get_all($source); 1431 | $this->code = ''; 1432 | while (list($index, $token) = each($this->tkns)) { 1433 | list($id, $text) = $this->get_token($token); 1434 | $this->ptr = $index; 1435 | switch ($id) { 1436 | case T_WHITESPACE: 1437 | $this->append_code(str_replace($this->indent_char, $this->indent_spaces, $text), false); 1438 | break; 1439 | default: 1440 | $this->append_code($text, false); 1441 | break; 1442 | } 1443 | } 1444 | return $this->code; 1445 | } 1446 | } 1447 | 1448 | final class PSR2LnAfterNamespace extends FormatterPass { 1449 | public function format($source) { 1450 | $this->tkns = token_get_all($source); 1451 | $this->code = ''; 1452 | 1453 | while (list($index, $token) = each($this->tkns)) { 1454 | list($id, $text) = $this->get_token($token); 1455 | $this->ptr = $index; 1456 | switch ($id) { 1457 | case T_NAMESPACE: 1458 | $this->append_code($text, false); 1459 | while (list($index, $token) = each($this->tkns)) { 1460 | list($id, $text) = $this->get_token($token); 1461 | $this->ptr = $index; 1462 | if (ST_SEMI_COLON == $id) { 1463 | $this->append_code($text, false); 1464 | list(, $text) = $this->inspect_token(); 1465 | if (1 == substr_count($text, $this->new_line)) { 1466 | $this->append_code($this->new_line, false); 1467 | break; 1468 | } 1469 | } else { 1470 | $this->append_code($text, false); 1471 | } 1472 | } 1473 | break; 1474 | default: 1475 | $this->append_code($text, false); 1476 | break; 1477 | } 1478 | } 1479 | return $this->code; 1480 | } 1481 | } 1482 | 1483 | final class PSR2CurlyOpenNextLine extends FormatterPass { 1484 | public function format($source) { 1485 | $this->indent_char = ' '; 1486 | $this->tkns = token_get_all($source); 1487 | $this->code = ''; 1488 | 1489 | while (list($index, $token) = each($this->tkns)) { 1490 | list($id, $text) = $this->get_token($token); 1491 | $this->ptr = $index; 1492 | switch ($id) { 1493 | case T_CLASS: 1494 | $this->append_code($text, false); 1495 | while (list($index, $token) = each($this->tkns)) { 1496 | list($id, $text) = $this->get_token($token); 1497 | $this->ptr = $index; 1498 | if (ST_CURLY_OPEN == $id) { 1499 | $this->append_code($this->get_crlf_indent(), false); 1500 | prev($this->tkns); 1501 | break; 1502 | } else { 1503 | $this->append_code($text, false); 1504 | } 1505 | } 1506 | break; 1507 | case T_FUNCTION: 1508 | if (!$this->is_token(ST_EQUAL, true)) { 1509 | $this->append_code($text, false); 1510 | while (list($index, $token) = each($this->tkns)) { 1511 | list($id, $text) = $this->get_token($token); 1512 | $this->ptr = $index; 1513 | if (ST_CURLY_OPEN == $id) { 1514 | $this->append_code($this->get_crlf_indent(), false); 1515 | prev($this->tkns); 1516 | break; 1517 | } else { 1518 | $this->append_code($text, false); 1519 | } 1520 | } 1521 | break; 1522 | } 1523 | break; 1524 | case ST_CURLY_OPEN: 1525 | $this->append_code($text, false); 1526 | $this->set_indent(+1); 1527 | break; 1528 | case ST_CURLY_CLOSE: 1529 | $this->set_indent(-1); 1530 | $this->append_code($text, false); 1531 | break; 1532 | default: 1533 | $this->append_code($text, false); 1534 | break; 1535 | } 1536 | } 1537 | return $this->code; 1538 | } 1539 | } 1540 | 1541 | final class PSR2ModifierVisibilityStaticOrder extends FormatterPass { 1542 | public function format($source) { 1543 | $this->tkns = token_get_all($source); 1544 | $this->code = ''; 1545 | 1546 | $found = []; 1547 | $visibility = null; 1548 | $final_or_abstract = null; 1549 | $static = null; 1550 | $skip_whitespaces = false; 1551 | while (list($index, $token) = each($this->tkns)) { 1552 | list($id, $text) = $this->get_token($token); 1553 | $this->ptr = $index; 1554 | switch ($id) { 1555 | case T_CLASS: 1556 | $found[] = T_CLASS; 1557 | $this->append_code($text, false); 1558 | break; 1559 | case ST_CURLY_OPEN: 1560 | $found[] = ST_CURLY_OPEN; 1561 | $this->append_code($text, false); 1562 | break; 1563 | case ST_CURLY_CLOSE: 1564 | array_pop($found); 1565 | if (1 == sizeof($found)) { 1566 | array_pop($found); 1567 | } 1568 | $this->append_code($text, false); 1569 | break; 1570 | case T_WHITESPACE: 1571 | if (!$skip_whitespaces) { 1572 | $this->append_code($text, false); 1573 | } 1574 | break; 1575 | case T_PUBLIC: 1576 | case T_PRIVATE: 1577 | case T_PROTECTED: 1578 | if (!$this->is_token(array(T_VARIABLE))) { 1579 | $visibility = $text; 1580 | $skip_whitespaces = true; 1581 | } else { 1582 | $this->append_code($text, false); 1583 | } 1584 | break; 1585 | case T_FINAL: 1586 | case T_ABSTRACT: 1587 | if (!$this->is_token(array(T_CLASS))) { 1588 | $final_or_abstract = $text; 1589 | $skip_whitespaces = true; 1590 | } else { 1591 | $this->append_code($text, false); 1592 | } 1593 | break; 1594 | case T_STATIC: 1595 | if (!$this->is_token(array(T_VARIABLE))) { 1596 | $static = $text; 1597 | $skip_whitespaces = true; 1598 | } else { 1599 | $this->append_code($text, false); 1600 | } 1601 | break; 1602 | case T_VARIABLE: 1603 | if ( 1604 | null !== $visibility || 1605 | null !== $final_or_abstract || 1606 | null !== $static 1607 | ) { 1608 | null !== $visibility && $this->append_code($final_or_abstract.$this->get_space(), false); 1609 | null !== $final_or_abstract && $this->append_code($visibility.$this->get_space(), false); 1610 | null !== $static && $this->append_code($static.$this->get_space(), false); 1611 | $final_or_abstract = null; 1612 | $visibility = null; 1613 | $static = null; 1614 | $skip_whitespaces = false; 1615 | } 1616 | $this->append_code($text, false); 1617 | break; 1618 | case T_FUNCTION: 1619 | if (isset($found[0]) && T_CLASS == $found[0] && null !== $final_or_abstract) { 1620 | $this->append_code($final_or_abstract.$this->get_space(), false); 1621 | } 1622 | if (isset($found[0]) && T_CLASS == $found[0] && null !== $visibility) { 1623 | $this->append_code($visibility.$this->get_space(), false); 1624 | } elseif (isset($found[0]) && T_CLASS == $found[0] && !$this->is_token(ST_EQUAL, true)) { 1625 | $this->append_code('public'.$this->get_space(), false); 1626 | } 1627 | if (isset($found[0]) && T_CLASS == $found[0] && null !== $static) { 1628 | $this->append_code($static.$this->get_space(), false); 1629 | } 1630 | $this->append_code($text, false); 1631 | $final_or_abstract = null; 1632 | $visibility = null; 1633 | $static = null; 1634 | $skip_whitespaces = false; 1635 | break; 1636 | default: 1637 | $this->append_code($text, false); 1638 | break; 1639 | } 1640 | } 1641 | return $this->code; 1642 | } 1643 | } 1644 | 1645 | final class PSR2SingleEmptyLineAndStripClosingTag extends FormatterPass { 1646 | public function format($source) { 1647 | $this->tkns = token_get_all($source); 1648 | $this->code = ''; 1649 | 1650 | $open_tag_count = 0; 1651 | while (list($index, $token) = each($this->tkns)) { 1652 | list($id, ) = $this->get_token($token); 1653 | if (T_OPEN_TAG == $id) { 1654 | $open_tag_count++; 1655 | break; 1656 | } 1657 | } 1658 | 1659 | reset($this->tkns); 1660 | if (1 == $open_tag_count) { 1661 | while (list($index, $token) = each($this->tkns)) { 1662 | list($id, $text) = $this->get_token($token); 1663 | $this->ptr = $index; 1664 | switch ($id) { 1665 | case T_CLOSE_TAG: 1666 | $this->append_code($this->get_crlf(), false); 1667 | break; 1668 | default: 1669 | $this->append_code($text, false); 1670 | break; 1671 | } 1672 | } 1673 | $this->code = rtrim($this->code); 1674 | } else { 1675 | while (list($index, $token) = each($this->tkns)) { 1676 | list($id, $text) = $this->get_token($token); 1677 | $this->ptr = $index; 1678 | $this->append_code($text, false); 1679 | } 1680 | } 1681 | $this->code .= $this->get_crlf().$this->get_crlf(); 1682 | 1683 | return $this->code; 1684 | } 1685 | } 1686 | 1687 | class PsrDecorator { 1688 | public static function decorate(CodeFormatter $fmt) { 1689 | $fmt->addPass(new PSR1OpenTags()); 1690 | $fmt->addPass(new PSR1BOMMark()); 1691 | $fmt->addPass(new PSR1ClassNames()); 1692 | $fmt->addPass(new PSR1ClassConstants()); 1693 | $fmt->addPass(new PSR1MethodNames()); 1694 | 1695 | $fmt->addPass(new PSR2IndentWithSpace()); 1696 | $fmt->addPass(new PSR2LnAfterNamespace()); 1697 | $fmt->addPass(new PSR2CurlyOpenNextLine()); 1698 | $fmt->addPass(new PSR2ModifierVisibilityStaticOrder()); 1699 | $fmt->addPass(new PSR2SingleEmptyLineAndStripClosingTag()); 1700 | } 1701 | } 1702 | if (!isset($testEnv)) { 1703 | $fmt = new CodeFormatter(); 1704 | $fmt->addPass(new TwoCommandsInSameLine()); 1705 | $fmt->addPass(new AddMissingCurlyBraces()); 1706 | $fmt->addPass(new NormalizeLnAndLtrimLines()); 1707 | $fmt->addPass(new MergeParenCloseWithCurlyOpen()); 1708 | $fmt->addPass(new MergeCurlyCloseAndDoWhile()); 1709 | $fmt->addPass(new MergeDoubleArrowAndArray()); 1710 | $fmt->addPass(new ExtraCommaInArray()); 1711 | $fmt->addPass(new ResizeSpaces()); 1712 | $fmt->addPass(new Reindent()); 1713 | $fmt->addPass(new ReindentColonBlocks()); 1714 | $fmt->addPass(new ReindentObjOps()); 1715 | $fmt->addPass(new OrderUseClauses()); 1716 | $fmt->addPass(new EliminateDuplicatedEmptyLines()); 1717 | $fmt->addPass(new AlignEquals()); 1718 | $fmt->addPass(new AlignDoubleArrow()); 1719 | 1720 | $opts = getopt('', ['psr', 'indent_with_space']); 1721 | if (isset($opts['psr'])) { 1722 | PsrDecorator::decorate($fmt); 1723 | $argv = array_values( 1724 | array_filter($argv, 1725 | function ($v) { 1726 | return $v != '--psr'; 1727 | } 1728 | ) 1729 | ); 1730 | } 1731 | if (isset($opts['indent_with_space'])) { 1732 | $fmt->addPass(new PSR2IndentWithSpace()); 1733 | $argv = array_values( 1734 | array_filter($argv, 1735 | function ($v) { 1736 | return $v != '--indent_with_space'; 1737 | } 1738 | ) 1739 | ); 1740 | } 1741 | $fmt->addPass(new RTrim()); 1742 | 1743 | if (!isset($argv[1])) { 1744 | exit(); 1745 | } 1746 | echo $fmt->formatCode(file_get_contents($argv[1])); 1747 | } -------------------------------------------------------------------------------- /install.txt: -------------------------------------------------------------------------------- 1 | # [php.fmt](https://github.com/dericofilho/php.tools) support for Sublime Text 3 2 | 3 | 4 | Thanks for installing phpfmt for Sublime Text. 5 | 6 | Please, before posting an issue that this plugin is not formatting your code, answer the following questions: 7 | 8 | - Have you installed PHP in the computer which is running Sublime Text? 9 | 10 | - Is PHP configured in the default $PATH/%PATH% variable? 11 | 12 | - If PHP is not configured in $PATH/%PATH%, have you added the option "php_bin" in the configuration file with full path of PHP binary? 13 | 14 | 15 | If you have answered more than one "no", then double check your environment. -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "install.txt" 3 | } -------------------------------------------------------------------------------- /phpfmt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import shutil 4 | import sublime 5 | import sublime_plugin 6 | import subprocess 7 | from os.path import dirname, realpath 8 | 9 | class phpfmt(sublime_plugin.EventListener): 10 | def __init__(self): 11 | self.debug = True 12 | 13 | 14 | def on_post_save(self, view): 15 | if int(sublime.version()) < 3000: 16 | self.on_post_save_async(view) 17 | 18 | def on_post_save_async(self, view): 19 | s = sublime.load_settings('phpfmt.sublime-settings') 20 | self.debug = s.get("debug", False) 21 | psr = s.get("psr1_and_2", False) 22 | indent_with_space = s.get("indent_with_space", False) 23 | php_bin = s.get("php_bin", "php") 24 | formatter_path = os.path.join(dirname(realpath(sublime.packages_path())), "Packages", "phpfmt", "codeFormatter.php") 25 | 26 | uri = view.file_name() 27 | dirnm, sfn = os.path.split(uri) 28 | ext = os.path.splitext(uri)[1][1:] 29 | 30 | if self.debug: 31 | print("phpfmt:", uri) 32 | 33 | if "php" != ext: 34 | return False 35 | 36 | cmd = [php_bin] 37 | 38 | if self.debug: 39 | cmd.append("-ddisplay_errors=0") 40 | 41 | cmd.append(formatter_path) 42 | 43 | if psr: 44 | cmd.append("--psr") 45 | 46 | if indent_with_space: 47 | cmd.append("--indent_with_space") 48 | 49 | cmd.append(uri) 50 | 51 | uri_tmp = uri + "~" 52 | 53 | if self.debug: 54 | print("cmd: ", cmd) 55 | 56 | if os.name == 'nt': 57 | startupinfo = subprocess.STARTUPINFO() 58 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 59 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dirnm, shell=False, startupinfo=startupinfo) 60 | else: 61 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dirnm, shell=False) 62 | res, err = p.communicate() 63 | if err: 64 | if self.debug: 65 | print("err: ", err) 66 | else: 67 | if int(sublime.version()) < 3000: 68 | with open(uri_tmp, 'w+') as f: 69 | f.write(res) 70 | else: 71 | with open(uri_tmp, 'bw+') as f: 72 | f.write(res) 73 | if self.debug: 74 | print("Stored:", len(res), "bytes") 75 | shutil.move(uri_tmp, uri) 76 | sublime.set_timeout(self.revert_active_window, 50) 77 | 78 | def revert_active_window(self): 79 | sublime.active_window().active_view().run_command("revert") 80 | -------------------------------------------------------------------------------- /phpfmt.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // "psr1_and_2":false, 3 | // "php_bin":"/usr/local/bin/php", 4 | // "indent_with_space":false 5 | } 6 | --------------------------------------------------------------------------------