├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src ├── Dispatcher.php ├── Indexer.php ├── NodeTraverser.php ├── NodeVisitor │ ├── ClassFinder.php │ ├── FileIdentifier.php │ ├── FunctionFinder.php │ ├── MethodFinder.php │ └── SymbolFinder.php ├── Parser.php └── SymbolTable.php ├── test └── TheForce │ └── SymbolTableTest.php └── theforce /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | sudo: false 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | before_script: 9 | - composer self-update 10 | - composer install --dev 11 | script: 12 | - ./vendor/bin/phpunit -c phpunit.xml.dist 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | [![Build Status](https://travis-ci.org/cweagans/theforce.svg?branch=master)](https://travis-ci.org/cweagans/theforce) 4 | [![Gratipay](https://img.shields.io/gratipay/cweagans.svg)](https://gratipay.com/cweagans/) 5 | 6 | The Force is an autocompletion tool for PHP. It sort of works right now, and it's 7 | not very fast. It's more of a work in progress. 8 | 9 | Eventually, I hope to get The Force to the point of feature parity with 10 | [Jedi](https://github.com/davidhalter/jedi), the autocompletion library for Python. 11 | 12 | I'll also be integrating this library with [ycmd](https://github.com/Valloric/ycmd) 13 | when it gets to the point of usefulness. 14 | 15 | # Architecture 16 | 17 | The Force is essentially a socket server that can be directed by JSON commands 18 | from a code editor. It behaves very similarly to [Omnisharp](http://www.omnisharp.net) 19 | in that regard. 20 | 21 | Code editors should be responsible for starting and stopping the server, as well 22 | as sending commands to it. Pretty much all of the command handling is built in 23 | the [Dispatcher](https://github.com/cweagans/theforce/master/src/Dispatcher.php) 24 | component, and it's all pretty straightforward. 25 | 26 | # Configuration 27 | 28 | If you have a project with more than one directory where you want the parser to 29 | discover PHP files, or you want to use a custom filemask to tell the parser what 30 | is PHP and what isn't, you'll need to create a config file. These config files 31 | are very straightforward, and can look something like this: 32 | 33 | paths[] = /Users/cweagans/Documents/Code/drupal 34 | paths[] = /Users/cweagans/Documents/Code/my-other-directory 35 | mask = "/^.+\.(php|inc|module|install)$/i" 36 | 37 | The default behavior is to look for anything ending in .php. The regex above 38 | should be appropriate for most Drupal installations, though some tweaking may be 39 | necessary for your specific project. Note that if you have spaces or any weird 40 | characters in your paths, you may need to wrap them in double quotes. 41 | 42 | # Contributing 43 | 44 | Pull requests, questions, comments, or suggestions are all very welcome. I want 45 | this library to be as awesome as possible, and your help could make it happen. 46 | 47 | In particular, if you're a Python developer, I'd love some help on integration 48 | with YCMD. 49 | 50 | Note that while the goal of this project is to provide fantastic PHP autocomplete 51 | functionality, I'm not at all interested in supporting plugins for individual 52 | editors. If you want code completion, you should integrate with YCMD instead, as 53 | that's the end-goal for this library as well. 54 | 55 | # Similar Projects 56 | 57 | [PHPCodeIntel](https://github.com/deweller/PHPCodeIntel) works in a pretty similar 58 | manner to this project. Unfortunately, there were a lot of design decisions in 59 | that project that I'm not a fan of, so I'm building the tool that I want. 60 | 61 | # Known issues 62 | 63 | * **Indexer performance**: For large projects, the indexer is **slow**. One approach 64 | to solving this may be to use pthreads if the extension is available, but my first 65 | priority is to actually get the library working. At that point, we can start thinking 66 | about performance improvements. 67 | * **Notices, warnings, and general lack of error checking**: Like I said, this is a 68 | work in progress. General hardening of the library will happen eventually. 69 | * **Complete lack of tests**: I know, I know. Should have written them first. 70 | * **Builtin symbols are ignored**: PHP ships with a ton of built-in symbols that 71 | this library doesn't currently know about. In the final 1.0.0 release, I'd like 72 | to ship with a couple of files that have function stubs and detailed docblocks 73 | for every built in PHP function and class (and their methods/properties). It 74 | shouldn't be too difficult to extract this information from the PHP docs. When 75 | that PHP file is generated, it should be as simple as always including it in 76 | the symbol table. 77 | 78 | # Roadmap 79 | 80 | These are the things I want to do, roughly in order of when I'm going to do them: 81 | 82 | * Ensure that all variables in the AST produced by the PHP Parser have info about 83 | the variable type. 84 | * Add scope-aware variable indexing 85 | * Start integration with YCMD 86 | * Add basic go-to-definition support 87 | * Add command to re-parse a file (editors will use this when a file is saved) 88 | * Add function autocompletion 89 | * Add class name autocompletion 90 | * Add class method autocompletion 91 | * Make class method autocompletion context aware 92 | * Outside callers should only be able to access public properties/methods 93 | * Subclasses should be able to access private/protected properties/methods 94 | 95 | # FAQ 96 | 97 | * Q. Does it work right now?
98 | A. Kind of. Code indexing mostly works, but you can't do anything useful with the data yet. 99 | 100 | * Q. Why did you call it "The Force"?
101 | A. Python has Jedi. PHP has "The Force". Also, you can answer "What do you use for 102 | code completion?" with "The Force". 103 | 104 | * Q. When will it be complete?
105 | A. When it's done. 106 | 107 | * Q. How can I help?
108 | A. See the "Contributing" section of this readme. If you need an idea for something 109 | to work on, see the "Known issues" section. 110 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cweagans/theforce", 3 | "description": "An autocomplete library for PHP.", 4 | "authors": [ 5 | { 6 | "name": "Cameron Eagans", 7 | "email": "me@cweagans.net" 8 | } 9 | ], 10 | "require": { 11 | "nikic/php-parser": "dev-errorRecovery2", 12 | "phpdocumentor/reflection-docblock": "2.0.4", 13 | "react/socket": "0.4.*@dev" 14 | }, 15 | "bin": [ 16 | "theforce" 17 | ], 18 | "require-dev": { 19 | "phpunit/phpunit": "4.5.0" 20 | }, 21 | "autoload": { 22 | "psr-4": {"cweagans\\TheForce\\": "src"} 23 | }, 24 | "autoload-dev": { 25 | "psr-4": {"cweagans\\TheForce\\Tests\\": "test"} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "4ecef237e6552b2bb56ff4449ec7b86f", 8 | "packages": [ 9 | { 10 | "name": "evenement/evenement", 11 | "version": "v2.0.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/igorw/evenement.git", 15 | "reference": "f6e843799fd4f4184d54d8fc7b5b3551c9fa803e" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/igorw/evenement/zipball/f6e843799fd4f4184d54d8fc7b5b3551c9fa803e", 20 | "reference": "f6e843799fd4f4184d54d8fc7b5b3551c9fa803e", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.4.0" 25 | }, 26 | "type": "library", 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "2.0-dev" 30 | } 31 | }, 32 | "autoload": { 33 | "psr-0": { 34 | "Evenement": "src" 35 | } 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "Igor Wiedler", 44 | "email": "igor@wiedler.ch", 45 | "homepage": "http://wiedler.ch/igor/" 46 | } 47 | ], 48 | "description": "Événement is a very simple event dispatching library for PHP", 49 | "keywords": [ 50 | "event-dispatcher", 51 | "event-emitter" 52 | ], 53 | "time": "2012-11-02 14:49:47" 54 | }, 55 | { 56 | "name": "nikic/php-parser", 57 | "version": "dev-errorRecovery2", 58 | "source": { 59 | "type": "git", 60 | "url": "https://github.com/nikic/PHP-Parser.git", 61 | "reference": "3c0c2380478596636f77bb5481e8322906554141" 62 | }, 63 | "dist": { 64 | "type": "zip", 65 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3c0c2380478596636f77bb5481e8322906554141", 66 | "reference": "3c0c2380478596636f77bb5481e8322906554141", 67 | "shasum": "" 68 | }, 69 | "require": { 70 | "ext-tokenizer": "*", 71 | "php": ">=5.3" 72 | }, 73 | "type": "library", 74 | "extra": { 75 | "branch-alias": { 76 | "dev-master": "1.1-dev" 77 | } 78 | }, 79 | "autoload": { 80 | "files": [ 81 | "lib/bootstrap.php" 82 | ] 83 | }, 84 | "notification-url": "https://packagist.org/downloads/", 85 | "license": [ 86 | "BSD-3-Clause" 87 | ], 88 | "authors": [ 89 | { 90 | "name": "Nikita Popov" 91 | } 92 | ], 93 | "description": "A PHP parser written in PHP", 94 | "keywords": [ 95 | "parser", 96 | "php" 97 | ], 98 | "time": "2015-02-04 16:26:36" 99 | }, 100 | { 101 | "name": "phpdocumentor/reflection-docblock", 102 | "version": "2.0.4", 103 | "source": { 104 | "type": "git", 105 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 106 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" 107 | }, 108 | "dist": { 109 | "type": "zip", 110 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", 111 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", 112 | "shasum": "" 113 | }, 114 | "require": { 115 | "php": ">=5.3.3" 116 | }, 117 | "require-dev": { 118 | "phpunit/phpunit": "~4.0" 119 | }, 120 | "suggest": { 121 | "dflydev/markdown": "~1.0", 122 | "erusev/parsedown": "~1.0" 123 | }, 124 | "type": "library", 125 | "extra": { 126 | "branch-alias": { 127 | "dev-master": "2.0.x-dev" 128 | } 129 | }, 130 | "autoload": { 131 | "psr-0": { 132 | "phpDocumentor": [ 133 | "src/" 134 | ] 135 | } 136 | }, 137 | "notification-url": "https://packagist.org/downloads/", 138 | "license": [ 139 | "MIT" 140 | ], 141 | "authors": [ 142 | { 143 | "name": "Mike van Riel", 144 | "email": "mike.vanriel@naenius.com" 145 | } 146 | ], 147 | "time": "2015-02-03 12:10:50" 148 | }, 149 | { 150 | "name": "react/event-loop", 151 | "version": "v0.4.1", 152 | "source": { 153 | "type": "git", 154 | "url": "https://github.com/reactphp/event-loop.git", 155 | "reference": "18c5297087ca01de85518e2b55078f444144aa1b" 156 | }, 157 | "dist": { 158 | "type": "zip", 159 | "url": "https://api.github.com/repos/reactphp/event-loop/zipball/18c5297087ca01de85518e2b55078f444144aa1b", 160 | "reference": "18c5297087ca01de85518e2b55078f444144aa1b", 161 | "shasum": "" 162 | }, 163 | "require": { 164 | "php": ">=5.4.0" 165 | }, 166 | "suggest": { 167 | "ext-event": "~1.0", 168 | "ext-libev": "*", 169 | "ext-libevent": ">=0.1.0" 170 | }, 171 | "type": "library", 172 | "extra": { 173 | "branch-alias": { 174 | "dev-master": "0.4-dev" 175 | } 176 | }, 177 | "autoload": { 178 | "psr-4": { 179 | "React\\EventLoop\\": "" 180 | } 181 | }, 182 | "notification-url": "https://packagist.org/downloads/", 183 | "license": [ 184 | "MIT" 185 | ], 186 | "description": "Event loop abstraction layer that libraries can use for evented I/O.", 187 | "keywords": [ 188 | "event-loop" 189 | ], 190 | "time": "2014-02-26 17:36:58" 191 | }, 192 | { 193 | "name": "react/socket", 194 | "version": "dev-master", 195 | "source": { 196 | "type": "git", 197 | "url": "https://github.com/reactphp/socket.git", 198 | "reference": "2949cf3673480fd968db22aa3ba1995d9f6ef936" 199 | }, 200 | "dist": { 201 | "type": "zip", 202 | "url": "https://api.github.com/repos/reactphp/socket/zipball/2949cf3673480fd968db22aa3ba1995d9f6ef936", 203 | "reference": "2949cf3673480fd968db22aa3ba1995d9f6ef936", 204 | "shasum": "" 205 | }, 206 | "require": { 207 | "evenement/evenement": "~2.0", 208 | "php": ">=5.4.0", 209 | "react/event-loop": "0.4.*", 210 | "react/stream": "0.4.*" 211 | }, 212 | "type": "library", 213 | "extra": { 214 | "branch-alias": { 215 | "dev-master": "0.4-dev" 216 | } 217 | }, 218 | "autoload": { 219 | "psr-4": { 220 | "React\\Socket\\": "src" 221 | } 222 | }, 223 | "notification-url": "https://packagist.org/downloads/", 224 | "license": [ 225 | "MIT" 226 | ], 227 | "description": "Library for building an evented socket server.", 228 | "keywords": [ 229 | "Socket" 230 | ], 231 | "time": "2014-12-04 14:13:22" 232 | }, 233 | { 234 | "name": "react/stream", 235 | "version": "v0.4.2", 236 | "source": { 237 | "type": "git", 238 | "url": "https://github.com/reactphp/stream.git", 239 | "reference": "acc7a5fec02e0aea674560e1d13c40ed0c8c5465" 240 | }, 241 | "dist": { 242 | "type": "zip", 243 | "url": "https://api.github.com/repos/reactphp/stream/zipball/acc7a5fec02e0aea674560e1d13c40ed0c8c5465", 244 | "reference": "acc7a5fec02e0aea674560e1d13c40ed0c8c5465", 245 | "shasum": "" 246 | }, 247 | "require": { 248 | "evenement/evenement": "~2.0", 249 | "php": ">=5.4.0" 250 | }, 251 | "require-dev": { 252 | "react/event-loop": "0.4.*", 253 | "react/promise": "~2.0" 254 | }, 255 | "suggest": { 256 | "react/event-loop": "0.4.*", 257 | "react/promise": "~2.0" 258 | }, 259 | "type": "library", 260 | "extra": { 261 | "branch-alias": { 262 | "dev-master": "0.5-dev" 263 | } 264 | }, 265 | "autoload": { 266 | "psr-4": { 267 | "React\\Stream\\": "src" 268 | } 269 | }, 270 | "notification-url": "https://packagist.org/downloads/", 271 | "license": [ 272 | "MIT" 273 | ], 274 | "description": "Basic readable and writable stream interfaces that support piping.", 275 | "keywords": [ 276 | "pipe", 277 | "stream" 278 | ], 279 | "time": "2014-09-10 03:32:31" 280 | } 281 | ], 282 | "packages-dev": [ 283 | { 284 | "name": "doctrine/instantiator", 285 | "version": "1.0.4", 286 | "source": { 287 | "type": "git", 288 | "url": "https://github.com/doctrine/instantiator.git", 289 | "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119" 290 | }, 291 | "dist": { 292 | "type": "zip", 293 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119", 294 | "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119", 295 | "shasum": "" 296 | }, 297 | "require": { 298 | "php": ">=5.3,<8.0-DEV" 299 | }, 300 | "require-dev": { 301 | "athletic/athletic": "~0.1.8", 302 | "ext-pdo": "*", 303 | "ext-phar": "*", 304 | "phpunit/phpunit": "~4.0", 305 | "squizlabs/php_codesniffer": "2.0.*@ALPHA" 306 | }, 307 | "type": "library", 308 | "extra": { 309 | "branch-alias": { 310 | "dev-master": "1.0.x-dev" 311 | } 312 | }, 313 | "autoload": { 314 | "psr-0": { 315 | "Doctrine\\Instantiator\\": "src" 316 | } 317 | }, 318 | "notification-url": "https://packagist.org/downloads/", 319 | "license": [ 320 | "MIT" 321 | ], 322 | "authors": [ 323 | { 324 | "name": "Marco Pivetta", 325 | "email": "ocramius@gmail.com", 326 | "homepage": "http://ocramius.github.com/" 327 | } 328 | ], 329 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 330 | "homepage": "https://github.com/doctrine/instantiator", 331 | "keywords": [ 332 | "constructor", 333 | "instantiate" 334 | ], 335 | "time": "2014-10-13 12:58:55" 336 | }, 337 | { 338 | "name": "phpspec/prophecy", 339 | "version": "v1.3.1", 340 | "source": { 341 | "type": "git", 342 | "url": "https://github.com/phpspec/prophecy.git", 343 | "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9" 344 | }, 345 | "dist": { 346 | "type": "zip", 347 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9ca52329bcdd1500de24427542577ebf3fc2f1c9", 348 | "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9", 349 | "shasum": "" 350 | }, 351 | "require": { 352 | "doctrine/instantiator": "~1.0,>=1.0.2", 353 | "phpdocumentor/reflection-docblock": "~2.0" 354 | }, 355 | "require-dev": { 356 | "phpspec/phpspec": "~2.0" 357 | }, 358 | "type": "library", 359 | "extra": { 360 | "branch-alias": { 361 | "dev-master": "1.2.x-dev" 362 | } 363 | }, 364 | "autoload": { 365 | "psr-0": { 366 | "Prophecy\\": "src/" 367 | } 368 | }, 369 | "notification-url": "https://packagist.org/downloads/", 370 | "license": [ 371 | "MIT" 372 | ], 373 | "authors": [ 374 | { 375 | "name": "Konstantin Kudryashov", 376 | "email": "ever.zet@gmail.com", 377 | "homepage": "http://everzet.com" 378 | }, 379 | { 380 | "name": "Marcello Duarte", 381 | "email": "marcello.duarte@gmail.com" 382 | } 383 | ], 384 | "description": "Highly opinionated mocking framework for PHP 5.3+", 385 | "homepage": "http://phpspec.org", 386 | "keywords": [ 387 | "Double", 388 | "Dummy", 389 | "fake", 390 | "mock", 391 | "spy", 392 | "stub" 393 | ], 394 | "time": "2014-11-17 16:23:49" 395 | }, 396 | { 397 | "name": "phpunit/php-code-coverage", 398 | "version": "2.0.15", 399 | "source": { 400 | "type": "git", 401 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 402 | "reference": "34cc484af1ca149188d0d9e91412191e398e0b67" 403 | }, 404 | "dist": { 405 | "type": "zip", 406 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/34cc484af1ca149188d0d9e91412191e398e0b67", 407 | "reference": "34cc484af1ca149188d0d9e91412191e398e0b67", 408 | "shasum": "" 409 | }, 410 | "require": { 411 | "php": ">=5.3.3", 412 | "phpunit/php-file-iterator": "~1.3", 413 | "phpunit/php-text-template": "~1.2", 414 | "phpunit/php-token-stream": "~1.3", 415 | "sebastian/environment": "~1.0", 416 | "sebastian/version": "~1.0" 417 | }, 418 | "require-dev": { 419 | "ext-xdebug": ">=2.1.4", 420 | "phpunit/phpunit": "~4" 421 | }, 422 | "suggest": { 423 | "ext-dom": "*", 424 | "ext-xdebug": ">=2.2.1", 425 | "ext-xmlwriter": "*" 426 | }, 427 | "type": "library", 428 | "extra": { 429 | "branch-alias": { 430 | "dev-master": "2.0.x-dev" 431 | } 432 | }, 433 | "autoload": { 434 | "classmap": [ 435 | "src/" 436 | ] 437 | }, 438 | "notification-url": "https://packagist.org/downloads/", 439 | "license": [ 440 | "BSD-3-Clause" 441 | ], 442 | "authors": [ 443 | { 444 | "name": "Sebastian Bergmann", 445 | "email": "sb@sebastian-bergmann.de", 446 | "role": "lead" 447 | } 448 | ], 449 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 450 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 451 | "keywords": [ 452 | "coverage", 453 | "testing", 454 | "xunit" 455 | ], 456 | "time": "2015-01-24 10:06:35" 457 | }, 458 | { 459 | "name": "phpunit/php-file-iterator", 460 | "version": "1.3.4", 461 | "source": { 462 | "type": "git", 463 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 464 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 465 | }, 466 | "dist": { 467 | "type": "zip", 468 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 469 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 470 | "shasum": "" 471 | }, 472 | "require": { 473 | "php": ">=5.3.3" 474 | }, 475 | "type": "library", 476 | "autoload": { 477 | "classmap": [ 478 | "File/" 479 | ] 480 | }, 481 | "notification-url": "https://packagist.org/downloads/", 482 | "include-path": [ 483 | "" 484 | ], 485 | "license": [ 486 | "BSD-3-Clause" 487 | ], 488 | "authors": [ 489 | { 490 | "name": "Sebastian Bergmann", 491 | "email": "sb@sebastian-bergmann.de", 492 | "role": "lead" 493 | } 494 | ], 495 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 496 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 497 | "keywords": [ 498 | "filesystem", 499 | "iterator" 500 | ], 501 | "time": "2013-10-10 15:34:57" 502 | }, 503 | { 504 | "name": "phpunit/php-text-template", 505 | "version": "1.2.0", 506 | "source": { 507 | "type": "git", 508 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 509 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" 510 | }, 511 | "dist": { 512 | "type": "zip", 513 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 514 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 515 | "shasum": "" 516 | }, 517 | "require": { 518 | "php": ">=5.3.3" 519 | }, 520 | "type": "library", 521 | "autoload": { 522 | "classmap": [ 523 | "Text/" 524 | ] 525 | }, 526 | "notification-url": "https://packagist.org/downloads/", 527 | "include-path": [ 528 | "" 529 | ], 530 | "license": [ 531 | "BSD-3-Clause" 532 | ], 533 | "authors": [ 534 | { 535 | "name": "Sebastian Bergmann", 536 | "email": "sb@sebastian-bergmann.de", 537 | "role": "lead" 538 | } 539 | ], 540 | "description": "Simple template engine.", 541 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 542 | "keywords": [ 543 | "template" 544 | ], 545 | "time": "2014-01-30 17:20:04" 546 | }, 547 | { 548 | "name": "phpunit/php-timer", 549 | "version": "1.0.5", 550 | "source": { 551 | "type": "git", 552 | "url": "https://github.com/sebastianbergmann/php-timer.git", 553 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" 554 | }, 555 | "dist": { 556 | "type": "zip", 557 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 558 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 559 | "shasum": "" 560 | }, 561 | "require": { 562 | "php": ">=5.3.3" 563 | }, 564 | "type": "library", 565 | "autoload": { 566 | "classmap": [ 567 | "PHP/" 568 | ] 569 | }, 570 | "notification-url": "https://packagist.org/downloads/", 571 | "include-path": [ 572 | "" 573 | ], 574 | "license": [ 575 | "BSD-3-Clause" 576 | ], 577 | "authors": [ 578 | { 579 | "name": "Sebastian Bergmann", 580 | "email": "sb@sebastian-bergmann.de", 581 | "role": "lead" 582 | } 583 | ], 584 | "description": "Utility class for timing", 585 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 586 | "keywords": [ 587 | "timer" 588 | ], 589 | "time": "2013-08-02 07:42:54" 590 | }, 591 | { 592 | "name": "phpunit/php-token-stream", 593 | "version": "1.4.0", 594 | "source": { 595 | "type": "git", 596 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 597 | "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74" 598 | }, 599 | "dist": { 600 | "type": "zip", 601 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db32c18eba00b121c145575fcbcd4d4d24e6db74", 602 | "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74", 603 | "shasum": "" 604 | }, 605 | "require": { 606 | "ext-tokenizer": "*", 607 | "php": ">=5.3.3" 608 | }, 609 | "require-dev": { 610 | "phpunit/phpunit": "~4.2" 611 | }, 612 | "type": "library", 613 | "extra": { 614 | "branch-alias": { 615 | "dev-master": "1.4-dev" 616 | } 617 | }, 618 | "autoload": { 619 | "classmap": [ 620 | "src/" 621 | ] 622 | }, 623 | "notification-url": "https://packagist.org/downloads/", 624 | "license": [ 625 | "BSD-3-Clause" 626 | ], 627 | "authors": [ 628 | { 629 | "name": "Sebastian Bergmann", 630 | "email": "sebastian@phpunit.de" 631 | } 632 | ], 633 | "description": "Wrapper around PHP's tokenizer extension.", 634 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 635 | "keywords": [ 636 | "tokenizer" 637 | ], 638 | "time": "2015-01-17 09:51:32" 639 | }, 640 | { 641 | "name": "phpunit/phpunit", 642 | "version": "4.5.0", 643 | "source": { 644 | "type": "git", 645 | "url": "https://github.com/sebastianbergmann/phpunit.git", 646 | "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5" 647 | }, 648 | "dist": { 649 | "type": "zip", 650 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5b578d3865a9128b9c209b011fda6539ec06e7a5", 651 | "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5", 652 | "shasum": "" 653 | }, 654 | "require": { 655 | "ext-dom": "*", 656 | "ext-json": "*", 657 | "ext-pcre": "*", 658 | "ext-reflection": "*", 659 | "ext-spl": "*", 660 | "php": ">=5.3.3", 661 | "phpspec/prophecy": "~1.3.1", 662 | "phpunit/php-code-coverage": "~2.0", 663 | "phpunit/php-file-iterator": "~1.3.2", 664 | "phpunit/php-text-template": "~1.2", 665 | "phpunit/php-timer": "~1.0.2", 666 | "phpunit/phpunit-mock-objects": "~2.3", 667 | "sebastian/comparator": "~1.1", 668 | "sebastian/diff": "~1.1", 669 | "sebastian/environment": "~1.2", 670 | "sebastian/exporter": "~1.2", 671 | "sebastian/global-state": "~1.0", 672 | "sebastian/version": "~1.0", 673 | "symfony/yaml": "~2.0" 674 | }, 675 | "suggest": { 676 | "phpunit/php-invoker": "~1.1" 677 | }, 678 | "bin": [ 679 | "phpunit" 680 | ], 681 | "type": "library", 682 | "extra": { 683 | "branch-alias": { 684 | "dev-master": "4.5.x-dev" 685 | } 686 | }, 687 | "autoload": { 688 | "classmap": [ 689 | "src/" 690 | ] 691 | }, 692 | "notification-url": "https://packagist.org/downloads/", 693 | "license": [ 694 | "BSD-3-Clause" 695 | ], 696 | "authors": [ 697 | { 698 | "name": "Sebastian Bergmann", 699 | "email": "sebastian@phpunit.de", 700 | "role": "lead" 701 | } 702 | ], 703 | "description": "The PHP Unit Testing framework.", 704 | "homepage": "https://phpunit.de/", 705 | "keywords": [ 706 | "phpunit", 707 | "testing", 708 | "xunit" 709 | ], 710 | "time": "2015-02-05 15:51:19" 711 | }, 712 | { 713 | "name": "phpunit/phpunit-mock-objects", 714 | "version": "2.3.0", 715 | "source": { 716 | "type": "git", 717 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 718 | "reference": "c63d2367247365f688544f0d500af90a11a44c65" 719 | }, 720 | "dist": { 721 | "type": "zip", 722 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/c63d2367247365f688544f0d500af90a11a44c65", 723 | "reference": "c63d2367247365f688544f0d500af90a11a44c65", 724 | "shasum": "" 725 | }, 726 | "require": { 727 | "doctrine/instantiator": "~1.0,>=1.0.1", 728 | "php": ">=5.3.3", 729 | "phpunit/php-text-template": "~1.2" 730 | }, 731 | "require-dev": { 732 | "phpunit/phpunit": "~4.3" 733 | }, 734 | "suggest": { 735 | "ext-soap": "*" 736 | }, 737 | "type": "library", 738 | "extra": { 739 | "branch-alias": { 740 | "dev-master": "2.3.x-dev" 741 | } 742 | }, 743 | "autoload": { 744 | "classmap": [ 745 | "src/" 746 | ] 747 | }, 748 | "notification-url": "https://packagist.org/downloads/", 749 | "license": [ 750 | "BSD-3-Clause" 751 | ], 752 | "authors": [ 753 | { 754 | "name": "Sebastian Bergmann", 755 | "email": "sb@sebastian-bergmann.de", 756 | "role": "lead" 757 | } 758 | ], 759 | "description": "Mock Object library for PHPUnit", 760 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 761 | "keywords": [ 762 | "mock", 763 | "xunit" 764 | ], 765 | "time": "2014-10-03 05:12:11" 766 | }, 767 | { 768 | "name": "sebastian/comparator", 769 | "version": "1.1.1", 770 | "source": { 771 | "type": "git", 772 | "url": "https://github.com/sebastianbergmann/comparator.git", 773 | "reference": "1dd8869519a225f7f2b9eb663e225298fade819e" 774 | }, 775 | "dist": { 776 | "type": "zip", 777 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e", 778 | "reference": "1dd8869519a225f7f2b9eb663e225298fade819e", 779 | "shasum": "" 780 | }, 781 | "require": { 782 | "php": ">=5.3.3", 783 | "sebastian/diff": "~1.2", 784 | "sebastian/exporter": "~1.2" 785 | }, 786 | "require-dev": { 787 | "phpunit/phpunit": "~4.4" 788 | }, 789 | "type": "library", 790 | "extra": { 791 | "branch-alias": { 792 | "dev-master": "1.1.x-dev" 793 | } 794 | }, 795 | "autoload": { 796 | "classmap": [ 797 | "src/" 798 | ] 799 | }, 800 | "notification-url": "https://packagist.org/downloads/", 801 | "license": [ 802 | "BSD-3-Clause" 803 | ], 804 | "authors": [ 805 | { 806 | "name": "Jeff Welch", 807 | "email": "whatthejeff@gmail.com" 808 | }, 809 | { 810 | "name": "Volker Dusch", 811 | "email": "github@wallbash.com" 812 | }, 813 | { 814 | "name": "Bernhard Schussek", 815 | "email": "bschussek@2bepublished.at" 816 | }, 817 | { 818 | "name": "Sebastian Bergmann", 819 | "email": "sebastian@phpunit.de" 820 | } 821 | ], 822 | "description": "Provides the functionality to compare PHP values for equality", 823 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 824 | "keywords": [ 825 | "comparator", 826 | "compare", 827 | "equality" 828 | ], 829 | "time": "2015-01-29 16:28:08" 830 | }, 831 | { 832 | "name": "sebastian/diff", 833 | "version": "1.2.0", 834 | "source": { 835 | "type": "git", 836 | "url": "https://github.com/sebastianbergmann/diff.git", 837 | "reference": "5843509fed39dee4b356a306401e9dd1a931fec7" 838 | }, 839 | "dist": { 840 | "type": "zip", 841 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/5843509fed39dee4b356a306401e9dd1a931fec7", 842 | "reference": "5843509fed39dee4b356a306401e9dd1a931fec7", 843 | "shasum": "" 844 | }, 845 | "require": { 846 | "php": ">=5.3.3" 847 | }, 848 | "require-dev": { 849 | "phpunit/phpunit": "~4.2" 850 | }, 851 | "type": "library", 852 | "extra": { 853 | "branch-alias": { 854 | "dev-master": "1.2-dev" 855 | } 856 | }, 857 | "autoload": { 858 | "classmap": [ 859 | "src/" 860 | ] 861 | }, 862 | "notification-url": "https://packagist.org/downloads/", 863 | "license": [ 864 | "BSD-3-Clause" 865 | ], 866 | "authors": [ 867 | { 868 | "name": "Kore Nordmann", 869 | "email": "mail@kore-nordmann.de" 870 | }, 871 | { 872 | "name": "Sebastian Bergmann", 873 | "email": "sebastian@phpunit.de" 874 | } 875 | ], 876 | "description": "Diff implementation", 877 | "homepage": "http://www.github.com/sebastianbergmann/diff", 878 | "keywords": [ 879 | "diff" 880 | ], 881 | "time": "2014-08-15 10:29:00" 882 | }, 883 | { 884 | "name": "sebastian/environment", 885 | "version": "1.2.1", 886 | "source": { 887 | "type": "git", 888 | "url": "https://github.com/sebastianbergmann/environment.git", 889 | "reference": "6e6c71d918088c251b181ba8b3088af4ac336dd7" 890 | }, 891 | "dist": { 892 | "type": "zip", 893 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e6c71d918088c251b181ba8b3088af4ac336dd7", 894 | "reference": "6e6c71d918088c251b181ba8b3088af4ac336dd7", 895 | "shasum": "" 896 | }, 897 | "require": { 898 | "php": ">=5.3.3" 899 | }, 900 | "require-dev": { 901 | "phpunit/phpunit": "~4.3" 902 | }, 903 | "type": "library", 904 | "extra": { 905 | "branch-alias": { 906 | "dev-master": "1.2.x-dev" 907 | } 908 | }, 909 | "autoload": { 910 | "classmap": [ 911 | "src/" 912 | ] 913 | }, 914 | "notification-url": "https://packagist.org/downloads/", 915 | "license": [ 916 | "BSD-3-Clause" 917 | ], 918 | "authors": [ 919 | { 920 | "name": "Sebastian Bergmann", 921 | "email": "sebastian@phpunit.de" 922 | } 923 | ], 924 | "description": "Provides functionality to handle HHVM/PHP environments", 925 | "homepage": "http://www.github.com/sebastianbergmann/environment", 926 | "keywords": [ 927 | "Xdebug", 928 | "environment", 929 | "hhvm" 930 | ], 931 | "time": "2014-10-25 08:00:45" 932 | }, 933 | { 934 | "name": "sebastian/exporter", 935 | "version": "1.2.0", 936 | "source": { 937 | "type": "git", 938 | "url": "https://github.com/sebastianbergmann/exporter.git", 939 | "reference": "84839970d05254c73cde183a721c7af13aede943" 940 | }, 941 | "dist": { 942 | "type": "zip", 943 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943", 944 | "reference": "84839970d05254c73cde183a721c7af13aede943", 945 | "shasum": "" 946 | }, 947 | "require": { 948 | "php": ">=5.3.3", 949 | "sebastian/recursion-context": "~1.0" 950 | }, 951 | "require-dev": { 952 | "phpunit/phpunit": "~4.4" 953 | }, 954 | "type": "library", 955 | "extra": { 956 | "branch-alias": { 957 | "dev-master": "1.2.x-dev" 958 | } 959 | }, 960 | "autoload": { 961 | "classmap": [ 962 | "src/" 963 | ] 964 | }, 965 | "notification-url": "https://packagist.org/downloads/", 966 | "license": [ 967 | "BSD-3-Clause" 968 | ], 969 | "authors": [ 970 | { 971 | "name": "Jeff Welch", 972 | "email": "whatthejeff@gmail.com" 973 | }, 974 | { 975 | "name": "Volker Dusch", 976 | "email": "github@wallbash.com" 977 | }, 978 | { 979 | "name": "Bernhard Schussek", 980 | "email": "bschussek@2bepublished.at" 981 | }, 982 | { 983 | "name": "Sebastian Bergmann", 984 | "email": "sebastian@phpunit.de" 985 | }, 986 | { 987 | "name": "Adam Harvey", 988 | "email": "aharvey@php.net" 989 | } 990 | ], 991 | "description": "Provides the functionality to export PHP variables for visualization", 992 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 993 | "keywords": [ 994 | "export", 995 | "exporter" 996 | ], 997 | "time": "2015-01-27 07:23:06" 998 | }, 999 | { 1000 | "name": "sebastian/global-state", 1001 | "version": "1.0.0", 1002 | "source": { 1003 | "type": "git", 1004 | "url": "https://github.com/sebastianbergmann/global-state.git", 1005 | "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" 1006 | }, 1007 | "dist": { 1008 | "type": "zip", 1009 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", 1010 | "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", 1011 | "shasum": "" 1012 | }, 1013 | "require": { 1014 | "php": ">=5.3.3" 1015 | }, 1016 | "require-dev": { 1017 | "phpunit/phpunit": "~4.2" 1018 | }, 1019 | "suggest": { 1020 | "ext-uopz": "*" 1021 | }, 1022 | "type": "library", 1023 | "extra": { 1024 | "branch-alias": { 1025 | "dev-master": "1.0-dev" 1026 | } 1027 | }, 1028 | "autoload": { 1029 | "classmap": [ 1030 | "src/" 1031 | ] 1032 | }, 1033 | "notification-url": "https://packagist.org/downloads/", 1034 | "license": [ 1035 | "BSD-3-Clause" 1036 | ], 1037 | "authors": [ 1038 | { 1039 | "name": "Sebastian Bergmann", 1040 | "email": "sebastian@phpunit.de" 1041 | } 1042 | ], 1043 | "description": "Snapshotting of global state", 1044 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1045 | "keywords": [ 1046 | "global state" 1047 | ], 1048 | "time": "2014-10-06 09:23:50" 1049 | }, 1050 | { 1051 | "name": "sebastian/recursion-context", 1052 | "version": "1.0.0", 1053 | "source": { 1054 | "type": "git", 1055 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1056 | "reference": "3989662bbb30a29d20d9faa04a846af79b276252" 1057 | }, 1058 | "dist": { 1059 | "type": "zip", 1060 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252", 1061 | "reference": "3989662bbb30a29d20d9faa04a846af79b276252", 1062 | "shasum": "" 1063 | }, 1064 | "require": { 1065 | "php": ">=5.3.3" 1066 | }, 1067 | "require-dev": { 1068 | "phpunit/phpunit": "~4.4" 1069 | }, 1070 | "type": "library", 1071 | "extra": { 1072 | "branch-alias": { 1073 | "dev-master": "1.0.x-dev" 1074 | } 1075 | }, 1076 | "autoload": { 1077 | "classmap": [ 1078 | "src/" 1079 | ] 1080 | }, 1081 | "notification-url": "https://packagist.org/downloads/", 1082 | "license": [ 1083 | "BSD-3-Clause" 1084 | ], 1085 | "authors": [ 1086 | { 1087 | "name": "Jeff Welch", 1088 | "email": "whatthejeff@gmail.com" 1089 | }, 1090 | { 1091 | "name": "Sebastian Bergmann", 1092 | "email": "sebastian@phpunit.de" 1093 | }, 1094 | { 1095 | "name": "Adam Harvey", 1096 | "email": "aharvey@php.net" 1097 | } 1098 | ], 1099 | "description": "Provides functionality to recursively process PHP variables", 1100 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1101 | "time": "2015-01-24 09:48:32" 1102 | }, 1103 | { 1104 | "name": "sebastian/version", 1105 | "version": "1.0.4", 1106 | "source": { 1107 | "type": "git", 1108 | "url": "https://github.com/sebastianbergmann/version.git", 1109 | "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b" 1110 | }, 1111 | "dist": { 1112 | "type": "zip", 1113 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/a77d9123f8e809db3fbdea15038c27a95da4058b", 1114 | "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b", 1115 | "shasum": "" 1116 | }, 1117 | "type": "library", 1118 | "autoload": { 1119 | "classmap": [ 1120 | "src/" 1121 | ] 1122 | }, 1123 | "notification-url": "https://packagist.org/downloads/", 1124 | "license": [ 1125 | "BSD-3-Clause" 1126 | ], 1127 | "authors": [ 1128 | { 1129 | "name": "Sebastian Bergmann", 1130 | "email": "sebastian@phpunit.de", 1131 | "role": "lead" 1132 | } 1133 | ], 1134 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1135 | "homepage": "https://github.com/sebastianbergmann/version", 1136 | "time": "2014-12-15 14:25:24" 1137 | }, 1138 | { 1139 | "name": "symfony/yaml", 1140 | "version": "v2.6.4", 1141 | "target-dir": "Symfony/Component/Yaml", 1142 | "source": { 1143 | "type": "git", 1144 | "url": "https://github.com/symfony/Yaml.git", 1145 | "reference": "60ed7751671113cf1ee7d7778e691642c2e9acd8" 1146 | }, 1147 | "dist": { 1148 | "type": "zip", 1149 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/60ed7751671113cf1ee7d7778e691642c2e9acd8", 1150 | "reference": "60ed7751671113cf1ee7d7778e691642c2e9acd8", 1151 | "shasum": "" 1152 | }, 1153 | "require": { 1154 | "php": ">=5.3.3" 1155 | }, 1156 | "type": "library", 1157 | "extra": { 1158 | "branch-alias": { 1159 | "dev-master": "2.6-dev" 1160 | } 1161 | }, 1162 | "autoload": { 1163 | "psr-0": { 1164 | "Symfony\\Component\\Yaml\\": "" 1165 | } 1166 | }, 1167 | "notification-url": "https://packagist.org/downloads/", 1168 | "license": [ 1169 | "MIT" 1170 | ], 1171 | "authors": [ 1172 | { 1173 | "name": "Symfony Community", 1174 | "homepage": "http://symfony.com/contributors" 1175 | }, 1176 | { 1177 | "name": "Fabien Potencier", 1178 | "email": "fabien@symfony.com" 1179 | } 1180 | ], 1181 | "description": "Symfony Yaml Component", 1182 | "homepage": "http://symfony.com", 1183 | "time": "2015-01-25 04:39:26" 1184 | } 1185 | ], 1186 | "aliases": [], 1187 | "minimum-stability": "stable", 1188 | "stability-flags": { 1189 | "nikic/php-parser": 20, 1190 | "react/socket": 20 1191 | }, 1192 | "prefer-stable": false, 1193 | "platform": [], 1194 | "platform-dev": [] 1195 | } 1196 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test/TheForce 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Dispatcher.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 15 | $this->connection->write("The Force (PHP Autocompletion Daemon) v1.0.0!\n"); 16 | $this->connection->write("Ready\n"); 17 | } 18 | 19 | public function dispatch($input) { 20 | $input = json_decode($input); 21 | if (method_exists($this, $input->cmd)) { 22 | if (property_exists($input, "data")) { 23 | call_user_func(array($this, $input->cmd), $input->data); 24 | } 25 | else { 26 | call_user_func(array($this, $input->cmd)); 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * Set the directory to index. 33 | * 34 | * Example: 35 | * {"cmd": "setDirectory", "data": {"path": "/path/to/your/project"}} 36 | */ 37 | public function setDirectory($data) { 38 | $this->indexer = new Indexer(array($data->path)); 39 | $this->indexer->index(function($file, $current, $total) { 40 | echo("($current/$total) Processing $file\n"); 41 | }); 42 | } 43 | 44 | /** 45 | * Use a config file for the indexer. 46 | * 47 | * Example: 48 | * {"cmd": "setConfig", "data": {"path": "/path/to/your/config.ini"}} 49 | */ 50 | public function setConfig($data) { 51 | $config = parse_ini_file($data->path); 52 | $this->indexer = new Indexer($config['paths'], $config['mask']); 53 | $this->indexer->index(function($file, $current, $total) { 54 | echo("($current/$total) Processing $file\n"); 55 | }); 56 | } 57 | 58 | /** 59 | * Get function completions from SymbolTable. 60 | * 61 | * Example: 62 | * {"cmd": "getFunctions", "data": {"prefix": "drupal_ge"}} 63 | */ 64 | public function getFunctions($data) { 65 | if (isset($data->prefix)) { 66 | $this->connection->write(json_encode(SymbolTable::getInstance()->getFunctions($data->prefix))); 67 | return; 68 | } 69 | $this->connection->write(json_encode(SymbolTable::getInstance()->getFunctions())); 70 | } 71 | 72 | /** 73 | * Get information for a specific function. 74 | * 75 | * Example: 76 | * {"cmd": "getFunction", "data": {"prefix": "drupal_get_path"}} 77 | */ 78 | public function getFunction($data) { 79 | $function_name = $data->function; 80 | $function_info = SymbolTable::getInstance()->getFunctions($function_name); 81 | $this->connection->write(json_encode($function_info[$function_name])); 82 | } 83 | 84 | /** 85 | * Disconnect the currently connected client. 86 | * 87 | * Example: 88 | * {"cmd": "disconnect"} 89 | */ 90 | public function disconnect() { 91 | $this->connection->close(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Indexer.php: -------------------------------------------------------------------------------- 1 | paths = $paths; 59 | $this->filemask = $filemask; 60 | 61 | // Build a new parser and store it for later. 62 | $lexer = new Lexer(); 63 | $this->parser = new Parser($lexer); 64 | 65 | // Build a new traverser and store it for later. 66 | $this->traverser = new NodeTraverser(); 67 | $this->traverser->addVisitor(new NameResolver()); 68 | $this->traverser->addVisitor(new FileIdentifier("/dev/null")); 69 | $this->traverser->addVisitor(new ClassFinder()); 70 | $this->traverser->addVisitor(new FunctionFinder()); 71 | $this->traverser->addVisitor(new MethodFinder()); 72 | 73 | // Rebuild the list of files to be indexed. 74 | $this->rebuildFileList(); 75 | } 76 | 77 | /** 78 | * Build a list of files to index. 79 | */ 80 | protected function rebuildFileList() { 81 | $this->filelist = array(); 82 | foreach ($this->paths as $path) { 83 | $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); 84 | $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST); 85 | $files = new \RegexIterator($iterator, $this->filemask, \RecursiveRegexIterator::GET_MATCH); 86 | foreach($files as $file) { 87 | $this->filelist[] = $file[0]; 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Reindex all symbols. 94 | * 95 | * @TODO: Can this be parallelized with pthreads? 96 | */ 97 | public function index($reportFunction = NULL) { 98 | SymbolTable::getInstance()->purgeData(); 99 | $this->rebuildFileList(); 100 | 101 | $current = 0; 102 | $total = count($this->filelist); 103 | foreach ($this->filelist as $file) { 104 | // Index the file. 105 | $this->indexFile($file); 106 | $current++; 107 | if (is_callable($reportFunction)) { 108 | $reportFunction($file, $current, $total); 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Reindex a specific file. 115 | */ 116 | public function indexFile($path) { 117 | // Let the file identifier visitor know what file we're working on. 118 | $this->traverser->replaceVisitor("cweagans\\TheForce\\NodeVisitor\\FileIdentifier", new FileIdentifier($path)); 119 | $this->traverser->traverse($this->parser->parseFile($path)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/NodeTraverser.php: -------------------------------------------------------------------------------- 1 | visitors as $index => $storedVisitor) { 13 | if ($storedVisitor instanceof $type) { 14 | $this->visitors[$index] = $newinstance; 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/NodeVisitor/ClassFinder.php: -------------------------------------------------------------------------------- 1 | addClass($node->namespacedName->parts, $this->gatherMetadata($node)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NodeVisitor/FileIdentifier.php: -------------------------------------------------------------------------------- 1 | filepath = $filepath; 20 | } 21 | 22 | /** 23 | * Add filepath to nodes. 24 | * 25 | * @param Node $node 26 | */ 27 | public function enterNode(Node $node) { 28 | $node->setAttribute('file', $this->filepath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NodeVisitor/FunctionFinder.php: -------------------------------------------------------------------------------- 1 | addFunction($node->name, $this->gatherMetadata($node)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NodeVisitor/MethodFinder.php: -------------------------------------------------------------------------------- 1 | currentClass = implode('\\', $node->namespacedName->parts); 22 | } 23 | 24 | if ($node instanceof Stmt\ClassMethod) { 25 | SymbolTable::getInstance()->addMethod($this->currentClass, $node->name, $this->gatherMetadata($node)); 26 | // var_dump($node); 27 | // SymbolTable::getInstance()->addMethod(); 28 | } 29 | } 30 | 31 | public function leaveNode(Node $node) { 32 | if ($node instanceof Stmt\Class_) { 33 | $this->currentClass = ''; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/NodeVisitor/SymbolFinder.php: -------------------------------------------------------------------------------- 1 | filepath = $filepath; 38 | } 39 | 40 | /** 41 | * Gathers any important metadata about the node, which will be passed to the SymbolTable. 42 | * 43 | * @param Node $node 44 | */ 45 | protected function gatherMetadata(Node $node) { 46 | $metadata = array(); 47 | 48 | // Every node will have an associated file. 49 | $metadata['file'] = $node->getAttribute("file"); 50 | 51 | // Every node will have an associated line number. 52 | $metadata['line_number'] = $node->getLine(); 53 | 54 | // Not every node has a docblock. 55 | $metadata['docblock'] = ''; 56 | if (method_exists($node, 'getDocComment')) { 57 | $metadata['docblock'] = $node->getDocComment(); 58 | } 59 | 60 | // If we're operating on a class, it might extend another class... 61 | if ($node instanceof Stmt\Class_ && property_exists($node, 'extends')) { 62 | $metadata['parent'] = $node->extends->parts; 63 | } 64 | 65 | return $metadata; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Parser.php: -------------------------------------------------------------------------------- 1 | parse(file_get_contents($path)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SymbolTable.php: -------------------------------------------------------------------------------- 1 | processMetadata($metadata); 45 | $this->classList[$namespacedClass] = $metadata; 46 | } 47 | 48 | /** 49 | * Get a list of classes that we know about. 50 | * 51 | * @return array 52 | */ 53 | public function getClasses($prefix = '') { 54 | if ($prefix != '') { 55 | return $this->filterArrayByPrefix($this->classList, $prefix); 56 | } 57 | return $this->classList; 58 | } 59 | 60 | /** 61 | * Add a function to the symbol table 62 | * 63 | * @param $functionName 64 | * @param $metadata 65 | */ 66 | public function addFunction($functionName, $metadata) { 67 | $this->processMetadata($metadata); 68 | $this->functionList[$functionName] = $metadata; 69 | } 70 | 71 | /** 72 | * Return the list of functions that we know about. 73 | * 74 | * @return array 75 | */ 76 | public function getFunctions($prefix = '') { 77 | if ($prefix != '') { 78 | return $this->filterArrayByPrefix($this->functionList, $prefix); 79 | } 80 | return $this->functionList; 81 | } 82 | 83 | /** 84 | * Add a class method to the symbol table. 85 | * 86 | * @param $className 87 | * @param $methodName 88 | * @param $metadata 89 | */ 90 | public function addMethod($className, $methodName, $metadata) { 91 | $this->processMetadata($metadata); 92 | $this->methodList[$className][$methodName] = $metadata; 93 | } 94 | 95 | /** 96 | * Return the full list of class methods we know about. 97 | * 98 | * @return array 99 | */ 100 | public function getMethods() { 101 | return $this->methodList; 102 | } 103 | 104 | /** 105 | * Return an optionally filtered list of methods found in a class and it's parents. 106 | * 107 | * @param string $classname The fully qualified class name. 108 | * @param string $prefix The prefix to filter by. 109 | * @return array 110 | */ 111 | public function getFilteredMethodsByClass($classname, $prefix = '') { 112 | if (!array_key_exists($classname, $this->methodList)) { 113 | throw new \InvalidArgumentException($classname . ' is not in the method list!'); 114 | } 115 | 116 | if ($prefix != '') { 117 | $class_methods = $this->getMethodsByClass($classname); 118 | return $this->filterArrayByPrefix($class_methods, $prefix); 119 | } 120 | return $this->getMethodsByClass($classname); 121 | } 122 | 123 | /** 124 | * Return a list of class methods (including parents) filtered by classname. 125 | * 126 | * @param $classname 127 | * @return array $fullMethodList 128 | */ 129 | public function getMethodsByClass($classname) { 130 | $methods = $this->getMethods(); 131 | 132 | // If we were given a fully qualified classpath, strip the leading backslash. 133 | if (substr($classname, 0, 1) == '\\') { 134 | $classname = substr($classname, 1); 135 | } 136 | 137 | // Start the list of methods with the class given to us. 138 | $fullMethodList = $methods[$classname]; 139 | 140 | $classList = $this->getClasses(); 141 | if (isset($classList[$classname]['parent'])) { 142 | $fullMethodList = array_merge($fullMethodList, $this->getMethodsByClass($classList[$classname]['parent'])); 143 | } 144 | 145 | return $fullMethodList; 146 | } 147 | 148 | public function purgeData() { 149 | $this->classList = array(); 150 | $this->methodList = array(); 151 | $this->functionList = array(); 152 | } 153 | 154 | /** 155 | * Process any associated metadata that comes with the symbol. 156 | * 157 | * @param array $metadata 158 | */ 159 | protected function processMetadata(array &$metadata) { 160 | if (isset($metadata['docblock'])) { 161 | $metadata['docblock'] = $this->parseDocblock($metadata['docblock']); 162 | } 163 | if (isset($metadata['parent'])) { 164 | $metadata['parent'] = implode('\\', $metadata['parent']); 165 | } 166 | } 167 | 168 | /** 169 | * Parse a docblock into usable data. 170 | * 171 | * @param $docblock 172 | * @return array $docblock 173 | */ 174 | protected function parseDocblock($docblock) { 175 | if (is_object($docblock)) { 176 | $docblock = $docblock->__toString(); 177 | } 178 | else { 179 | return array(); 180 | } 181 | 182 | try { 183 | $phpdoc = new \phpDocumentor\Reflection\DocBlock($docblock); 184 | } 185 | catch (\Exception $e) { 186 | // Something's messed up with the docblock. 187 | // @TODO: Do something better here. 188 | return array(); 189 | } 190 | 191 | $docblock = array(); 192 | $docblock['description'] = $phpdoc->getText(); 193 | $return = $phpdoc->getTagsByName('return'); 194 | if (!empty($return)) { 195 | $docblock['return'] = $this->parseDocTag($return[0]->getContent()); 196 | } 197 | 198 | foreach ($phpdoc->getTagsByName('param') as $param) { 199 | $docblock['params'][] = $this->parseDocTag($param->getContent()); 200 | 201 | } 202 | 203 | return $docblock; 204 | } 205 | 206 | /** 207 | * Parse a doctag into machine-readable data. 208 | * 209 | * The doctag should be in the form of @tagname ($optionalVarName) type short_description. 210 | * 211 | * @param $doctag 212 | * @return array $doctag 213 | */ 214 | protected function parseDocTag($doctag) { 215 | $doctag = explode(" ", $doctag); 216 | 217 | // First, check to see if there's something that starts with $. Name of the var for params. 218 | $paramName = ''; 219 | if (substr($doctag[0], 0, 1) == '$') { 220 | $paramName = array_shift($doctag); 221 | } 222 | 223 | // Next, we can assume there's a type and a description for all tags passed to this function. 224 | // @TODO: Add better checking as to whether the first word of the content is actually the type. 225 | $type = array_shift($doctag); 226 | $description = implode(" ", $doctag); 227 | 228 | $doctag = array( 229 | 'type' => $type, 230 | 'description' => $description, 231 | ); 232 | 233 | if ($paramName !== '') { 234 | $doctag['paramName'] = $paramName; 235 | } 236 | 237 | return $doctag; 238 | } 239 | 240 | protected function filterArrayByPrefix(array $array, $prefix) { 241 | $allowed_keys = array_filter(array_keys($array), function($key) use($prefix) { 242 | return (strrpos($key, $prefix, -strlen($key)) !== FALSE); 243 | }); 244 | return array_intersect_key($array, array_flip($allowed_keys)); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /test/TheForce/SymbolTableTest.php: -------------------------------------------------------------------------------- 1 | on('connection', function(\React\Stream\DuplexStreamInterface $conn) { 19 | $dispatcher = new cweagans\TheForce\Dispatcher($conn); 20 | $conn->on('data', array($dispatcher, 'dispatch')); 21 | }); 22 | 23 | $socket->listen('13370'); 24 | 25 | $loop->run(); 26 | --------------------------------------------------------------------------------