├── .gitignore ├── .php_cs ├── .project ├── .settings ├── com.dubture.composer.core.prefs ├── org.eclipse.php.composer.core.prefs ├── org.eclipse.php.core.prefs ├── org.eclipse.wst.common.project.facet.core.xml └── org.eclipse.wst.validation.prefs ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── phormat ├── composer.json ├── phpunit.xml.dist ├── profiles └── default.yml ├── sami.php ├── src ├── Formatter.php ├── FormatterCli.php ├── collections │ └── UnitCollection.php ├── commands │ └── FormatterCommand.php ├── entities │ ├── Block.php │ ├── Group.php │ └── Unit.php ├── events │ ├── BlockEvent.php │ ├── GroupEvent.php │ └── TokenEvent.php ├── formatters │ ├── BaseFormatter.php │ ├── BlanksFormatter.php │ ├── CommentsFormatter.php │ ├── DefaultFormatter.php │ ├── DelegateFormatter.php │ ├── IndentationFormatter.php │ ├── NewlineFormatter.php │ ├── SpecializedFormatter.php │ └── WhitespaceFormatter.php ├── parser │ ├── Analyzer.php │ ├── Context.php │ ├── Lexer.php │ ├── Parser.php │ ├── TokenMatcher.php │ └── TokenTracker.php └── utils │ ├── Sanitizer.php │ └── Writer.php └── tests ├── fixtures └── samples │ ├── default │ ├── abstract-class.php │ ├── blanks.php │ ├── class-phpdoc.php │ ├── class.php │ └── sample1.php │ └── raw │ ├── abstract-class.php │ ├── blanks.php │ ├── class-phpdoc.php │ ├── class.php │ └── sample1.php ├── formatter └── SamplesTest.php ├── parser ├── AnalyzerTest.php └── ContextTest.php ├── token └── TokenizerTest.php └── utils └── SamplesTrait.php /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | composer.lock 3 | vendor/ 4 | coverage/ 5 | cache/ 6 | api/ 7 | .php_cs.cache -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | getFinder() 5 | ->exclude(['fixtures']) 6 | ->in(__DIR__ . '/src') 7 | ->in(__DIR__ . '/tests'); 8 | 9 | $cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; 10 | 11 | $config->setCacheFile($cacheDir . '/.php_cs.cache'); 12 | 13 | return $config; -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | php-code-formatter 4 | 5 | 6 | 7 | 8 | 9 | com.dubture.composer.core.builder.buildPathManagementBuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.wst.common.project.facet.core.builder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.dltk.core.scriptbuilder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.php.core.PHPNature 31 | org.eclipse.wst.common.project.facet.core.nature 32 | com.dubture.composer.core.composerNature 33 | 34 | 35 | -------------------------------------------------------------------------------- /.settings/com.dubture.composer.core.prefs: -------------------------------------------------------------------------------- 1 | com.dubture.composer.corebuildpath.includes.excludes=\n 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.php.composer.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.php.composer.corebuildpath.includes.excludes=\n 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.php.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | include_path= 3 | phpVersion=php5.6 4 | useShortTags=false 5 | use_asp_tags_as_php=false 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.validation.prefs: -------------------------------------------------------------------------------- 1 | disabled=114tests/fixtures06vendor 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | dist: trusty 4 | 5 | php: 6 | - 5.6 7 | - 7.0 8 | - 7.1 9 | - 7.2 10 | - hhvm 11 | 12 | install: 13 | - composer install 14 | 15 | script: 16 | - vendor/bin/phpunit --coverage-clover=coverage.clover 17 | 18 | after_script: 19 | - wget https://scrutinizer-ci.com/ocular.phar 20 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Thomas Gossmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Code Formatter 2 | 3 | [![License](https://img.shields.io/github/license/gossi/php-code-formatter.svg?style=flat-square)](https://packagist.org/packages/gossi/php-code-formatter) 4 | [![Latest Stable Version](https://img.shields.io/packagist/v/gossi/php-code-formatter.svg?style=flat-square)](https://packagist.org/packages/gossi/php-code-formatter) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/gossi/php-code-formatter.svg?style=flat-square&colorB=007ec6)](https://packagist.org/packages/gossi/php-code-formatter)
6 | [![Build Status](https://img.shields.io/scrutinizer/build/g/gossi/php-code-formatter.svg?style=flat-square)](https://travis-ci.org/gossi/php-code-formatter) 7 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/gossi/php-code-formatter.svg?style=flat-square)](https://scrutinizer-ci.com/g/gossi/php-code-formatter) 8 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/gossi/php-code-formatter.svg?style=flat-square)](https://scrutinizer-ci.com/g/gossi/php-code-formatter) 9 | 10 | A library for formatting php code. 11 | 12 | 13 | ## Features 14 | 15 | - Whitespace 16 | - New lines 17 | - Indentation (on curly braces only) 18 | - Blanks (partial support) 19 | 20 | -> [Wishlist](https://github.com/gossi/php-code-formatter/labels/feature-request) 21 | 22 | ## Getting started 23 | 24 | ### Installation 25 | 26 | Via composer: 27 | 28 | ``` 29 | composer require gossi/php-code-formatter 30 | ``` 31 | 32 | ### From Code 33 | 34 | This simple code snippet is all you need: 35 | 36 | ```php 37 | use gossi\formatter\Formatter; 38 | 39 | $formatter = new Formatter(); 40 | $beautifulCode = $formatter->format($uglyCode); 41 | ``` 42 | 43 | ### From CLI 44 | 45 | A bare cli version is available: 46 | 47 | ``` 48 | vendor/bin/phormat path/to/file.php 49 | ``` 50 | 51 | will output the formatted source code to stdout 52 | 53 | 54 | ## Development 55 | 56 | php code formatter is not yet finished (see [Wishlist](https://github.com/gossi/php-code-formatter/labels/feature-request)). Please help the development, by picking one of the open issues or implement your own rules. See the wiki on [creating your own rules](https://github.com/gossi/php-code-formatter/wiki/creating-your-own-Rules). 57 | 58 | Psr-2? Spaces suck, deal with it :p Once [Version 1.0](https://github.com/gossi/php-code-formatter/milestones/Version%201.0) is reached, a psr-2 profile will be shipped. 59 | 60 | -------------------------------------------------------------------------------- /bin/phormat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "gossi/php-code-formatter", 3 | "type" : "library", 4 | "description" : "A code formatter for php code", 5 | "license" : "MIT", 6 | "authors" : [{ 7 | "name" : "Thomas Gossmann", 8 | "homepage" : "http://gos.si" 9 | } 10 | ], 11 | "keywords" : [ 12 | "php", 13 | "formatter", 14 | "beautifier" 15 | ], 16 | "support" : { 17 | "issues" : "https://github.com/gossi/php-code-formatter/issues" 18 | }, 19 | "autoload" : { 20 | "psr-4" : { 21 | "gossi\\formatter\\" : "src" 22 | } 23 | }, 24 | "autoload-dev" : { 25 | "psr-4" : { 26 | "gossi\\formatter\\tests\\" : "tests" 27 | } 28 | }, 29 | "require" : { 30 | "php" : ">=7.2", 31 | "gossi/php-code-profiles" : "dev-master", 32 | "symfony/config" : "^5.0", 33 | "symfony/yaml" : "^5.0", 34 | "symfony/event-dispatcher" : "~2|^3|^4", 35 | "symfony/console" : "~2|^3|^4", 36 | "phootwork/collection" : "^1.5", 37 | "phootwork/tokenizer" : "~0" 38 | }, 39 | "require-dev" : { 40 | "phpunit/phpunit" : "^5.7", 41 | "sami/sami" : "^4|^3", 42 | "phootwork/php-cs-fixer-config" : "~0.1" 43 | }, 44 | "scripts" : { 45 | "check" : "@cs", 46 | "cs" : "php-cs-fixer fix -v --diff --dry-run", 47 | "cs-fix" : "php-cs-fixer fix -v --diff", 48 | "test" : "phpunit", 49 | "api" : "sami.php update sami.php" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | ./tests/fixtures 17 | 18 | 19 | 20 | 21 | 22 | tests/ 23 | vendor/ 24 | 25 | 26 | src/ 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /profiles/default.yml: -------------------------------------------------------------------------------- 1 | indentation: 2 | # policy 3 | character: tab 4 | size: 1 5 | 6 | # declarations 7 | struct: true 8 | function: true 9 | blocks: true 10 | switch: true 11 | case: true 12 | break: true 13 | empty_lines: false 14 | 15 | # curly braces 16 | braces: 17 | struct: same 18 | function: same 19 | blocks: same 20 | 21 | whitespace: 22 | 23 | # global 24 | default: 25 | before_curly: true 26 | 27 | before_open: true 28 | after_open: false 29 | before_close: false 30 | after_close: false 31 | 32 | before_comma: false 33 | after_comma: true 34 | before_semicolon: false 35 | after_semicolon: true 36 | 37 | before_arrow: false 38 | after_arrow: false 39 | before_doublecolon: false 40 | after_doublecolon: false 41 | 42 | # # declarations 43 | # struct: 44 | # before_curly: true 45 | # before_comma: false 46 | # after_comma: true 47 | # 48 | # properties: 49 | # # multi properties 50 | # before_comma: false 51 | # after_comma: true 52 | # 53 | # constants: 54 | # # multi constants 55 | # before_comma: false 56 | # after_comma: true 57 | # 58 | # function: 59 | # before_curly: true 60 | # before_open: false 61 | # after_open: false 62 | # before_close: false 63 | # before_comma: false 64 | # after_comma: true 65 | # 66 | # # control statements 67 | # blocks: 68 | # before_curly: true 69 | # after_curly: true 70 | # 71 | # ifelse: 72 | # before_open: false 73 | # after_open: false 74 | # before_close: false 75 | # 76 | # for: 77 | # before_open: false 78 | # after_open: false 79 | # before_close: false 80 | # before_comma: false 81 | # after_comma: true 82 | # before_semicolon: false 83 | # after_semicolon: true 84 | # 85 | # foreach: 86 | # before_open: false 87 | # after_open: false 88 | # before_close: false 89 | # before_arrow: true 90 | # after_arrow: true 91 | # 92 | # switch: 93 | # before_open: false 94 | # after_open: false 95 | # before_close: false 96 | # before_colon: false 97 | # 98 | # while: 99 | # before_open: false 100 | # after_open: false 101 | # before_close: false 102 | # 103 | # catch: 104 | # before_open: false 105 | # after_open: false 106 | # before_close: false 107 | # 108 | # # multiple static 109 | # static: 110 | # before_comma: false 111 | # after_comma: true 112 | # 113 | # global: 114 | # before_comma: false 115 | # after_comma: true 116 | # 117 | # echo: 118 | # before_comma: false 119 | # after_comma: true 120 | # 121 | # yield: 122 | # before_arrow: true 123 | # after_arrow: true 124 | 125 | # expressions 126 | field_access: 127 | before_arrow: false 128 | after_arrow: false 129 | before_doublecolon: false 130 | after_doublecolon: false 131 | 132 | function_invocation: 133 | before_open: false 134 | after_open: false 135 | before_close: false 136 | before_comma: false 137 | after_comma: true 138 | before_arrow: false 139 | after_arrow: false 140 | before_doublecolon: false 141 | after_doublecolon: false 142 | 143 | assignments: 144 | before_assignment: true 145 | after_assignment: true 146 | 147 | operators: 148 | before_binary: true 149 | after_binary: true 150 | before_unary: true 151 | after_unary: false 152 | before_prefix: false 153 | after_prefix: false 154 | before_postfix: false 155 | after_postfix: true 156 | 157 | # type_casts: 158 | # before_open: false 159 | # after_open: false 160 | # before_close: false 161 | 162 | conditionals: 163 | before_questionmark: true 164 | after_questionmark: true 165 | before_colon: true 166 | after_colon: true 167 | 168 | # grouping with parenthesis 169 | grouping: 170 | before_open: false 171 | after_open: false 172 | before_close: false 173 | after_close: false 174 | 175 | # arrays 176 | 177 | newlines: 178 | elseif_else: false 179 | catch: false 180 | finally: false 181 | do_while: false 182 | 183 | blanks: 184 | 185 | # namespace 186 | before_namespace: 0 187 | after_namespace: 1 188 | after_use: 1 189 | 190 | # structs 191 | before_struct: 1 192 | before_traits: 1 193 | before_constants: 1 194 | before_fields: 1 195 | before_methods: 1 196 | beginning_method: 0 197 | end_method: 0 198 | end_struct: 1 199 | 200 | # file 201 | end_file: 1 202 | -------------------------------------------------------------------------------- /sami.php: -------------------------------------------------------------------------------- 1 | files() 11 | ->name('*.php') 12 | ->in($dir) 13 | ; 14 | 15 | return new Sami($iterator, [ 16 | 'title' => 'PHP Code Formatter API', 17 | 'theme' => 'default', 18 | 'build_dir' => __DIR__ . '/api/%version%', 19 | 'cache_dir' => __DIR__ . '/cache/%version%', 20 | 'default_opened_level' => 2 21 | ]); 22 | -------------------------------------------------------------------------------- /src/Formatter.php: -------------------------------------------------------------------------------- 1 | profile = $profile; 21 | } 22 | 23 | public function format($code) { 24 | $parser = new Parser(); 25 | $parser->parse($code); 26 | 27 | // formatting 28 | $delegate = new DelegateFormatter($parser, $this->profile); 29 | $delegate->format(); 30 | 31 | // post processing 32 | 33 | return $delegate->getCode(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/FormatterCli.php: -------------------------------------------------------------------------------- 1 | setArguments(); 44 | 45 | return $inputDefinition; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/collections/UnitCollection.php: -------------------------------------------------------------------------------- 1 | collection as $unit) { 28 | if ($unit->start === $token) { 29 | return $unit; 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/FormatterCommand.php: -------------------------------------------------------------------------------- 1 | setName('format') 19 | ->setDescription('Beautify PHP Code') 20 | ->addArgument( 21 | 'input', 22 | InputArgument::REQUIRED, 23 | 'The input' 24 | ) 25 | // ->addArgument( 26 | // 'output', 27 | // InputArgument::OPTIONAL, 28 | // 'The output' 29 | // ) 30 | ->addOption( 31 | 'profile', 32 | null, 33 | InputOption::VALUE_OPTIONAL, 34 | 'The profile with the formatting options' 35 | ) 36 | ; 37 | } 38 | 39 | protected function execute(InputInterface $in, OutputInterface $output) { 40 | $in = $in->getArgument('input'); 41 | // $out = $in->getArgument('output'); 42 | 43 | $code = file_exists($in) ? file_get_contents($in) : null; 44 | 45 | if ($code !== null) { 46 | $formatter = new Formatter(); 47 | $beauty = $formatter->format($code); 48 | 49 | echo $output->writeln($beauty); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/entities/Block.php: -------------------------------------------------------------------------------- 1 | self::TYPE_NAMESPACE, 34 | T_CLASS => self::TYPE_CLASS, 35 | T_TRAIT => self::TYPE_TRAIT, 36 | T_INTERFACE => self::TYPE_INTERFACE, 37 | T_FUNCTION => self::TYPE_FUNCTION, 38 | T_IF => self::TYPE_IF, 39 | T_ELSEIF => self::TYPE_ELSEIF, 40 | T_ELSE => self::TYPE_ELSE, 41 | T_DO => self::TYPE_DO, 42 | T_WHILE => self::TYPE_WHILE, 43 | T_FOR => self::TYPE_FOR, 44 | T_FOREACH => self::TYPE_FOREACH, 45 | T_SWITCH => self::TYPE_SWITCH, 46 | T_USE => self::TYPE_USE, 47 | T_TRY => self::TYPE_TRY, 48 | T_CATCH => self::TYPE_CATCH, 49 | ]; 50 | 51 | private static $STRUCTS = [self::TYPE_CLASS, self::TYPE_TRAIT, self::TYPE_INTERFACE]; 52 | private static $ROUTINE = [self::TYPE_FUNCTION, self::TYPE_METHOD]; 53 | private static $BLOCKS = [self::TYPE_IF, self::TYPE_ELSEIF, self::TYPE_ELSE, self::TYPE_DO, 54 | self::TYPE_WHILE, self::TYPE_FOR, self::TYPE_FOREACH, self::TYPE_SWITCH, self::TYPE_USE, 55 | self::TYPE_NAMESPACE]; 56 | 57 | /** 58 | * The opening curly brace 59 | * 60 | * @var Token 61 | */ 62 | public $open = null; 63 | 64 | /** 65 | * The closing curly brace 66 | * 67 | * @var Token 68 | */ 69 | public $close = null; 70 | 71 | /** 72 | * Start is the initial token of that block 73 | * 74 | * @var Token 75 | */ 76 | public $start = null; 77 | 78 | /** 79 | * Start is the last token of that block 80 | * 81 | * @var Token 82 | */ 83 | public $end = null; 84 | 85 | public $type = ''; 86 | 87 | public function __construct($type) { 88 | $this->type = $type; 89 | } 90 | 91 | public function isStruct() { 92 | return in_array($this->type, self::$STRUCTS); 93 | } 94 | 95 | public function isRoutine() { 96 | return in_array($this->type, self::$ROUTINE); 97 | } 98 | 99 | public function isBlock() { 100 | return in_array($this->type, self::$BLOCKS); 101 | } 102 | 103 | public static function getType(Token $token) { 104 | if (isset(self::$typeMap[$token->type])) { 105 | return self::$typeMap[$token->type]; 106 | } else if ($token->contents == 'finally') { 107 | return self::TYPE_FINALLY; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/entities/Group.php: -------------------------------------------------------------------------------- 1 | type == self::BLOCK; 25 | } 26 | 27 | public function isCall() { 28 | return $this->type == self::CALL; 29 | } 30 | 31 | public function isGroup() { 32 | return $this->type == self::GROUP; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/entities/Unit.php: -------------------------------------------------------------------------------- 1 | self::UNIT_NAMESPACE, 18 | T_USE => self::UNIT_USE, 19 | T_CONST => self::UNIT_CONSTANTS, 20 | T_NAMESPACE => self::UNIT_NAMESPACE, 21 | T_USE => self::UNIT_USE, 22 | T_REQUIRE => self::UNIT_IMPORTS, 23 | T_REQUIRE_ONCE => self::UNIT_IMPORTS, 24 | T_INCLUDE => self::UNIT_IMPORTS, 25 | T_INCLUDE_ONCE => self::UNIT_IMPORTS 26 | ]; 27 | 28 | /** @var Token */ 29 | public $start = null; 30 | 31 | /** @var Token */ 32 | public $end = null; 33 | public $type = ''; 34 | 35 | public static function getType(Token $token) { 36 | if (isset(self::$typeMap[$token->type])) { 37 | return self::$typeMap[$token->type]; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/events/BlockEvent.php: -------------------------------------------------------------------------------- 1 | block = $block; 15 | } 16 | 17 | public function getName() { 18 | return 'context.block_' . ($this->block->end === null ? 'enter' : 'leave'); 19 | } 20 | 21 | /** 22 | * Returns the associated block 23 | * 24 | * @return Block 25 | */ 26 | public function getBlock() { 27 | return $this->block; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/events/GroupEvent.php: -------------------------------------------------------------------------------- 1 | group = $group; 15 | } 16 | 17 | public function getName() { 18 | return 'context.group_' . ($this->group->end === null ? 'enter' : 'leave'); 19 | } 20 | 21 | /** 22 | * Returns the associated group 23 | * 24 | * @return Group 25 | */ 26 | public function getGroup() { 27 | return $this->group; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/events/TokenEvent.php: -------------------------------------------------------------------------------- 1 | token = $token; 13 | } 14 | 15 | public function getToken() { 16 | return $this->token; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/formatters/BaseFormatter.php: -------------------------------------------------------------------------------- 1 | config = $profile; 30 | $this->writer = $writer; 31 | $this->parser = $parser; 32 | $this->context = $parser->getContext(); 33 | $this->matcher = $parser->getMatcher(); 34 | 35 | $this->init(); 36 | } 37 | 38 | public function visitToken(Token $token) { 39 | $this->nextToken = $this->parser->getTracker()->getNextToken(); 40 | $this->prevToken = $this->parser->getTracker()->getPrevToken(); 41 | 42 | $this->doVisitToken($token); 43 | } 44 | 45 | protected function doVisitToken(Token $token) { 46 | 47 | } 48 | 49 | protected function init() { 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/formatters/BlanksFormatter.php: -------------------------------------------------------------------------------- 1 | units = $this->parser->getAnalyzer()->getUnits(); 13 | } 14 | 15 | protected function doVisitToken(Token $token) { 16 | $this->unitStart($token); 17 | $this->unitEnd($token); 18 | } 19 | 20 | private function unitStart(Token $token) { 21 | $unit = $this->units->findUnitByStart($token); 22 | 23 | if ($unit !== null) { 24 | $this->currentUnit = $unit; 25 | $this->blankBefore($unit->type); 26 | } 27 | } 28 | 29 | private function unitEnd(Token $token) { 30 | if ($this->currentUnit !== null && $this->currentUnit->end === $token) { 31 | $this->blankAfter($this->currentUnit->type); 32 | $this->currentUnit = null; 33 | } 34 | } 35 | 36 | private function blankBefore($key) { 37 | for ($i = 0, $count = $this->config->getBlanks('before_' . $key); $i < $count; $i++) { 38 | $this->defaultFormatter->addPreWriteln(); 39 | } 40 | } 41 | 42 | private function blankAfter($key) { 43 | for ($i = 0, $count = $this->config->getBlanks('after_' . $key); $i < $count; $i++) { 44 | $this->defaultFormatter->addPostWriteln(); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/formatters/CommentsFormatter.php: -------------------------------------------------------------------------------- 1 | type == T_DOC_COMMENT 11 | || $token->type == T_INLINE_HTML && strpos($token->contents, '/*') !== 0) { 12 | 13 | $lines = explode("\n", $token->contents); 14 | $firstLine = array_shift($lines); 15 | $this->writer->writeln(); 16 | $this->writer->writeln($firstLine); 17 | 18 | foreach ($lines as $line) { 19 | $this->writer->writeln(' ' . ltrim($line)); 20 | } 21 | 22 | $this->defaultFormatter->hideToken(); 23 | } 24 | } 25 | 26 | public static function isComment(Token $token) { 27 | return $token->type == T_DOC_COMMENT 28 | || ($token->type == T_INLINE_HTML && strpos($token->contents, '/*') !== 0) 29 | || $token->type == T_COMMENT; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/formatters/DefaultFormatter.php: -------------------------------------------------------------------------------- 1 | preCommands = new Queue(); 16 | $this->postCommands = new Queue(); 17 | } 18 | 19 | public function addPreWrite($content = '') { 20 | $this->preCommands->enqueue(['write', $content]); 21 | } 22 | 23 | public function addPreWriteln($content = '') { 24 | $this->preCommands->enqueue(['writeln', $content]); 25 | } 26 | 27 | public function addPreIndent() { 28 | $this->preCommands->enqueue(['indent']); 29 | } 30 | 31 | public function addPreOutdent() { 32 | $this->preCommands->enqueue(['outdent']); 33 | } 34 | 35 | public function addPostWrite($content = '') { 36 | $this->postCommands->enqueue(['write', $content]); 37 | } 38 | 39 | public function addPostWriteln($content = '') { 40 | $this->postCommands->enqueue(['writeln', $content]); 41 | } 42 | 43 | public function addPostIndent() { 44 | $this->postCommands->enqueue(['indent']); 45 | } 46 | 47 | public function addPostOutdent() { 48 | $this->postCommands->enqueue(['outdent']); 49 | } 50 | 51 | public function hideToken() { 52 | $this->showToken = false; 53 | } 54 | 55 | protected function doVisitToken(Token $token) { 56 | $group = $this->context->getGroupContext(); 57 | 58 | // pre commands 59 | $this->processCommands($this->preCommands); 60 | 61 | // finish line on semicolon 62 | if ($token->contents == ';' && $group->type != Group::BLOCK) { 63 | $this->context->resetLineContext(); 64 | $this->writer->writeln($token->contents); 65 | } 66 | 67 | // when no semicolon and token output allowed 68 | else if ($this->showToken) { 69 | $this->writer->write($token->contents); 70 | } 71 | 72 | // post commands 73 | $this->processCommands($this->postCommands); 74 | 75 | // reset 76 | $this->preCommands->clear(); 77 | $this->postCommands->clear(); 78 | $this->showToken = true; 79 | } 80 | 81 | private function processCommands(Queue $commands) { 82 | foreach ($commands as $cmd) { 83 | switch ($cmd[0]) { 84 | case 'write': 85 | $this->writer->write($cmd[1]); 86 | break; 87 | 88 | case 'writeln': 89 | $this->writer->writeln($cmd[1]); 90 | break; 91 | 92 | case 'indent': 93 | $this->writer->indent(); 94 | break; 95 | 96 | case 'outdent': 97 | $this->writer->outdent(); 98 | break; 99 | } 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/formatters/DelegateFormatter.php: -------------------------------------------------------------------------------- 1 | profile = $profile; 31 | $this->parser = $parser; 32 | $this->writer = new Writer([ 33 | 'indentation_character' => $profile->getIndentation('character') == 'tab' ? "\t" : ' ', 34 | 'indentation_size' => $profile->getIndentation('size') 35 | ]); 36 | 37 | // define rules 38 | $this->defaultFormatter = new DefaultFormatter($parser, $profile, $this->writer); 39 | $this->commentsFormatter = new CommentsFormatter($parser, $profile, $this->writer, $this->defaultFormatter); 40 | $this->indentationFormatter = new IndentationFormatter($parser, $profile, $this->writer, $this->defaultFormatter); 41 | $this->newlineFormatter = new NewlineFormatter($parser, $profile, $this->writer, $this->defaultFormatter); 42 | $this->whitespaceFormatter = new WhitespaceFormatter($parser, $profile, $this->writer, $this->defaultFormatter); 43 | $this->blanksFormatter = new BlanksFormatter($parser, $profile, $this->writer, $this->defaultFormatter); 44 | } 45 | 46 | public function format() { 47 | foreach ($this->parser->getTokens() as $token) { 48 | $token->accept($this); 49 | } 50 | } 51 | 52 | public function visitToken(Token $token) { 53 | $this->parser->getTracker()->visitToken($token); 54 | 55 | // visit all rules 56 | $this->commentsFormatter->visitToken($token); 57 | $this->indentationFormatter->visitToken($token); 58 | $this->newlineFormatter->visitToken($token); 59 | $this->whitespaceFormatter->visitToken($token); 60 | $this->blanksFormatter->visitToken($token); 61 | $this->defaultFormatter->visitToken($token); 62 | } 63 | 64 | public function getCode() { 65 | return $this->writer->getContent(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/formatters/IndentationFormatter.php: -------------------------------------------------------------------------------- 1 | indentOpenCurlyBrace($token); 10 | $this->indentCloseCurlyBrace($token); 11 | } 12 | 13 | private function indentOpenCurlyBrace(Token $token) { 14 | if ($token->contents == '{') { 15 | $this->defaultFormatter->addPostIndent(); 16 | } 17 | } 18 | 19 | private function indentCloseCurlyBrace(Token $token) { 20 | if ($token->contents == '}') { 21 | $this->defaultFormatter->addPreOutdent(); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/formatters/NewlineFormatter.php: -------------------------------------------------------------------------------- 1 | context->addListener(Context::EVENT_BLOCK_ENTER, [$this, 'preOpenCurlyBrace']); 12 | $this->context->addListener(Context::EVENT_BLOCK_LEAVE, [$this, 'postCloseCurlyBrace']); 13 | } 14 | 15 | public function preOpenCurlyBrace(BlockEvent $event) { 16 | $block = $event->getBlock(); 17 | 18 | // curly braces in strucs 19 | if ($block->isStruct()) { 20 | $this->newlineOrSpaceBeforeCurly($this->config->getBraces('struct') == 'next'); 21 | } 22 | 23 | // curly braces in functions 24 | else if ($block->isRoutine()) { 25 | $this->newlineOrSpaceBeforeCurly($this->config->getBraces('function') == 'next'); 26 | } 27 | 28 | // curly braces in blocks 29 | else if ($block->isBlock()) { 30 | $this->newlineOrSpaceBeforeCurly($this->config->getBraces('blocks') == 'next'); 31 | } 32 | 33 | // new line after open curly brace 34 | $this->defaultFormatter->addPostWriteln(); 35 | } 36 | 37 | public function postCloseCurlyBrace(BlockEvent $event) { 38 | $block = $event->getBlock(); 39 | $token = $event->getToken(); 40 | $nextToken = $this->parser->getTracker()->nextToken($token); 41 | 42 | // check new line before T_ELSE and T_ELSEIF 43 | if (in_array($block->type, [Block::TYPE_IF, Block::TYPE_ELSEIF]) 44 | && in_array($nextToken->type, [T_ELSE, T_ELSEIF])) { 45 | $this->newlineOrSpaceAfterCurly($this->config->getNewline('elseif_else')); 46 | } 47 | 48 | // check new line before T_CATCH 49 | else if ($this->nextToken->type == T_CATCH) { 50 | $this->newlineOrSpaceAfterCurly($this->config->getNewline('catch')); 51 | } 52 | 53 | // check new line before finally 54 | else if ($token->contents == 'finally') { 55 | $this->newlineOrSpaceAfterCurly($this->config->getNewline('finally')); 56 | } 57 | 58 | // check new line before while in a do-while block 59 | else if ($block->type == Block::TYPE_DO && $nextToken->type == T_WHILE) { 60 | $this->newlineOrSpaceAfterCurly($this->config->getNewline('do_while')); 61 | } 62 | 63 | // anyway a new line 64 | else { 65 | $this->defaultFormatter->addPostWriteln(); 66 | } 67 | } 68 | 69 | private function newlineOrSpaceBeforeCurly($condition) { 70 | if ($condition) { 71 | $this->writer->writeln(); 72 | } else if ($this->config->getWhitespace('before_curly')) { 73 | $this->writer->write(' '); 74 | } 75 | } 76 | 77 | private function newlineOrSpaceAfterCurly($condition) { 78 | if ($condition) { 79 | $this->writer->writeln(); 80 | } else { 81 | $this->defaultFormatter->addPostWrite(' '); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/formatters/SpecializedFormatter.php: -------------------------------------------------------------------------------- 1 | defaultFormatter = $default; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/formatters/WhitespaceFormatter.php: -------------------------------------------------------------------------------- 1 | 'ifelse', 11 | T_ELSEIF => 'ifelse', 12 | T_WHILE => 'while', 13 | T_FOREACH => 'foreach', 14 | T_FOR => 'for', 15 | T_CATCH => 'catch' 16 | ]; 17 | 18 | private static $SYNTAX = [ 19 | ')' => 'close', 20 | '(' => 'open', 21 | ',' => 'comma', 22 | ';' => 'semicolon', 23 | ':' => 'colon', 24 | '=>' => 'arrow', 25 | '->' => 'arrow', // function invocation 26 | '::' => 'doublecolon', // function invocation 27 | '?' => 'questionmark' 28 | ]; 29 | 30 | protected function doVisitToken(Token $token) { 31 | $this->applyKeywords($token); 32 | $this->applyAssignments($token); 33 | $this->applyOperators($token); 34 | $this->applyPrefixPostfix($token); 35 | $this->applyUnary($token); 36 | $this->applySyntax($token); 37 | } 38 | 39 | private function applyKeywords(Token $token) { 40 | if ($this->matcher->isKeyword($token)) { 41 | $this->defaultFormatter->addPostWrite(' '); 42 | } 43 | } 44 | 45 | private function applyAssignments(Token $token) { 46 | if ($this->matcher->isAssignment($token)) { 47 | $this->whitespaceBeforeAfter('assignment', 'assignments'); 48 | } 49 | } 50 | 51 | private function applyOperators(Token $token) { 52 | if ($this->matcher->isOperator($token)) { 53 | $this->whitespaceBeforeAfter('binary', 'operators'); 54 | } 55 | } 56 | 57 | private function applyPrefixPostfix(Token $token) { 58 | if ($token->type == T_INC || $token->type == T_DEC) { 59 | // pre 60 | if ($this->nextToken->type == T_VAR) { 61 | $this->whitespaceBeforeAfter('prefix', 'operators'); 62 | } 63 | 64 | // post 65 | else if ($this->prevToken->type == T_VAR) { 66 | $this->whitespaceBeforeAfter('postfix', 'operators'); 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * @TODO 73 | * @param Token $token 74 | */ 75 | private function applyUnary(Token $token) { 76 | 77 | } 78 | 79 | private function applySyntax(Token $token) { 80 | if (array_key_exists($token->contents, self::$SYNTAX)) { 81 | $key = self::$SYNTAX[$token->contents]; 82 | $group = $this->context->getGroupContext(); 83 | 84 | // return when semicolon is not inside a block context 85 | if ($token->contents == ';' && $group->type != Group::BLOCK) { 86 | return; 87 | } 88 | 89 | // anyway find context and apply it 90 | $context = $this->findContext($token); 91 | $this->whitespaceBeforeAfter($key, $context); 92 | } 93 | } 94 | 95 | private function findContext(Token $token) { 96 | $group = $this->context->getGroupContext(); 97 | $context = 'default'; 98 | 99 | // first check the context of the current line 100 | $line = $this->context->getLineContext(); 101 | if (!empty($line)) { 102 | $context = $line; 103 | } 104 | 105 | // is it a parens group? 106 | else if ($group->type == Group::GROUP) { 107 | $context = 'grouping'; 108 | } 109 | 110 | // a function call? 111 | else if ($group->type == Group::CALL) { 112 | $context = 'function_invocation'; 113 | } 114 | 115 | // field access? 116 | else if ($token->contents === '->' || $token->contents === '::') { 117 | $context = 'field_access'; 118 | } 119 | 120 | // or a given block statement? 121 | else if ($group->type == Group::BLOCK 122 | && isset(self::$BLOCK_CONTEXT_MAPPING[$group->token->type])) { 123 | $context = self::$BLOCK_CONTEXT_MAPPING[$group->token->type]; 124 | } 125 | 126 | return $context; 127 | } 128 | 129 | private function whitespaceBeforeAfter($key, $context = 'default') { 130 | if ($this->config->getWhitespace('before_' . $key, $context)) { 131 | $this->defaultFormatter->addPreWrite(' '); 132 | } 133 | 134 | if ($this->config->getWhitespace('after_' . $key, $context)) { 135 | $this->defaultFormatter->addPostWrite(' '); 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/parser/Analyzer.php: -------------------------------------------------------------------------------- 1 | parser = $parser; 25 | $this->matcher = $parser->getMatcher(); 26 | $this->units = new UnitCollection(); 27 | 28 | // register listeners 29 | $context = $parser->getContext(); 30 | $context->addListener(Context::EVENT_ROUTINE_LEAVE, [$this, 'onRoutineClosed']); 31 | $context->addListener(Context::EVENT_BLOCK_LEAVE, [$this, 'onBlockClosed']); 32 | } 33 | 34 | public function getUnits() { 35 | return $this->units; 36 | } 37 | 38 | public function analyze(TokenCollection $tokens) { 39 | foreach ($tokens as $token) { 40 | $this->parser->getTracker()->visitToken($token); 41 | $this->findUnitStart($token); 42 | $this->findUnitEnd($token); 43 | $this->finish($token); 44 | } 45 | } 46 | 47 | private function findUnitStart(Token $token) { 48 | $detectedUnit = null; 49 | $detectedUnitType = null; 50 | 51 | if ($this->detectedUnit === null && ($this->matcher->isModifier($token) 52 | || $this->matcher->isUnitIdentifier($token))) { 53 | $detectedUnit = $token; 54 | } 55 | 56 | if ($detectedUnit !== null) { 57 | 58 | // traits = use statements in struct body 59 | if ($token->type == T_USE && $this->parser->getContext()->getCurrentContext() == Context::CONTEXT_STRUCT) { 60 | $detectedUnitType = Unit::UNIT_TRAITS; 61 | } 62 | 63 | // line statements 64 | else if ($this->matcher->isUnitIdentifier($token)) { 65 | $detectedUnitType = Unit::getType($token); 66 | } 67 | 68 | // check for properties 69 | else if ($this->matcher->isModifier($token)) { 70 | $nextToken = $token; 71 | do { 72 | $nextToken = $this->parser->getTracker()->nextToken($nextToken); 73 | if ($nextToken !== null && $nextToken->type == T_VARIABLE) { 74 | $detectedUnitType = Unit::UNIT_FIELDS; 75 | break; 76 | } else if ($nextToken !== null && $nextToken->type == T_FUNCTION) { 77 | $detectedUnitType = Unit::UNIT_METHODS; 78 | break; 79 | } else if ($nextToken !== null && $nextToken->type == T_CLASS) { 80 | return; 81 | } 82 | } while ($this->matcher->isModifier($nextToken)); 83 | } 84 | 85 | // continue last unit, or start new unit? 86 | if ($detectedUnitType !== Unit::UNIT_METHODS 87 | && $this->currentUnit !== null 88 | && $detectedUnitType === $this->currentUnit->type) { 89 | $prevToken = $token; 90 | do { 91 | $prevToken = $this->parser->getTracker()->prevToken($prevToken); 92 | } while (CommentsFormatter::isComment($prevToken) 93 | || $this->matcher->isModifier($prevToken)); 94 | 95 | // yes, new unit 96 | if ($prevToken !== $this->currentUnit->end) { 97 | $this->dumpCurrentUnit(); 98 | $this->detectedUnit = $detectedUnit; 99 | $this->detectedUnitType = $detectedUnitType; 100 | } 101 | } else { 102 | $this->dumpCurrentUnit(); 103 | $this->detectedUnit = $detectedUnit; 104 | $this->detectedUnitType = $detectedUnitType; 105 | } 106 | } 107 | } 108 | 109 | private function findUnitEnd(Token $token) { 110 | if ($this->detectedUnit !== null) { 111 | if ($token->contents == ';') { 112 | $this->flushDetection($token); 113 | } 114 | } 115 | } 116 | 117 | private function flushDetection(Token $token) { 118 | $this->currentUnit = new Unit(); 119 | $this->currentUnit->start = $this->detectedUnit; 120 | $this->currentUnit->type = $this->detectedUnitType; 121 | $this->currentUnit->end = $token; 122 | 123 | $this->detectedUnit = null; 124 | $this->detectedUnitType = null; 125 | } 126 | 127 | public function onRoutineClosed(BlockEvent $event) { 128 | if ($this->parser->getContext()->getCurrentContext() == Context::CONTEXT_STRUCT) { 129 | 130 | $block = $event->getBlock(); 131 | 132 | if ($this->currentUnit === null || $block->start != $this->currentUnit->start) { 133 | $this->detectedUnit = $block->start; 134 | $this->detectedUnitType = Unit::UNIT_METHODS; 135 | $this->flushDetection($event->getToken()); 136 | } 137 | 138 | $this->dumpCurrentUnit(); 139 | } 140 | } 141 | 142 | public function onBlockClosed(BlockEvent $event) { 143 | $block = $event->getBlock(); 144 | if ($block->type == Block::TYPE_USE || $block->type == Block::TYPE_NAMESPACE) { 145 | $this->detectedUnit = $block->start; 146 | $this->detectedUnitType = $block->type; 147 | $this->flushDetection($event->getToken()); 148 | } 149 | } 150 | 151 | private function finish(Token $token) { 152 | if ($this->parser->getTracker()->isLastToken($token)) { 153 | $this->dumpCurrentUnit(); 154 | } 155 | } 156 | 157 | private function dumpCurrentUnit() { 158 | if ($this->currentUnit !== null) { 159 | $this->units->add($this->currentUnit); 160 | $this->currentUnit = null; 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/parser/Context.php: -------------------------------------------------------------------------------- 1 | parser = $parser; 61 | $this->matcher = $parser->getMatcher(); 62 | $this->blockStack = new Stack(); 63 | $this->groupStack = new Stack(); 64 | $this->contextStack = new Stack(); 65 | $this->dispatcher = new EventDispatcher(); 66 | } 67 | 68 | public function reset() { 69 | // remove listeners 70 | foreach ($this->events as $event) { 71 | $listeners = $this->dispatcher->getListeners($event); 72 | foreach ($listeners as $listener) { 73 | $this->dispatcher->removeListener($event, $listener); 74 | } 75 | } 76 | 77 | // reset data objects 78 | $this->blockStack->clear(); 79 | $this->groupStack->clear(); 80 | $this->contextStack->clear(); 81 | } 82 | 83 | public function setTracker(TokenTracker $tracker) { 84 | $this->tracker = $tracker; 85 | } 86 | 87 | public function addListener($name, $listener) { 88 | $this->dispatcher->addListener($name, $listener); 89 | } 90 | 91 | public function removeListener($name, $listener) { 92 | $this->dispatcher->removeListener($name, $listener); 93 | } 94 | 95 | public function visitToken(Token $token) { 96 | // load current contexts 97 | $this->block = $this->peekBlock(); 98 | $this->group = $this->peekGroup(); 99 | 100 | // detect new contexts 101 | $this->detectBlockContext($token); 102 | $this->detectGroupContext($token); 103 | $this->detectLineContext($token); 104 | } 105 | 106 | private function detectBlockContext(Token $token) { 107 | if ($this->matcher->isBlock($token)) { 108 | $this->blockDetected = $token; 109 | } 110 | 111 | $this->enterBlockContext($token); 112 | $this->leaveBlockContext($token); 113 | 114 | // neglect block detection 115 | if ($this->blockDetected !== null && $token->contents == ';' 116 | && ($this->group !== null ? !($this->group->type == Group::BLOCK 117 | || $this->group->type == Group::GROUP) : true)) { 118 | $this->blockDetected = null; 119 | } 120 | } 121 | 122 | private function enterBlockContext(Token $token) { 123 | if ($token->contents == '{' && $this->blockDetected !== null) { 124 | $type = Block::getType($this->blockDetected); 125 | if ($type == Block::TYPE_FUNCTION && $this->getCurrentContext() == self::CONTEXT_STRUCT) { 126 | $type = Block::TYPE_METHOD; 127 | } 128 | $block = new Block($type); 129 | $block->start = $this->findBlockStart($this->blockDetected); 130 | $block->open = $token; 131 | $this->blockStack->push($block); 132 | $this->block = $block; 133 | $this->blockDetected = null; 134 | 135 | $event = new BlockEvent($token, $this->block); 136 | $this->dispatcher->dispatch(self::EVENT_BLOCK_ENTER, $event); 137 | 138 | // enter struct context 139 | if ($block->isStruct()) { 140 | $this->inStructBody = true; 141 | $this->dispatcher->dispatch(self::EVENT_STRUCT_ENTER, $event); 142 | $this->contextStack->push(self::CONTEXT_STRUCT); 143 | } 144 | 145 | // enter routine context 146 | else if ($block->isRoutine()) { 147 | $this->inRoutineBody = true; 148 | $this->dispatcher->dispatch(self::EVENT_ROUTINE_ENTER, $event); 149 | $this->contextStack->push(self::CONTEXT_ROUTINE); 150 | } 151 | 152 | // enter block context 153 | else { 154 | $this->contextStack->push(self::CONTEXT_BLOCK); 155 | } 156 | } 157 | } 158 | 159 | private function findBlockStart(Token $token) { 160 | $startToken = $token; 161 | $prevToken = $this->tracker->prevToken($token); 162 | 163 | while ($this->matcher->isModifier($prevToken)) { 164 | $startToken = $prevToken; 165 | $prevToken = $this->tracker->prevToken($prevToken); 166 | } 167 | 168 | return $startToken; 169 | } 170 | 171 | private function leaveBlockContext(Token $token) { 172 | if ($token->contents == '}') { 173 | $this->block = $this->blockStack->pop(); 174 | 175 | // find block end 176 | if ($this->block->type == Block::TYPE_DO) { 177 | $nextToken = $token; 178 | do { 179 | $nextToken = $this->tracker->nextToken($nextToken); 180 | } while ($nextToken->contents != ';'); 181 | $this->block->end = $nextToken; 182 | } else { 183 | $this->block->end = $token; 184 | } 185 | 186 | $event = new BlockEvent($token, $this->block); 187 | $this->dispatcher->dispatch(self::EVENT_BLOCK_LEAVE, $event); 188 | $this->contextStack->pop(); 189 | 190 | // leave struct context 191 | if ($this->inStructBody && $this->block->isStruct()) { 192 | $this->inStructBody = false; 193 | $this->dispatcher->dispatch(self::EVENT_STRUCT_LEAVE, $event); 194 | } 195 | 196 | // leave routine context 197 | else if ($this->inRoutineBody && $this->block->isRoutine()) { 198 | $this->inRoutineBody = false; 199 | $this->dispatcher->dispatch(self::EVENT_ROUTINE_LEAVE, $event); 200 | } 201 | } 202 | } 203 | 204 | public function isBlockContextDetected() { 205 | return $this->blockDetected !== null; 206 | } 207 | 208 | /** 209 | * Tells you, whenever being in a struct, this is also true when inside 210 | * a method or inside a function, which is inside a method 211 | * 212 | * @return bool 213 | */ 214 | public function inStructBody() { 215 | return $this->inStructBody; 216 | } 217 | 218 | /** 219 | * Tells you, whenever being in a function or method 220 | * 221 | * @return bool 222 | */ 223 | public function inRoutineBody() { 224 | return $this->inRoutineBody; 225 | } 226 | 227 | private function detectGroupContext(Token $token) { 228 | $prevToken = $this->tracker->getPrevToken(); 229 | if ($token->contents == '(') { 230 | $group = new Group(); 231 | $group->start = $token; 232 | // if (in_array($prevToken->type, Tokenizer::$BLOCKS) 233 | // || in_array($prevToken->type, Tokenizer::$OPERATORS)) { 234 | if (($this->matcher->isBlock($prevToken) || $this->matcher->isOperator($prevToken)) 235 | && !$this->group->isBlock()) { 236 | $group->type = Group::BLOCK; 237 | $group->token = $prevToken; 238 | } else if ($this->isFunctionInvocation($token)) { 239 | $group->type = Group::CALL; 240 | $group->token = $prevToken; 241 | } else { 242 | $group->type = Group::GROUP; 243 | } 244 | 245 | $this->groupStack->push($group); 246 | $this->group = $group; 247 | 248 | $event = new GroupEvent($token, $group); 249 | $this->dispatcher->dispatch(self::EVENT_GROUP_ENTER, $event); 250 | } else if ($token->contents == ')') { 251 | $this->group = $this->groupStack->pop(); 252 | $this->group->end = $token; 253 | 254 | $event = new GroupEvent($token, $this->group); 255 | $this->dispatcher->dispatch(self::EVENT_GROUP_LEAVE, $event); 256 | } 257 | } 258 | 259 | private function isFunctionInvocation($token) { 260 | $prevToken = $this->tracker->getPrevToken(); 261 | return $token->contents == '(' && $prevToken->type == T_STRING; 262 | } 263 | 264 | private function detectLineContext(Token $token) { 265 | if ($this->matcher->isLineContext($token)) { 266 | $this->line = $token->contents; 267 | } 268 | } 269 | 270 | public function resetLineContext() { 271 | $this->line = null; 272 | } 273 | 274 | /** 275 | * Returns the line context or null if not present 276 | * 277 | * @return string|null 278 | */ 279 | public function getLineContext() { 280 | return $this->line; 281 | } 282 | 283 | /** 284 | * Returns the current context. Context is one of the Context::CONTEXT_* constants. 285 | * 286 | * @return string 287 | */ 288 | public function getCurrentContext() { 289 | if ($this->contextStack->size() > 0) { 290 | return $this->contextStack->peek(); 291 | } 292 | 293 | return self::CONTEXT_FILE; 294 | } 295 | 296 | /** 297 | * Returns the current block context 298 | * 299 | * @return Block 300 | */ 301 | public function getBlockContext() { 302 | return $this->block; 303 | } 304 | 305 | /** 306 | * Returns the current group context 307 | * 308 | * @return Group|null 309 | */ 310 | public function getGroupContext() { 311 | return $this->group; 312 | } 313 | 314 | private function peekBlock() { 315 | if ($this->blockStack->size() > 0) { 316 | return $this->blockStack->peek(); 317 | } 318 | return new Block(null); 319 | } 320 | 321 | private function peekGroup() { 322 | if ($this->groupStack->size() > 0) { 323 | return $this->groupStack->peek(); 324 | } 325 | return new Group(); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/parser/Lexer.php: -------------------------------------------------------------------------------- 1 | size(); $i < $n; $i++) { 19 | $token = $tokens->get($i); 20 | 21 | // fix ELSEIF 22 | if ($token->type == T_ELSE) { 23 | $nextToken = $tokens->get($i + 1); 24 | 25 | if ($nextToken->type == T_IF) { 26 | $i++; 27 | $fixedTokens->add(new Token([T_ELSEIF, 'else if'])); 28 | } else { 29 | $fixedTokens->add($token); 30 | } 31 | 32 | continue; 33 | } 34 | 35 | $fixedTokens->add($token); 36 | } 37 | return $fixedTokens; 38 | } 39 | 40 | /** 41 | * 42 | * @param TokenCollection $tokens 43 | * @return TokenCollection 44 | */ 45 | public function filterTokens(TokenCollection $tokens) { 46 | return $tokens->filter(function (Token $token) { 47 | return $token->type != T_WHITESPACE; 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/parser/Parser.php: -------------------------------------------------------------------------------- 1 | matcher = new TokenMatcher(); 26 | $this->tokenizer = new PhpTokenizer(); 27 | $this->lexer = new Lexer(); 28 | $this->context = new Context($this); 29 | $this->analyzer = new Analyzer($this); 30 | } 31 | 32 | public function parse($code) { 33 | // get tokens 34 | $tokens = $this->tokenizer->tokenize($code); 35 | 36 | // preparations 37 | $tokens = $this->lexer->filterTokens($tokens); 38 | $tokens = $this->lexer->repair($tokens); 39 | 40 | // helpers 41 | $this->tracker = new TokenTracker($tokens, $this->context); 42 | $this->tokens = $tokens; 43 | 44 | // analyze 45 | $this->analyzer->analyze($tokens); 46 | $this->context->reset(); 47 | } 48 | 49 | /** 50 | * 51 | * @return TokenCollection 52 | */ 53 | public function getTokens() { 54 | return $this->tokens; 55 | } 56 | 57 | /** 58 | * @return TokenTracker 59 | */ 60 | public function getTracker() { 61 | return $this->tracker; 62 | } 63 | 64 | /** 65 | * @return Context 66 | */ 67 | public function getContext() { 68 | return $this->context; 69 | } 70 | 71 | /** 72 | * @return Analyzer 73 | */ 74 | public function getAnalyzer() { 75 | return $this->analyzer; 76 | } 77 | 78 | /** 79 | * @return TokenMatcher 80 | */ 81 | public function getMatcher() { 82 | return $this->matcher; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/parser/TokenMatcher.php: -------------------------------------------------------------------------------- 1 | >=', '^=']; 25 | public static $OPERATORS = ['&', '&&', 'and', '|', '||', 'or', '^', 'xor', 'instanceof', '==', '>=', '>', '===', '!=', '<>', '!==', '<=', '<', '<<', '>>', '+', '-', '*', '/', '**', 'as']; 26 | public static $STRUCTURAL = [T_CLASS, T_INTERFACE, T_TRAIT, T_NAMESPACE, T_USE, T_FUNCTION]; 27 | public static $STRUCTS = [T_CLASS, T_INTERFACE, T_TRAIT]; 28 | public static $LINE_CONTEXT = ['echo', 'global', 'static', 'yield', 'case']; 29 | 30 | /** @var Set */ 31 | private $keywords; 32 | /** @var Set */ 33 | private $blocks; 34 | /** @var Set */ 35 | private $casts; 36 | /** @var Set */ 37 | private $assignments; 38 | /** @var Set */ 39 | private $operators; 40 | /** @var Set */ 41 | private $structural; 42 | /** @var Set */ 43 | private $structs; 44 | /** @var Set */ 45 | private $lineContext; 46 | /** @var Set */ 47 | private $imports; 48 | /** @var Set */ 49 | private $unitIdentifier; 50 | /** @var Set */ 51 | private $modifier; 52 | 53 | public function __construct() { 54 | $this->keywords = new Set([T_ABSTRACT, T_CASE, T_CLASS, T_FUNCTION, T_CLONE, T_CONST, 55 | T_EXTENDS, T_FINAL, T_GLOBAL, T_IMPLEMENTS, T_INTERFACE, T_NAMESPACE, T_NEW, 56 | T_PRIVATE, T_PUBLIC, T_PROTECTED, T_THROW, T_TRAIT, T_USE]); 57 | 58 | $this->blocks = new Set([T_IF, T_ELSEIF, T_ELSE, T_FOR, T_FOREACH, T_WHILE, 59 | T_DO, T_SWITCH, T_TRY, T_CATCH, T_CLASS, T_INTERFACE, T_TRAIT, 60 | T_NAMESPACE, T_USE, T_FUNCTION]); 61 | 62 | $this->casts = new Set([T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, 63 | T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST]); 64 | 65 | $this->assignments = new Set(['=', '&=', '.=', '/=', '-=', '%=', '*=', '|=', '+=', 66 | '**=', '<<=', '>>=', '^=']); 67 | 68 | $this->operators = new Set(['&', '&&', 'and', '|', '||', 'or', '^', 'xor', 69 | 'instanceof', '==', '>=', '>', '===', '!=', '<>', '!==', '<=', '<', '<<', 70 | '>>', '+', '-', '*', '/', '**', 'as']); 71 | 72 | $this->structural = new Set([T_CLASS, T_INTERFACE, T_TRAIT, T_NAMESPACE, T_USE, T_FUNCTION]); 73 | 74 | $this->structs = new Set([T_CLASS, T_INTERFACE, T_TRAIT]); 75 | 76 | $this->lineContext = new Set(['echo', 'global', 'static', 'yield', 'case']); 77 | 78 | $this->imports = new Set([T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]); 79 | 80 | $this->unitIdentifier = new Set([T_CONST, T_NAMESPACE, T_USE]); 81 | $this->unitIdentifier->addAll($this->imports); 82 | 83 | $this->modifier = new Set([T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC, T_VAR, T_ABSTRACT]); 84 | } 85 | 86 | public function isKeyword(Token $token) { 87 | return $this->keywords->contains($token->type); 88 | } 89 | 90 | public function isBlock(Token $token) { 91 | return $this->blocks->contains($token->type); 92 | } 93 | 94 | public function isCast(Token $token) { 95 | return $this->blocks->contains($token->type); 96 | } 97 | 98 | public function isAssignment(Token $token) { 99 | return $this->assignments->contains($token->contents); 100 | } 101 | 102 | public function isOperator(Token $token) { 103 | return $this->operators->contains($token->contents); 104 | } 105 | 106 | public function isStruct(Token $token) { 107 | return $this->operators->contains($token->type); 108 | } 109 | 110 | public function isLineContext(Token $token) { 111 | return $this->lineContext->contains($token->contents); 112 | } 113 | 114 | public function isImport(Token $token) { 115 | return $this->imports->contains($token->type); 116 | } 117 | 118 | public function isUnitIdentifier(Token $token) { 119 | return $this->unitIdentifier->contains($token->type); 120 | } 121 | 122 | public function isModifier(Token $token) { 123 | return $this->modifier->contains($token->type); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/parser/TokenTracker.php: -------------------------------------------------------------------------------- 1 | tokens = $tokens; 18 | $this->context = $contextManager; 19 | $this->context->setTracker($this); 20 | } 21 | 22 | public function visitToken(Token $token) { 23 | $this->next = $this->nextToken($token); 24 | $this->prev = $this->prevToken($token); 25 | $this->context->visitToken($token); 26 | } 27 | 28 | public function getNextToken() { 29 | return $this->next; 30 | } 31 | 32 | public function getPrevToken() { 33 | return $this->prev; 34 | } 35 | 36 | public function nextToken(Token $token, $offset = 1) { 37 | $index = $this->tokens->indexOf($token); 38 | $index += $offset; 39 | $token = $this->tokens->get($index); 40 | 41 | if ($token === null) { 42 | $token = new Token(); 43 | } 44 | 45 | return $token; 46 | } 47 | 48 | public function prevToken($token, $offset = 1) { 49 | $index = $this->tokens->indexOf($token); 50 | $index -= $offset; 51 | $token = $this->tokens->get($index); 52 | 53 | if ($token === null) { 54 | $token = new Token(); 55 | } 56 | 57 | return $token; 58 | } 59 | 60 | public function isLastToken(Token $token) { 61 | return $this->tokens->indexOf($token) == $this->tokens->size() - 1; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/Sanitizer.php: -------------------------------------------------------------------------------- 1 | trimLines(); 15 | $this->removeDoubleBlanks(); 16 | } 17 | 18 | private function trimLines() { 19 | 20 | } 21 | 22 | private function removeDoubleBlanks() { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/Writer.php: -------------------------------------------------------------------------------- 1 | "\t", 18 | 'indentation_size' => 1 19 | ]; 20 | 21 | public function __construct($options = []) { 22 | $this->options = array_merge($this->options, $options); 23 | 24 | $this->indentation = str_repeat($this->options['indentation_character'], 25 | $this->options['indentation_size']); 26 | } 27 | 28 | public function indent() { 29 | $this->indentationLevel += 1; 30 | 31 | return $this; 32 | } 33 | 34 | public function outdent() { 35 | $this->indentationLevel -= 1; 36 | 37 | if ($this->indentationLevel < 0) { 38 | throw new \RuntimeException('The identation level cannot be less than zero.'); 39 | } 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * 46 | * @param string $content 47 | */ 48 | public function writeln($content = '') { 49 | $this->write($content . "\n"); 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * 56 | * @param string $content 57 | */ 58 | public function write($content) { 59 | $lines = explode("\n", $content); 60 | for ($i = 0, $c = count($lines); $i < $c; $i ++) { 61 | if ($this->indentationLevel > 0 62 | && !empty($lines[$i]) 63 | && (empty($this->content) || "\n" === substr($this->content, -1))) { 64 | $this->content .= str_repeat($this->indentation, $this->indentationLevel); 65 | } 66 | 67 | $this->content .= $lines[$i]; 68 | 69 | if ($i + 1 < $c) { 70 | $this->content .= "\n"; 71 | } 72 | } 73 | 74 | return $this; 75 | } 76 | 77 | public function rtrim() { 78 | $addNl = "\n" === substr($this->content, -1); 79 | $this->content = rtrim($this->content); 80 | 81 | if ($addNl) { 82 | $this->content .= "\n"; 83 | } 84 | 85 | return $this; 86 | } 87 | 88 | public function endsWith($search) { 89 | return substr($this->content, -strlen($search)) === $search; 90 | } 91 | 92 | public function reset() { 93 | $this->content = ''; 94 | $this->indentationLevel = 0; 95 | 96 | return $this; 97 | } 98 | 99 | public function getContent() { 100 | return $this->content; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/fixtures/samples/default/abstract-class.php: -------------------------------------------------------------------------------- 1 | content == self::XYZ) { 18 | doSomething(); 19 | } 20 | } 21 | 22 | public function mthd() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/fixtures/samples/default/sample1.php: -------------------------------------------------------------------------------- 1 | (1 - 1)); 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/samples/raw/abstract-class.php: -------------------------------------------------------------------------------- 1 | content == self::XYZ) { 26 | doSomething(); 27 | } 28 | } 29 | /** 30 | * Description for mthd 31 | * 32 | * @see #__construct 33 | * 34 | * @param string $param Description for $param 35 | * @return void 36 | */ 37 | public function mthd($param = false) { 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /tests/fixtures/samples/raw/class.php: -------------------------------------------------------------------------------- 1 | content == self::XYZ) { 13 | doSomething(); 14 | } 15 | } 16 | public function mthd() { 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /tests/fixtures/samples/raw/sample1.php: -------------------------------------------------------------------------------- 1 | (1-1));} -------------------------------------------------------------------------------- /tests/formatter/SamplesTest.php: -------------------------------------------------------------------------------- 1 | compareSample('sample1'); 12 | } 13 | 14 | public function testClass() { 15 | $this->compareSample('class'); 16 | } 17 | 18 | // public function testClassWithDocblock() { 19 | // $this->compareSample('class-phpdoc'); 20 | // } 21 | } 22 | -------------------------------------------------------------------------------- /tests/parser/AnalyzerTest.php: -------------------------------------------------------------------------------- 1 | getRawContent($file); 15 | $parser = new Parser(); 16 | $parser->parse($code); 17 | return $parser; 18 | } 19 | 20 | private function getUnits($file) { 21 | return $this->getParser($file)->getAnalyzer()->getUnits(); 22 | } 23 | 24 | public function testUnitsOrder() { 25 | $this->assertUnitsOrder($this->getUnits('class')); 26 | } 27 | 28 | public function testUnitsWithDocblockOrder() { 29 | $this->assertUnitsOrder($this->getUnits('class-phpdoc')); 30 | } 31 | 32 | private function assertUnitsOrder(UnitCollection $units) { 33 | $this->assertEquals(Unit::UNIT_NAMESPACE, $units->get(0)->type); 34 | $this->assertEquals(Unit::UNIT_USE, $units->get(1)->type); 35 | $this->assertEquals(Unit::UNIT_TRAITS, $units->get(2)->type); 36 | $this->assertEquals(Unit::UNIT_CONSTANTS, $units->get(3)->type); 37 | $this->assertEquals(Unit::UNIT_FIELDS, $units->get(4)->type); 38 | $this->assertEquals(Unit::UNIT_METHODS, $units->get(5)->type); 39 | } 40 | 41 | public function testUnitsAbstractOrder() { 42 | $units = $this->getUnits('abstract-class'); 43 | 44 | $this->assertEquals(Unit::UNIT_NAMESPACE, $units->get(0)->type); 45 | $this->assertEquals(Unit::UNIT_FIELDS, $units->get(1)->type); 46 | $this->assertEquals(Unit::UNIT_METHODS, $units->get(2)->type); 47 | $this->assertEquals(Unit::UNIT_METHODS, $units->get(3)->type); 48 | $this->assertEquals(Unit::UNIT_METHODS, $units->get(4)->type); 49 | $this->assertEquals(Unit::UNIT_METHODS, $units->get(5)->type); 50 | $this->assertEquals(Unit::UNIT_CONSTANTS, $units->get(6)->type); 51 | } 52 | 53 | // public function testUnitsOrderPosition() { 54 | 55 | // } 56 | } 57 | -------------------------------------------------------------------------------- /tests/parser/ContextTest.php: -------------------------------------------------------------------------------- 1 | getRawContent('sample1'); 18 | } 19 | 20 | public function testBlockStack() { 21 | $log = new ArrayList(); 22 | $listener = function (BlockEvent $event) use ($log) { 23 | $log->add($event->getName() . ' ' . $event->getBlock()->type); 24 | }; 25 | 26 | $parser = new Parser(); 27 | $parser->getContext()->addListener(Context::EVENT_BLOCK_ENTER, $listener); 28 | $parser->getContext()->addListener(Context::EVENT_BLOCK_LEAVE, $listener); 29 | $parser->parse($this->getCode()); 30 | 31 | $this->assertEquals([ 32 | 'context.block_enter if', 33 | 'context.block_leave if', 34 | 'context.block_enter else', 35 | 'context.block_leave else', 36 | 'context.block_enter for', 37 | 'context.block_enter do', 38 | 'context.block_enter if', 39 | 'context.block_leave if', 40 | 'context.block_leave do', 41 | 'context.block_leave for'], 42 | $log->toArray()); 43 | } 44 | 45 | public function testBlockPositionsOnSample1() { 46 | $blocks = new ArrayList(); 47 | $listener = function (BlockEvent $event) use ($blocks) { 48 | $blocks->add($event->getBlock()); 49 | }; 50 | 51 | $parser = new Parser(); 52 | $parser->getContext()->addListener(Context::EVENT_BLOCK_LEAVE, $listener); 53 | $parser->parse($this->getCode()); 54 | $tokens = $parser->getTokens(); 55 | 56 | // test: if (first one) 57 | $if = $blocks->get(0); 58 | $this->assertEquals(Block::TYPE_IF, $if->type); 59 | $this->assertEquals($tokens->get(1), $if->start, 'if start token'); 60 | $this->assertEquals($tokens->get(12), $if->end, 'if end token'); 61 | 62 | // test: for 63 | $for = $blocks->get(4); 64 | $this->assertEquals(Block::TYPE_FOR, $for->type); 65 | $this->assertEquals($tokens->get(20), $for->start, 'for start token'); 66 | $this->assertEquals($tokens->get(68), $for->end, 'for end token'); 67 | 68 | // test: do 69 | $do = $blocks->get(3); 70 | $this->assertEquals(Block::TYPE_DO, $do->type); 71 | $this->assertEquals($tokens->get(34), $do->start, 'do start token'); 72 | $this->assertEquals($tokens->get(67), $do->end, 'do end token'); 73 | } 74 | 75 | public function testBlockPositionsOnAbstractClass() { 76 | $blocks = new ArrayList(); 77 | $listener = function (BlockEvent $event) use ($blocks) { 78 | $blocks->add($event->getBlock()); 79 | }; 80 | 81 | $parser = new Parser(); 82 | $parser->getContext()->addListener(Context::EVENT_BLOCK_LEAVE, $listener); 83 | $parser->parse($this->getRawContent('abstract-class')); 84 | $tokens = $parser->getTokens(); 85 | 86 | // test: class 87 | $class = $blocks->get(3); 88 | $this->assertEquals(Block::TYPE_CLASS, $class->type); 89 | $this->assertEquals($tokens->get(8), $class->start); 90 | $this->assertEquals($tokens->get(59), $class->end); 91 | 92 | // test: static method 93 | $static = $blocks->get(1); 94 | $this->assertEquals(Block::TYPE_METHOD, $static->type); 95 | $this->assertEquals($tokens->get(34), $static->start); 96 | $this->assertEquals($tokens->get(46), $static->end); 97 | } 98 | 99 | public function testGroupStack() { 100 | $log = new ArrayList(); 101 | $listener = function (GroupEvent $event) use ($log) { 102 | $log->add($event->getName() . ' ' . $event->getGroup()->type); 103 | }; 104 | 105 | $parser = new Parser(); 106 | $parser->getContext()->addListener(Context::EVENT_GROUP_ENTER, $listener); 107 | $parser->getContext()->addListener(Context::EVENT_GROUP_LEAVE, $listener); 108 | $parser->parse($this->getCode()); 109 | 110 | $this->assertEquals([ 111 | // if 112 | 'context.group_enter block', 113 | 'context.group_leave block', 114 | // for 115 | 'context.group_enter block', 116 | 'context.group_leave block', 117 | // call 118 | 'context.group_enter call', 119 | 'context.group_leave call', 120 | // if 121 | 'context.group_enter block', 122 | 'context.group_leave block', 123 | // while 124 | 'context.group_enter block', 125 | // while -> group 126 | 'context.group_enter group', 127 | 'context.group_leave group', 128 | 'context.group_leave block', 129 | ], $log->toArray()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/token/TokenizerTest.php: -------------------------------------------------------------------------------- 1 | parse($this->getRawContent('sample1')); 15 | 16 | $tokens = $parser->getTokens(); 17 | $tracker = $parser->getTracker(); 18 | 19 | $firstIf = $tokens->get(1); 20 | $firstIfOpen = $tokens->get(2); 21 | 22 | $this->assertEquals('if', $firstIf->contents); 23 | $this->assertEquals('(', $firstIfOpen->contents); 24 | 25 | $tracker->visitToken($firstIfOpen); 26 | $this->assertEquals($firstIf, $tracker->getPrevToken()); 27 | 28 | $tracker->visitToken($firstIf); 29 | $this->assertEquals($firstIfOpen, $tracker->getNextToken()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/utils/SamplesTrait.php: -------------------------------------------------------------------------------- 1 | getContent('raw/' . $file); 14 | } 15 | 16 | protected function getDefaultContent($file) { 17 | return $this->getContent('default/' . $file); 18 | } 19 | 20 | protected function compareSample($sample) { 21 | $raw = $this->getRawContent($sample); 22 | $formatter = new Formatter(); 23 | 24 | // default coding style 25 | $code = $formatter->format($raw); 26 | // if ($sample == 'class') { 27 | // echo $code; 28 | // } 29 | $this->assertEquals($this->getDefaultContent($sample), $code); 30 | 31 | // psr2 coding style 32 | } 33 | } 34 | --------------------------------------------------------------------------------