├── .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 | [](https://travis-ci.org/cweagans/theforce)
4 | [](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 |
--------------------------------------------------------------------------------