├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src ├── ClassInfo.php ├── CliParser.php ├── Context.php ├── ContextCollector.php ├── FileContext.php ├── FunctionInfo.php ├── MutableString.php ├── MutatingVisitor.php ├── NoDynamicProperties.php ├── Options.php ├── Type.php ├── TypeAnnotationVisitor.php ├── TypeExtractor.php ├── TypeRemovalVisitor.php └── functions.php ├── test ├── IntegrationTest.php ├── bootstrap.php └── code │ ├── add_strict.php │ ├── anon_class.php │ ├── basic.php │ ├── inheritance.php │ ├── iterable.php │ ├── name_resolution.php │ ├── no_add_strict.php │ ├── null.php │ ├── nullable.php │ ├── nullable_inheritance.php │ ├── object.php │ ├── php_version.php │ ├── property_types.php │ ├── remove.php │ ├── rename.php │ ├── return_type_position.php │ ├── self_inheritance.php │ ├── self_parent_static.php │ └── unsupported_types.php └── type-util.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 by Nikita Popov. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Utility for adding return types and scalar types 2 | 3 | > **WARNING**: Utility directly modifies files under the assumption that you're using version control. 4 | 5 | To add return types and scalar types based on doc comments, run: 6 | 7 | php-7.1 type-util.php add dir1/ dir2/ ... 8 | 9 | To remove all PHP 5 incompatible type information, run: 10 | 11 | php-7.1 type-util.php remove dir1/ dir2/ ... 12 | 13 | Notes: 14 | 15 | * Not well tested - probably doesn't work in many cases 16 | * Uses only doc comments, so requires good doc comment coverage 17 | * Requires PHP 7.1 to run 18 | * The main job of this utility is dealing with the fact that PHP return types are semi-invariant 19 | and argument types are fully invariant 20 | 21 | ### Help Text 22 | 23 | ``` 24 | Usage: php ./type-util.php add|remove [--options] dir1 dir2 ... 25 | 26 | Options: 27 | --php VERSION Enable all features supported up to VERSION 28 | E.g. --php 7.1 29 | --[no-]object Toggle generation of object type (PHP 7.2) 30 | --[no-]nullable-types Toggle generation of nullable types (PHP 7.1) 31 | --[no-]iterable Toggle generation of iterable type (PHP 7.1) 32 | --[no-]strict-types Toggle use of strict_types (PHP 7.0) 33 | 34 | Examples: 35 | # Add everything that's possible! 36 | php ./type-utils.php add path/to/dir 37 | 38 | # Only add features available in PHP 7.0 39 | php ./type-utils.php --php 7.0 40 | 41 | # Add everything available in PHP 7.1, apart from strict types 42 | php ./type-utils.php --php 7.1 --no-strict-types 43 | 44 | NOTE: Will directly modify files, assumes that you're using VCS. 45 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php": "^7.1", 4 | "nikic/php-parser": "^4.13" 5 | }, 6 | "autoload": { 7 | "psr-4": { 8 | "TypeUtil\\": "src/" 9 | }, 10 | "files": ["src/functions.php"] 11 | }, 12 | "require-dev": { 13 | "phpunit/phpunit": "^7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "7bf2cc44fb2eee50be89f55f39780fdf", 8 | "packages": [ 9 | { 10 | "name": "nikic/php-parser", 11 | "version": "v4.14.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/nikic/PHP-Parser.git", 15 | "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", 20 | "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-tokenizer": "*", 25 | "php": ">=7.0" 26 | }, 27 | "require-dev": { 28 | "ircmaxell/php-yacc": "^0.0.7", 29 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 30 | }, 31 | "bin": [ 32 | "bin/php-parse" 33 | ], 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "4.9-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "PhpParser\\": "lib/PhpParser" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "BSD-3-Clause" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Nikita Popov" 52 | } 53 | ], 54 | "description": "A PHP parser written in PHP", 55 | "keywords": [ 56 | "parser", 57 | "php" 58 | ], 59 | "time": "2022-05-31T20:59:12+00:00" 60 | } 61 | ], 62 | "packages-dev": [ 63 | { 64 | "name": "doctrine/instantiator", 65 | "version": "1.4.1", 66 | "source": { 67 | "type": "git", 68 | "url": "https://github.com/doctrine/instantiator.git", 69 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" 70 | }, 71 | "dist": { 72 | "type": "zip", 73 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", 74 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", 75 | "shasum": "" 76 | }, 77 | "require": { 78 | "php": "^7.1 || ^8.0" 79 | }, 80 | "require-dev": { 81 | "doctrine/coding-standard": "^9", 82 | "ext-pdo": "*", 83 | "ext-phar": "*", 84 | "phpbench/phpbench": "^0.16 || ^1", 85 | "phpstan/phpstan": "^1.4", 86 | "phpstan/phpstan-phpunit": "^1", 87 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 88 | "vimeo/psalm": "^4.22" 89 | }, 90 | "type": "library", 91 | "autoload": { 92 | "psr-4": { 93 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 94 | } 95 | }, 96 | "notification-url": "https://packagist.org/downloads/", 97 | "license": [ 98 | "MIT" 99 | ], 100 | "authors": [ 101 | { 102 | "name": "Marco Pivetta", 103 | "email": "ocramius@gmail.com", 104 | "homepage": "https://ocramius.github.io/" 105 | } 106 | ], 107 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 108 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 109 | "keywords": [ 110 | "constructor", 111 | "instantiate" 112 | ], 113 | "funding": [ 114 | { 115 | "url": "https://www.doctrine-project.org/sponsorship.html", 116 | "type": "custom" 117 | }, 118 | { 119 | "url": "https://www.patreon.com/phpdoctrine", 120 | "type": "patreon" 121 | }, 122 | { 123 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 124 | "type": "tidelift" 125 | } 126 | ], 127 | "time": "2022-03-03T08:28:38+00:00" 128 | }, 129 | { 130 | "name": "myclabs/deep-copy", 131 | "version": "1.11.0", 132 | "source": { 133 | "type": "git", 134 | "url": "https://github.com/myclabs/DeepCopy.git", 135 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" 136 | }, 137 | "dist": { 138 | "type": "zip", 139 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", 140 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", 141 | "shasum": "" 142 | }, 143 | "require": { 144 | "php": "^7.1 || ^8.0" 145 | }, 146 | "conflict": { 147 | "doctrine/collections": "<1.6.8", 148 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 149 | }, 150 | "require-dev": { 151 | "doctrine/collections": "^1.6.8", 152 | "doctrine/common": "^2.13.3 || ^3.2.2", 153 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 154 | }, 155 | "type": "library", 156 | "autoload": { 157 | "files": [ 158 | "src/DeepCopy/deep_copy.php" 159 | ], 160 | "psr-4": { 161 | "DeepCopy\\": "src/DeepCopy/" 162 | } 163 | }, 164 | "notification-url": "https://packagist.org/downloads/", 165 | "license": [ 166 | "MIT" 167 | ], 168 | "description": "Create deep copies (clones) of your objects", 169 | "keywords": [ 170 | "clone", 171 | "copy", 172 | "duplicate", 173 | "object", 174 | "object graph" 175 | ], 176 | "funding": [ 177 | { 178 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 179 | "type": "tidelift" 180 | } 181 | ], 182 | "time": "2022-03-03T13:19:32+00:00" 183 | }, 184 | { 185 | "name": "phar-io/manifest", 186 | "version": "1.0.3", 187 | "source": { 188 | "type": "git", 189 | "url": "https://github.com/phar-io/manifest.git", 190 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 191 | }, 192 | "dist": { 193 | "type": "zip", 194 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 195 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 196 | "shasum": "" 197 | }, 198 | "require": { 199 | "ext-dom": "*", 200 | "ext-phar": "*", 201 | "phar-io/version": "^2.0", 202 | "php": "^5.6 || ^7.0" 203 | }, 204 | "type": "library", 205 | "extra": { 206 | "branch-alias": { 207 | "dev-master": "1.0.x-dev" 208 | } 209 | }, 210 | "autoload": { 211 | "classmap": [ 212 | "src/" 213 | ] 214 | }, 215 | "notification-url": "https://packagist.org/downloads/", 216 | "license": [ 217 | "BSD-3-Clause" 218 | ], 219 | "authors": [ 220 | { 221 | "name": "Arne Blankerts", 222 | "email": "arne@blankerts.de", 223 | "role": "Developer" 224 | }, 225 | { 226 | "name": "Sebastian Heuer", 227 | "email": "sebastian@phpeople.de", 228 | "role": "Developer" 229 | }, 230 | { 231 | "name": "Sebastian Bergmann", 232 | "email": "sebastian@phpunit.de", 233 | "role": "Developer" 234 | } 235 | ], 236 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 237 | "time": "2018-07-08T19:23:20+00:00" 238 | }, 239 | { 240 | "name": "phar-io/version", 241 | "version": "2.0.1", 242 | "source": { 243 | "type": "git", 244 | "url": "https://github.com/phar-io/version.git", 245 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 246 | }, 247 | "dist": { 248 | "type": "zip", 249 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 250 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 251 | "shasum": "" 252 | }, 253 | "require": { 254 | "php": "^5.6 || ^7.0" 255 | }, 256 | "type": "library", 257 | "autoload": { 258 | "classmap": [ 259 | "src/" 260 | ] 261 | }, 262 | "notification-url": "https://packagist.org/downloads/", 263 | "license": [ 264 | "BSD-3-Clause" 265 | ], 266 | "authors": [ 267 | { 268 | "name": "Arne Blankerts", 269 | "email": "arne@blankerts.de", 270 | "role": "Developer" 271 | }, 272 | { 273 | "name": "Sebastian Heuer", 274 | "email": "sebastian@phpeople.de", 275 | "role": "Developer" 276 | }, 277 | { 278 | "name": "Sebastian Bergmann", 279 | "email": "sebastian@phpunit.de", 280 | "role": "Developer" 281 | } 282 | ], 283 | "description": "Library for handling version information and constraints", 284 | "time": "2018-07-08T19:19:57+00:00" 285 | }, 286 | { 287 | "name": "phpdocumentor/reflection-common", 288 | "version": "2.2.0", 289 | "source": { 290 | "type": "git", 291 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 292 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 293 | }, 294 | "dist": { 295 | "type": "zip", 296 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 297 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 298 | "shasum": "" 299 | }, 300 | "require": { 301 | "php": "^7.2 || ^8.0" 302 | }, 303 | "type": "library", 304 | "extra": { 305 | "branch-alias": { 306 | "dev-2.x": "2.x-dev" 307 | } 308 | }, 309 | "autoload": { 310 | "psr-4": { 311 | "phpDocumentor\\Reflection\\": "src/" 312 | } 313 | }, 314 | "notification-url": "https://packagist.org/downloads/", 315 | "license": [ 316 | "MIT" 317 | ], 318 | "authors": [ 319 | { 320 | "name": "Jaap van Otterdijk", 321 | "email": "opensource@ijaap.nl" 322 | } 323 | ], 324 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 325 | "homepage": "http://www.phpdoc.org", 326 | "keywords": [ 327 | "FQSEN", 328 | "phpDocumentor", 329 | "phpdoc", 330 | "reflection", 331 | "static analysis" 332 | ], 333 | "time": "2020-06-27T09:03:43+00:00" 334 | }, 335 | { 336 | "name": "phpdocumentor/reflection-docblock", 337 | "version": "5.3.0", 338 | "source": { 339 | "type": "git", 340 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 341 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" 342 | }, 343 | "dist": { 344 | "type": "zip", 345 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", 346 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", 347 | "shasum": "" 348 | }, 349 | "require": { 350 | "ext-filter": "*", 351 | "php": "^7.2 || ^8.0", 352 | "phpdocumentor/reflection-common": "^2.2", 353 | "phpdocumentor/type-resolver": "^1.3", 354 | "webmozart/assert": "^1.9.1" 355 | }, 356 | "require-dev": { 357 | "mockery/mockery": "~1.3.2", 358 | "psalm/phar": "^4.8" 359 | }, 360 | "type": "library", 361 | "extra": { 362 | "branch-alias": { 363 | "dev-master": "5.x-dev" 364 | } 365 | }, 366 | "autoload": { 367 | "psr-4": { 368 | "phpDocumentor\\Reflection\\": "src" 369 | } 370 | }, 371 | "notification-url": "https://packagist.org/downloads/", 372 | "license": [ 373 | "MIT" 374 | ], 375 | "authors": [ 376 | { 377 | "name": "Mike van Riel", 378 | "email": "me@mikevanriel.com" 379 | }, 380 | { 381 | "name": "Jaap van Otterdijk", 382 | "email": "account@ijaap.nl" 383 | } 384 | ], 385 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 386 | "time": "2021-10-19T17:43:47+00:00" 387 | }, 388 | { 389 | "name": "phpdocumentor/type-resolver", 390 | "version": "1.6.1", 391 | "source": { 392 | "type": "git", 393 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 394 | "reference": "77a32518733312af16a44300404e945338981de3" 395 | }, 396 | "dist": { 397 | "type": "zip", 398 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", 399 | "reference": "77a32518733312af16a44300404e945338981de3", 400 | "shasum": "" 401 | }, 402 | "require": { 403 | "php": "^7.2 || ^8.0", 404 | "phpdocumentor/reflection-common": "^2.0" 405 | }, 406 | "require-dev": { 407 | "ext-tokenizer": "*", 408 | "psalm/phar": "^4.8" 409 | }, 410 | "type": "library", 411 | "extra": { 412 | "branch-alias": { 413 | "dev-1.x": "1.x-dev" 414 | } 415 | }, 416 | "autoload": { 417 | "psr-4": { 418 | "phpDocumentor\\Reflection\\": "src" 419 | } 420 | }, 421 | "notification-url": "https://packagist.org/downloads/", 422 | "license": [ 423 | "MIT" 424 | ], 425 | "authors": [ 426 | { 427 | "name": "Mike van Riel", 428 | "email": "me@mikevanriel.com" 429 | } 430 | ], 431 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 432 | "time": "2022-03-15T21:29:03+00:00" 433 | }, 434 | { 435 | "name": "phpspec/prophecy", 436 | "version": "v1.15.0", 437 | "source": { 438 | "type": "git", 439 | "url": "https://github.com/phpspec/prophecy.git", 440 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" 441 | }, 442 | "dist": { 443 | "type": "zip", 444 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 445 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 446 | "shasum": "" 447 | }, 448 | "require": { 449 | "doctrine/instantiator": "^1.2", 450 | "php": "^7.2 || ~8.0, <8.2", 451 | "phpdocumentor/reflection-docblock": "^5.2", 452 | "sebastian/comparator": "^3.0 || ^4.0", 453 | "sebastian/recursion-context": "^3.0 || ^4.0" 454 | }, 455 | "require-dev": { 456 | "phpspec/phpspec": "^6.0 || ^7.0", 457 | "phpunit/phpunit": "^8.0 || ^9.0" 458 | }, 459 | "type": "library", 460 | "extra": { 461 | "branch-alias": { 462 | "dev-master": "1.x-dev" 463 | } 464 | }, 465 | "autoload": { 466 | "psr-4": { 467 | "Prophecy\\": "src/Prophecy" 468 | } 469 | }, 470 | "notification-url": "https://packagist.org/downloads/", 471 | "license": [ 472 | "MIT" 473 | ], 474 | "authors": [ 475 | { 476 | "name": "Konstantin Kudryashov", 477 | "email": "ever.zet@gmail.com", 478 | "homepage": "http://everzet.com" 479 | }, 480 | { 481 | "name": "Marcello Duarte", 482 | "email": "marcello.duarte@gmail.com" 483 | } 484 | ], 485 | "description": "Highly opinionated mocking framework for PHP 5.3+", 486 | "homepage": "https://github.com/phpspec/prophecy", 487 | "keywords": [ 488 | "Double", 489 | "Dummy", 490 | "fake", 491 | "mock", 492 | "spy", 493 | "stub" 494 | ], 495 | "time": "2021-12-08T12:19:24+00:00" 496 | }, 497 | { 498 | "name": "phpunit/php-code-coverage", 499 | "version": "6.1.4", 500 | "source": { 501 | "type": "git", 502 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 503 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" 504 | }, 505 | "dist": { 506 | "type": "zip", 507 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 508 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 509 | "shasum": "" 510 | }, 511 | "require": { 512 | "ext-dom": "*", 513 | "ext-xmlwriter": "*", 514 | "php": "^7.1", 515 | "phpunit/php-file-iterator": "^2.0", 516 | "phpunit/php-text-template": "^1.2.1", 517 | "phpunit/php-token-stream": "^3.0", 518 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 519 | "sebastian/environment": "^3.1 || ^4.0", 520 | "sebastian/version": "^2.0.1", 521 | "theseer/tokenizer": "^1.1" 522 | }, 523 | "require-dev": { 524 | "phpunit/phpunit": "^7.0" 525 | }, 526 | "suggest": { 527 | "ext-xdebug": "^2.6.0" 528 | }, 529 | "type": "library", 530 | "extra": { 531 | "branch-alias": { 532 | "dev-master": "6.1-dev" 533 | } 534 | }, 535 | "autoload": { 536 | "classmap": [ 537 | "src/" 538 | ] 539 | }, 540 | "notification-url": "https://packagist.org/downloads/", 541 | "license": [ 542 | "BSD-3-Clause" 543 | ], 544 | "authors": [ 545 | { 546 | "name": "Sebastian Bergmann", 547 | "email": "sebastian@phpunit.de", 548 | "role": "lead" 549 | } 550 | ], 551 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 552 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 553 | "keywords": [ 554 | "coverage", 555 | "testing", 556 | "xunit" 557 | ], 558 | "time": "2018-10-31T16:06:48+00:00" 559 | }, 560 | { 561 | "name": "phpunit/php-file-iterator", 562 | "version": "2.0.5", 563 | "source": { 564 | "type": "git", 565 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 566 | "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" 567 | }, 568 | "dist": { 569 | "type": "zip", 570 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", 571 | "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", 572 | "shasum": "" 573 | }, 574 | "require": { 575 | "php": ">=7.1" 576 | }, 577 | "require-dev": { 578 | "phpunit/phpunit": "^8.5" 579 | }, 580 | "type": "library", 581 | "extra": { 582 | "branch-alias": { 583 | "dev-master": "2.0.x-dev" 584 | } 585 | }, 586 | "autoload": { 587 | "classmap": [ 588 | "src/" 589 | ] 590 | }, 591 | "notification-url": "https://packagist.org/downloads/", 592 | "license": [ 593 | "BSD-3-Clause" 594 | ], 595 | "authors": [ 596 | { 597 | "name": "Sebastian Bergmann", 598 | "email": "sebastian@phpunit.de", 599 | "role": "lead" 600 | } 601 | ], 602 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 603 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 604 | "keywords": [ 605 | "filesystem", 606 | "iterator" 607 | ], 608 | "funding": [ 609 | { 610 | "url": "https://github.com/sebastianbergmann", 611 | "type": "github" 612 | } 613 | ], 614 | "time": "2021-12-02T12:42:26+00:00" 615 | }, 616 | { 617 | "name": "phpunit/php-text-template", 618 | "version": "1.2.1", 619 | "source": { 620 | "type": "git", 621 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 622 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 623 | }, 624 | "dist": { 625 | "type": "zip", 626 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 627 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 628 | "shasum": "" 629 | }, 630 | "require": { 631 | "php": ">=5.3.3" 632 | }, 633 | "type": "library", 634 | "autoload": { 635 | "classmap": [ 636 | "src/" 637 | ] 638 | }, 639 | "notification-url": "https://packagist.org/downloads/", 640 | "license": [ 641 | "BSD-3-Clause" 642 | ], 643 | "authors": [ 644 | { 645 | "name": "Sebastian Bergmann", 646 | "email": "sebastian@phpunit.de", 647 | "role": "lead" 648 | } 649 | ], 650 | "description": "Simple template engine.", 651 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 652 | "keywords": [ 653 | "template" 654 | ], 655 | "time": "2015-06-21T13:50:34+00:00" 656 | }, 657 | { 658 | "name": "phpunit/php-timer", 659 | "version": "2.1.3", 660 | "source": { 661 | "type": "git", 662 | "url": "https://github.com/sebastianbergmann/php-timer.git", 663 | "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" 664 | }, 665 | "dist": { 666 | "type": "zip", 667 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", 668 | "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", 669 | "shasum": "" 670 | }, 671 | "require": { 672 | "php": ">=7.1" 673 | }, 674 | "require-dev": { 675 | "phpunit/phpunit": "^8.5" 676 | }, 677 | "type": "library", 678 | "extra": { 679 | "branch-alias": { 680 | "dev-master": "2.1-dev" 681 | } 682 | }, 683 | "autoload": { 684 | "classmap": [ 685 | "src/" 686 | ] 687 | }, 688 | "notification-url": "https://packagist.org/downloads/", 689 | "license": [ 690 | "BSD-3-Clause" 691 | ], 692 | "authors": [ 693 | { 694 | "name": "Sebastian Bergmann", 695 | "email": "sebastian@phpunit.de", 696 | "role": "lead" 697 | } 698 | ], 699 | "description": "Utility class for timing", 700 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 701 | "keywords": [ 702 | "timer" 703 | ], 704 | "funding": [ 705 | { 706 | "url": "https://github.com/sebastianbergmann", 707 | "type": "github" 708 | } 709 | ], 710 | "time": "2020-11-30T08:20:02+00:00" 711 | }, 712 | { 713 | "name": "phpunit/php-token-stream", 714 | "version": "3.1.3", 715 | "source": { 716 | "type": "git", 717 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 718 | "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" 719 | }, 720 | "dist": { 721 | "type": "zip", 722 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", 723 | "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", 724 | "shasum": "" 725 | }, 726 | "require": { 727 | "ext-tokenizer": "*", 728 | "php": ">=7.1" 729 | }, 730 | "require-dev": { 731 | "phpunit/phpunit": "^7.0" 732 | }, 733 | "type": "library", 734 | "extra": { 735 | "branch-alias": { 736 | "dev-master": "3.1-dev" 737 | } 738 | }, 739 | "autoload": { 740 | "classmap": [ 741 | "src/" 742 | ] 743 | }, 744 | "notification-url": "https://packagist.org/downloads/", 745 | "license": [ 746 | "BSD-3-Clause" 747 | ], 748 | "authors": [ 749 | { 750 | "name": "Sebastian Bergmann", 751 | "email": "sebastian@phpunit.de" 752 | } 753 | ], 754 | "description": "Wrapper around PHP's tokenizer extension.", 755 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 756 | "keywords": [ 757 | "tokenizer" 758 | ], 759 | "funding": [ 760 | { 761 | "url": "https://github.com/sebastianbergmann", 762 | "type": "github" 763 | } 764 | ], 765 | "abandoned": true, 766 | "time": "2021-07-26T12:15:06+00:00" 767 | }, 768 | { 769 | "name": "phpunit/phpunit", 770 | "version": "7.5.20", 771 | "source": { 772 | "type": "git", 773 | "url": "https://github.com/sebastianbergmann/phpunit.git", 774 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" 775 | }, 776 | "dist": { 777 | "type": "zip", 778 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", 779 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", 780 | "shasum": "" 781 | }, 782 | "require": { 783 | "doctrine/instantiator": "^1.1", 784 | "ext-dom": "*", 785 | "ext-json": "*", 786 | "ext-libxml": "*", 787 | "ext-mbstring": "*", 788 | "ext-xml": "*", 789 | "myclabs/deep-copy": "^1.7", 790 | "phar-io/manifest": "^1.0.2", 791 | "phar-io/version": "^2.0", 792 | "php": "^7.1", 793 | "phpspec/prophecy": "^1.7", 794 | "phpunit/php-code-coverage": "^6.0.7", 795 | "phpunit/php-file-iterator": "^2.0.1", 796 | "phpunit/php-text-template": "^1.2.1", 797 | "phpunit/php-timer": "^2.1", 798 | "sebastian/comparator": "^3.0", 799 | "sebastian/diff": "^3.0", 800 | "sebastian/environment": "^4.0", 801 | "sebastian/exporter": "^3.1", 802 | "sebastian/global-state": "^2.0", 803 | "sebastian/object-enumerator": "^3.0.3", 804 | "sebastian/resource-operations": "^2.0", 805 | "sebastian/version": "^2.0.1" 806 | }, 807 | "conflict": { 808 | "phpunit/phpunit-mock-objects": "*" 809 | }, 810 | "require-dev": { 811 | "ext-pdo": "*" 812 | }, 813 | "suggest": { 814 | "ext-soap": "*", 815 | "ext-xdebug": "*", 816 | "phpunit/php-invoker": "^2.0" 817 | }, 818 | "bin": [ 819 | "phpunit" 820 | ], 821 | "type": "library", 822 | "extra": { 823 | "branch-alias": { 824 | "dev-master": "7.5-dev" 825 | } 826 | }, 827 | "autoload": { 828 | "classmap": [ 829 | "src/" 830 | ] 831 | }, 832 | "notification-url": "https://packagist.org/downloads/", 833 | "license": [ 834 | "BSD-3-Clause" 835 | ], 836 | "authors": [ 837 | { 838 | "name": "Sebastian Bergmann", 839 | "email": "sebastian@phpunit.de", 840 | "role": "lead" 841 | } 842 | ], 843 | "description": "The PHP Unit Testing framework.", 844 | "homepage": "https://phpunit.de/", 845 | "keywords": [ 846 | "phpunit", 847 | "testing", 848 | "xunit" 849 | ], 850 | "time": "2020-01-08T08:45:45+00:00" 851 | }, 852 | { 853 | "name": "sebastian/code-unit-reverse-lookup", 854 | "version": "1.0.2", 855 | "source": { 856 | "type": "git", 857 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 858 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" 859 | }, 860 | "dist": { 861 | "type": "zip", 862 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", 863 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", 864 | "shasum": "" 865 | }, 866 | "require": { 867 | "php": ">=5.6" 868 | }, 869 | "require-dev": { 870 | "phpunit/phpunit": "^8.5" 871 | }, 872 | "type": "library", 873 | "extra": { 874 | "branch-alias": { 875 | "dev-master": "1.0.x-dev" 876 | } 877 | }, 878 | "autoload": { 879 | "classmap": [ 880 | "src/" 881 | ] 882 | }, 883 | "notification-url": "https://packagist.org/downloads/", 884 | "license": [ 885 | "BSD-3-Clause" 886 | ], 887 | "authors": [ 888 | { 889 | "name": "Sebastian Bergmann", 890 | "email": "sebastian@phpunit.de" 891 | } 892 | ], 893 | "description": "Looks up which function or method a line of code belongs to", 894 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 895 | "funding": [ 896 | { 897 | "url": "https://github.com/sebastianbergmann", 898 | "type": "github" 899 | } 900 | ], 901 | "time": "2020-11-30T08:15:22+00:00" 902 | }, 903 | { 904 | "name": "sebastian/comparator", 905 | "version": "3.0.3", 906 | "source": { 907 | "type": "git", 908 | "url": "https://github.com/sebastianbergmann/comparator.git", 909 | "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" 910 | }, 911 | "dist": { 912 | "type": "zip", 913 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", 914 | "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", 915 | "shasum": "" 916 | }, 917 | "require": { 918 | "php": ">=7.1", 919 | "sebastian/diff": "^3.0", 920 | "sebastian/exporter": "^3.1" 921 | }, 922 | "require-dev": { 923 | "phpunit/phpunit": "^8.5" 924 | }, 925 | "type": "library", 926 | "extra": { 927 | "branch-alias": { 928 | "dev-master": "3.0-dev" 929 | } 930 | }, 931 | "autoload": { 932 | "classmap": [ 933 | "src/" 934 | ] 935 | }, 936 | "notification-url": "https://packagist.org/downloads/", 937 | "license": [ 938 | "BSD-3-Clause" 939 | ], 940 | "authors": [ 941 | { 942 | "name": "Sebastian Bergmann", 943 | "email": "sebastian@phpunit.de" 944 | }, 945 | { 946 | "name": "Jeff Welch", 947 | "email": "whatthejeff@gmail.com" 948 | }, 949 | { 950 | "name": "Volker Dusch", 951 | "email": "github@wallbash.com" 952 | }, 953 | { 954 | "name": "Bernhard Schussek", 955 | "email": "bschussek@2bepublished.at" 956 | } 957 | ], 958 | "description": "Provides the functionality to compare PHP values for equality", 959 | "homepage": "https://github.com/sebastianbergmann/comparator", 960 | "keywords": [ 961 | "comparator", 962 | "compare", 963 | "equality" 964 | ], 965 | "funding": [ 966 | { 967 | "url": "https://github.com/sebastianbergmann", 968 | "type": "github" 969 | } 970 | ], 971 | "time": "2020-11-30T08:04:30+00:00" 972 | }, 973 | { 974 | "name": "sebastian/diff", 975 | "version": "3.0.3", 976 | "source": { 977 | "type": "git", 978 | "url": "https://github.com/sebastianbergmann/diff.git", 979 | "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" 980 | }, 981 | "dist": { 982 | "type": "zip", 983 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", 984 | "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", 985 | "shasum": "" 986 | }, 987 | "require": { 988 | "php": ">=7.1" 989 | }, 990 | "require-dev": { 991 | "phpunit/phpunit": "^7.5 || ^8.0", 992 | "symfony/process": "^2 || ^3.3 || ^4" 993 | }, 994 | "type": "library", 995 | "extra": { 996 | "branch-alias": { 997 | "dev-master": "3.0-dev" 998 | } 999 | }, 1000 | "autoload": { 1001 | "classmap": [ 1002 | "src/" 1003 | ] 1004 | }, 1005 | "notification-url": "https://packagist.org/downloads/", 1006 | "license": [ 1007 | "BSD-3-Clause" 1008 | ], 1009 | "authors": [ 1010 | { 1011 | "name": "Sebastian Bergmann", 1012 | "email": "sebastian@phpunit.de" 1013 | }, 1014 | { 1015 | "name": "Kore Nordmann", 1016 | "email": "mail@kore-nordmann.de" 1017 | } 1018 | ], 1019 | "description": "Diff implementation", 1020 | "homepage": "https://github.com/sebastianbergmann/diff", 1021 | "keywords": [ 1022 | "diff", 1023 | "udiff", 1024 | "unidiff", 1025 | "unified diff" 1026 | ], 1027 | "funding": [ 1028 | { 1029 | "url": "https://github.com/sebastianbergmann", 1030 | "type": "github" 1031 | } 1032 | ], 1033 | "time": "2020-11-30T07:59:04+00:00" 1034 | }, 1035 | { 1036 | "name": "sebastian/environment", 1037 | "version": "4.2.4", 1038 | "source": { 1039 | "type": "git", 1040 | "url": "https://github.com/sebastianbergmann/environment.git", 1041 | "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" 1042 | }, 1043 | "dist": { 1044 | "type": "zip", 1045 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", 1046 | "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", 1047 | "shasum": "" 1048 | }, 1049 | "require": { 1050 | "php": ">=7.1" 1051 | }, 1052 | "require-dev": { 1053 | "phpunit/phpunit": "^7.5" 1054 | }, 1055 | "suggest": { 1056 | "ext-posix": "*" 1057 | }, 1058 | "type": "library", 1059 | "extra": { 1060 | "branch-alias": { 1061 | "dev-master": "4.2-dev" 1062 | } 1063 | }, 1064 | "autoload": { 1065 | "classmap": [ 1066 | "src/" 1067 | ] 1068 | }, 1069 | "notification-url": "https://packagist.org/downloads/", 1070 | "license": [ 1071 | "BSD-3-Clause" 1072 | ], 1073 | "authors": [ 1074 | { 1075 | "name": "Sebastian Bergmann", 1076 | "email": "sebastian@phpunit.de" 1077 | } 1078 | ], 1079 | "description": "Provides functionality to handle HHVM/PHP environments", 1080 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1081 | "keywords": [ 1082 | "Xdebug", 1083 | "environment", 1084 | "hhvm" 1085 | ], 1086 | "funding": [ 1087 | { 1088 | "url": "https://github.com/sebastianbergmann", 1089 | "type": "github" 1090 | } 1091 | ], 1092 | "time": "2020-11-30T07:53:42+00:00" 1093 | }, 1094 | { 1095 | "name": "sebastian/exporter", 1096 | "version": "3.1.4", 1097 | "source": { 1098 | "type": "git", 1099 | "url": "https://github.com/sebastianbergmann/exporter.git", 1100 | "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" 1101 | }, 1102 | "dist": { 1103 | "type": "zip", 1104 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", 1105 | "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", 1106 | "shasum": "" 1107 | }, 1108 | "require": { 1109 | "php": ">=7.0", 1110 | "sebastian/recursion-context": "^3.0" 1111 | }, 1112 | "require-dev": { 1113 | "ext-mbstring": "*", 1114 | "phpunit/phpunit": "^8.5" 1115 | }, 1116 | "type": "library", 1117 | "extra": { 1118 | "branch-alias": { 1119 | "dev-master": "3.1.x-dev" 1120 | } 1121 | }, 1122 | "autoload": { 1123 | "classmap": [ 1124 | "src/" 1125 | ] 1126 | }, 1127 | "notification-url": "https://packagist.org/downloads/", 1128 | "license": [ 1129 | "BSD-3-Clause" 1130 | ], 1131 | "authors": [ 1132 | { 1133 | "name": "Sebastian Bergmann", 1134 | "email": "sebastian@phpunit.de" 1135 | }, 1136 | { 1137 | "name": "Jeff Welch", 1138 | "email": "whatthejeff@gmail.com" 1139 | }, 1140 | { 1141 | "name": "Volker Dusch", 1142 | "email": "github@wallbash.com" 1143 | }, 1144 | { 1145 | "name": "Adam Harvey", 1146 | "email": "aharvey@php.net" 1147 | }, 1148 | { 1149 | "name": "Bernhard Schussek", 1150 | "email": "bschussek@gmail.com" 1151 | } 1152 | ], 1153 | "description": "Provides the functionality to export PHP variables for visualization", 1154 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1155 | "keywords": [ 1156 | "export", 1157 | "exporter" 1158 | ], 1159 | "funding": [ 1160 | { 1161 | "url": "https://github.com/sebastianbergmann", 1162 | "type": "github" 1163 | } 1164 | ], 1165 | "time": "2021-11-11T13:51:24+00:00" 1166 | }, 1167 | { 1168 | "name": "sebastian/global-state", 1169 | "version": "2.0.0", 1170 | "source": { 1171 | "type": "git", 1172 | "url": "https://github.com/sebastianbergmann/global-state.git", 1173 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 1174 | }, 1175 | "dist": { 1176 | "type": "zip", 1177 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1178 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1179 | "shasum": "" 1180 | }, 1181 | "require": { 1182 | "php": "^7.0" 1183 | }, 1184 | "require-dev": { 1185 | "phpunit/phpunit": "^6.0" 1186 | }, 1187 | "suggest": { 1188 | "ext-uopz": "*" 1189 | }, 1190 | "type": "library", 1191 | "extra": { 1192 | "branch-alias": { 1193 | "dev-master": "2.0-dev" 1194 | } 1195 | }, 1196 | "autoload": { 1197 | "classmap": [ 1198 | "src/" 1199 | ] 1200 | }, 1201 | "notification-url": "https://packagist.org/downloads/", 1202 | "license": [ 1203 | "BSD-3-Clause" 1204 | ], 1205 | "authors": [ 1206 | { 1207 | "name": "Sebastian Bergmann", 1208 | "email": "sebastian@phpunit.de" 1209 | } 1210 | ], 1211 | "description": "Snapshotting of global state", 1212 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1213 | "keywords": [ 1214 | "global state" 1215 | ], 1216 | "time": "2017-04-27T15:39:26+00:00" 1217 | }, 1218 | { 1219 | "name": "sebastian/object-enumerator", 1220 | "version": "3.0.4", 1221 | "source": { 1222 | "type": "git", 1223 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1224 | "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" 1225 | }, 1226 | "dist": { 1227 | "type": "zip", 1228 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", 1229 | "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", 1230 | "shasum": "" 1231 | }, 1232 | "require": { 1233 | "php": ">=7.0", 1234 | "sebastian/object-reflector": "^1.1.1", 1235 | "sebastian/recursion-context": "^3.0" 1236 | }, 1237 | "require-dev": { 1238 | "phpunit/phpunit": "^6.0" 1239 | }, 1240 | "type": "library", 1241 | "extra": { 1242 | "branch-alias": { 1243 | "dev-master": "3.0.x-dev" 1244 | } 1245 | }, 1246 | "autoload": { 1247 | "classmap": [ 1248 | "src/" 1249 | ] 1250 | }, 1251 | "notification-url": "https://packagist.org/downloads/", 1252 | "license": [ 1253 | "BSD-3-Clause" 1254 | ], 1255 | "authors": [ 1256 | { 1257 | "name": "Sebastian Bergmann", 1258 | "email": "sebastian@phpunit.de" 1259 | } 1260 | ], 1261 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1262 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1263 | "funding": [ 1264 | { 1265 | "url": "https://github.com/sebastianbergmann", 1266 | "type": "github" 1267 | } 1268 | ], 1269 | "time": "2020-11-30T07:40:27+00:00" 1270 | }, 1271 | { 1272 | "name": "sebastian/object-reflector", 1273 | "version": "1.1.2", 1274 | "source": { 1275 | "type": "git", 1276 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1277 | "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" 1278 | }, 1279 | "dist": { 1280 | "type": "zip", 1281 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", 1282 | "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", 1283 | "shasum": "" 1284 | }, 1285 | "require": { 1286 | "php": ">=7.0" 1287 | }, 1288 | "require-dev": { 1289 | "phpunit/phpunit": "^6.0" 1290 | }, 1291 | "type": "library", 1292 | "extra": { 1293 | "branch-alias": { 1294 | "dev-master": "1.1-dev" 1295 | } 1296 | }, 1297 | "autoload": { 1298 | "classmap": [ 1299 | "src/" 1300 | ] 1301 | }, 1302 | "notification-url": "https://packagist.org/downloads/", 1303 | "license": [ 1304 | "BSD-3-Clause" 1305 | ], 1306 | "authors": [ 1307 | { 1308 | "name": "Sebastian Bergmann", 1309 | "email": "sebastian@phpunit.de" 1310 | } 1311 | ], 1312 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1313 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1314 | "funding": [ 1315 | { 1316 | "url": "https://github.com/sebastianbergmann", 1317 | "type": "github" 1318 | } 1319 | ], 1320 | "time": "2020-11-30T07:37:18+00:00" 1321 | }, 1322 | { 1323 | "name": "sebastian/recursion-context", 1324 | "version": "3.0.1", 1325 | "source": { 1326 | "type": "git", 1327 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1328 | "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" 1329 | }, 1330 | "dist": { 1331 | "type": "zip", 1332 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", 1333 | "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", 1334 | "shasum": "" 1335 | }, 1336 | "require": { 1337 | "php": ">=7.0" 1338 | }, 1339 | "require-dev": { 1340 | "phpunit/phpunit": "^6.0" 1341 | }, 1342 | "type": "library", 1343 | "extra": { 1344 | "branch-alias": { 1345 | "dev-master": "3.0.x-dev" 1346 | } 1347 | }, 1348 | "autoload": { 1349 | "classmap": [ 1350 | "src/" 1351 | ] 1352 | }, 1353 | "notification-url": "https://packagist.org/downloads/", 1354 | "license": [ 1355 | "BSD-3-Clause" 1356 | ], 1357 | "authors": [ 1358 | { 1359 | "name": "Sebastian Bergmann", 1360 | "email": "sebastian@phpunit.de" 1361 | }, 1362 | { 1363 | "name": "Jeff Welch", 1364 | "email": "whatthejeff@gmail.com" 1365 | }, 1366 | { 1367 | "name": "Adam Harvey", 1368 | "email": "aharvey@php.net" 1369 | } 1370 | ], 1371 | "description": "Provides functionality to recursively process PHP variables", 1372 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1373 | "funding": [ 1374 | { 1375 | "url": "https://github.com/sebastianbergmann", 1376 | "type": "github" 1377 | } 1378 | ], 1379 | "time": "2020-11-30T07:34:24+00:00" 1380 | }, 1381 | { 1382 | "name": "sebastian/resource-operations", 1383 | "version": "2.0.2", 1384 | "source": { 1385 | "type": "git", 1386 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1387 | "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" 1388 | }, 1389 | "dist": { 1390 | "type": "zip", 1391 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", 1392 | "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", 1393 | "shasum": "" 1394 | }, 1395 | "require": { 1396 | "php": ">=7.1" 1397 | }, 1398 | "type": "library", 1399 | "extra": { 1400 | "branch-alias": { 1401 | "dev-master": "2.0-dev" 1402 | } 1403 | }, 1404 | "autoload": { 1405 | "classmap": [ 1406 | "src/" 1407 | ] 1408 | }, 1409 | "notification-url": "https://packagist.org/downloads/", 1410 | "license": [ 1411 | "BSD-3-Clause" 1412 | ], 1413 | "authors": [ 1414 | { 1415 | "name": "Sebastian Bergmann", 1416 | "email": "sebastian@phpunit.de" 1417 | } 1418 | ], 1419 | "description": "Provides a list of PHP built-in functions that operate on resources", 1420 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1421 | "funding": [ 1422 | { 1423 | "url": "https://github.com/sebastianbergmann", 1424 | "type": "github" 1425 | } 1426 | ], 1427 | "time": "2020-11-30T07:30:19+00:00" 1428 | }, 1429 | { 1430 | "name": "sebastian/version", 1431 | "version": "2.0.1", 1432 | "source": { 1433 | "type": "git", 1434 | "url": "https://github.com/sebastianbergmann/version.git", 1435 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1436 | }, 1437 | "dist": { 1438 | "type": "zip", 1439 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1440 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1441 | "shasum": "" 1442 | }, 1443 | "require": { 1444 | "php": ">=5.6" 1445 | }, 1446 | "type": "library", 1447 | "extra": { 1448 | "branch-alias": { 1449 | "dev-master": "2.0.x-dev" 1450 | } 1451 | }, 1452 | "autoload": { 1453 | "classmap": [ 1454 | "src/" 1455 | ] 1456 | }, 1457 | "notification-url": "https://packagist.org/downloads/", 1458 | "license": [ 1459 | "BSD-3-Clause" 1460 | ], 1461 | "authors": [ 1462 | { 1463 | "name": "Sebastian Bergmann", 1464 | "email": "sebastian@phpunit.de", 1465 | "role": "lead" 1466 | } 1467 | ], 1468 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1469 | "homepage": "https://github.com/sebastianbergmann/version", 1470 | "time": "2016-10-03T07:35:21+00:00" 1471 | }, 1472 | { 1473 | "name": "theseer/tokenizer", 1474 | "version": "1.2.1", 1475 | "source": { 1476 | "type": "git", 1477 | "url": "https://github.com/theseer/tokenizer.git", 1478 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 1479 | }, 1480 | "dist": { 1481 | "type": "zip", 1482 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 1483 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 1484 | "shasum": "" 1485 | }, 1486 | "require": { 1487 | "ext-dom": "*", 1488 | "ext-tokenizer": "*", 1489 | "ext-xmlwriter": "*", 1490 | "php": "^7.2 || ^8.0" 1491 | }, 1492 | "type": "library", 1493 | "autoload": { 1494 | "classmap": [ 1495 | "src/" 1496 | ] 1497 | }, 1498 | "notification-url": "https://packagist.org/downloads/", 1499 | "license": [ 1500 | "BSD-3-Clause" 1501 | ], 1502 | "authors": [ 1503 | { 1504 | "name": "Arne Blankerts", 1505 | "email": "arne@blankerts.de", 1506 | "role": "Developer" 1507 | } 1508 | ], 1509 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1510 | "funding": [ 1511 | { 1512 | "url": "https://github.com/theseer", 1513 | "type": "github" 1514 | } 1515 | ], 1516 | "time": "2021-07-28T10:34:58+00:00" 1517 | }, 1518 | { 1519 | "name": "webmozart/assert", 1520 | "version": "1.11.0", 1521 | "source": { 1522 | "type": "git", 1523 | "url": "https://github.com/webmozarts/assert.git", 1524 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" 1525 | }, 1526 | "dist": { 1527 | "type": "zip", 1528 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", 1529 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", 1530 | "shasum": "" 1531 | }, 1532 | "require": { 1533 | "ext-ctype": "*", 1534 | "php": "^7.2 || ^8.0" 1535 | }, 1536 | "conflict": { 1537 | "phpstan/phpstan": "<0.12.20", 1538 | "vimeo/psalm": "<4.6.1 || 4.6.2" 1539 | }, 1540 | "require-dev": { 1541 | "phpunit/phpunit": "^8.5.13" 1542 | }, 1543 | "type": "library", 1544 | "extra": { 1545 | "branch-alias": { 1546 | "dev-master": "1.10-dev" 1547 | } 1548 | }, 1549 | "autoload": { 1550 | "psr-4": { 1551 | "Webmozart\\Assert\\": "src/" 1552 | } 1553 | }, 1554 | "notification-url": "https://packagist.org/downloads/", 1555 | "license": [ 1556 | "MIT" 1557 | ], 1558 | "authors": [ 1559 | { 1560 | "name": "Bernhard Schussek", 1561 | "email": "bschussek@gmail.com" 1562 | } 1563 | ], 1564 | "description": "Assertions to validate method input/output with nice error messages.", 1565 | "keywords": [ 1566 | "assert", 1567 | "check", 1568 | "validate" 1569 | ], 1570 | "time": "2022-06-03T18:03:27+00:00" 1571 | } 1572 | ], 1573 | "aliases": [], 1574 | "minimum-stability": "stable", 1575 | "stability-flags": [], 1576 | "prefer-stable": false, 1577 | "prefer-lowest": false, 1578 | "platform": { 1579 | "php": "^7.1" 1580 | }, 1581 | "platform-dev": [], 1582 | "plugin-api-version": "1.1.0" 1583 | } 1584 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | ./test/ 8 | 9 | 10 | 11 | 12 | 13 | ./src/ 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/ClassInfo.php: -------------------------------------------------------------------------------- 1 | name = $name; 17 | $this->parent = $parent; 18 | } 19 | } -------------------------------------------------------------------------------- /src/CliParser.php: -------------------------------------------------------------------------------- 1 | = $c) { 15 | throw new \Exception('--php must be followed by version'); 16 | } 17 | $phpVersion = $argv[$i]; 18 | } else if ($arg[0] === '-') { 19 | $flags[] = $arg; 20 | } else { 21 | $rest[] = $arg; 22 | } 23 | } 24 | 25 | $options = Options::fromPhpVersion($phpVersion); 26 | foreach ($flags as $flag) { 27 | if (!$options->setCliOption($flag)) { 28 | throw new \Exception("Unknown option $flag"); 29 | } 30 | } 31 | return [$options, $rest]; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Context.php: -------------------------------------------------------------------------------- 1 | [ClassName]] 17 | // The reason why this is separate from ClassInfo is that we need to record trait 18 | // pseudo-parents possibly prior to seeing the trait. 19 | public $parents = []; 20 | 21 | public function setFileContext(FileContext $file) { 22 | $this->currentFile = $file; 23 | } 24 | 25 | public function getClassKey(ClassLike $node): string { 26 | if (isset($node->namespacedName)) { 27 | return $node->namespacedName->toLowerString(); 28 | } 29 | return "anon#{$this->currentFile->path}#{$node->getStartFilePos()}"; 30 | } 31 | 32 | public function addClassInfo(string $key, ClassInfo $info) { 33 | $this->classInfos[$key] = $info; 34 | } 35 | 36 | public function getFunctionInfoForMethod(string $classKey, string $method) : ?FunctionInfo { 37 | $lowerMethod = strtolower($method); 38 | $classInfo = $this->classInfos[$classKey] ?? null; 39 | $typeInfo = $classInfo->funcInfos[$lowerMethod] ?? null; 40 | if ($lowerMethod === '__construct') { 41 | // __construct is excluded from LSP 42 | return $typeInfo; 43 | } 44 | 45 | $inheritedFunctionInfo = $this->getInheritedFunctionInfo($classKey, $method); 46 | if (null === $typeInfo) { 47 | return $inheritedFunctionInfo; 48 | } 49 | 50 | if (null === $inheritedFunctionInfo) { 51 | return $typeInfo; 52 | } 53 | 54 | return $this->mergeFunctionInfo($typeInfo, $inheritedFunctionInfo); 55 | } 56 | 57 | public function getPropertyType(string $classKey, string $property) : ?Type { 58 | $classInfo = $this->classInfos[$classKey] ?? null; 59 | $type = $classInfo->propTypes[$property] ?? null; 60 | $inheritedType = $this->getInheritedPropertyType($classKey, $property); 61 | if ($type === null) { 62 | return $inheritedType; 63 | } 64 | // TODO Handle invariance 65 | return $type; 66 | } 67 | 68 | private function getInheritedPropertyType(string $classKey, string $property) : ?Type { 69 | $parents = $this->parents[$classKey] ?? []; 70 | foreach ($parents as $parent) { 71 | if (!$this->isKnownClass($parent)) { 72 | // Could get property type from reflection here, but there are no internal classes using property types 73 | // right now... 74 | continue; 75 | } 76 | 77 | $type = $this->getPropertyType(strtolower($parent), $property); 78 | if (null !== $type) { 79 | return $type; 80 | } 81 | } 82 | return null; 83 | } 84 | 85 | private function getInheritedFunctionInfo(string $classKey, string $method) : ?FunctionInfo { 86 | $parents = $this->parents[$classKey] ?? []; 87 | foreach ($parents as $parent) { 88 | if (!$this->isKnownClass($parent)) { 89 | $typeInfo = $this->getReflectionFunctionInfo($parent, $method); 90 | if (null !== $typeInfo) { 91 | return $typeInfo; 92 | } 93 | } 94 | 95 | $typeInfo = $this->getFunctionInfoForMethod(strtolower($parent), $method); 96 | if (null !== $typeInfo) { 97 | return $this->resolveFunctionInfoTypes($typeInfo); 98 | } 99 | } 100 | return null; 101 | } 102 | 103 | private function getReflectionFunctionInfo(string $class, string $method) : ?FunctionInfo { 104 | try { 105 | $r = new \ReflectionMethod($class, $method); 106 | } catch (\Exception $e) { 107 | return null; 108 | } 109 | 110 | $paramTypes = []; 111 | foreach ($r->getParameters() as $param) { 112 | $isNullable = $param->allowsNull(); 113 | if ($param->isArray()) { 114 | $type = Type::fromString('array', $isNullable); 115 | } else if ($param->isCallable()) { 116 | $type = Type::fromString('callable', $isNullable); 117 | } else if (null !== $class = $param->getClass()) { 118 | $type = Type::fromString($class->name, $isNullable); 119 | } else { 120 | $type = null; 121 | } 122 | $paramTypes[] = $type; 123 | } 124 | 125 | return new FunctionInfo($paramTypes, null); 126 | } 127 | 128 | private function resolveFunctionInfoTypes(FunctionInfo $info) : FunctionInfo { 129 | return new FunctionInfo( 130 | array_map(function(?Type $type) { 131 | return $type !== null ? $type->asResolvedType() : null; 132 | }, $info->paramTypes), 133 | $info->returnType !== null ? $info->returnType->asResolvedType() : null 134 | ); 135 | } 136 | 137 | private function mergeFunctionInfo(FunctionInfo $child, FunctionInfo $parent) : FunctionInfo { 138 | $paramTypes = $this->mergeParamTypesArray($parent->paramTypes, $child->paramTypes); 139 | $returnType = $this->mergeReturnTypes($parent->returnType, $child->returnType); 140 | 141 | return new FunctionInfo($paramTypes, $returnType); 142 | } 143 | 144 | private function mergeParamTypesArray(array $parentTypes, array $childTypes) : array { 145 | $resultTypes = []; 146 | foreach ($childTypes as $i => $childType) { 147 | if (isset($parentTypes[$i])) { 148 | $resultTypes[$i] = $this->mergeParamTypes($parentTypes[$i], $childType); 149 | } else { 150 | $resultTypes[$i] = $childType; 151 | } 152 | } 153 | return $resultTypes; 154 | } 155 | 156 | private function mergeParamTypes(?Type $parent, ?Type $child) : ?Type { 157 | if ($this->isSubtype($parent, $child) && $child !== null) { 158 | return $child; 159 | } 160 | return $parent; 161 | } 162 | 163 | private function mergeReturnTypes(?Type $parent, ?Type $child) : ?Type { 164 | if ($this->isSubtype($child, $parent)) { 165 | return $child; 166 | } 167 | return $parent; 168 | } 169 | 170 | private function isSubtype(?Type $a, ?Type $b) : bool { 171 | if ($b === null) { 172 | // No type means "mixed", of which everything is a subtype 173 | return true; 174 | } 175 | if ($a === null) { 176 | // "mixed" is not a subtype of anything but "mixed" 177 | return false; 178 | } 179 | 180 | if ($a->isNullable && !$b->isNullable) { 181 | // Nullable is not a subtype of non-nullable 182 | return false; 183 | } 184 | 185 | $a = $a->asNotNullable(); 186 | $b = $b->asNotNullable(); 187 | 188 | if ($a->name === $b->name) { 189 | return true; 190 | } 191 | 192 | if ($b->name === 'iterable') { 193 | return $a->name === 'array'; 194 | } 195 | 196 | if ($b->name === 'object') { 197 | return $a->isClassHint(); 198 | } 199 | 200 | // We don't check for class subtypes, as PHP does not support this in LSP checks 201 | return false; 202 | } 203 | 204 | private function isKnownClass(string $name) : bool { 205 | return isset($this->parents[strtolower($name)]); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/ContextCollector.php: -------------------------------------------------------------------------------- 1 | extractor = $extractor; 21 | $this->context = $context; 22 | } 23 | 24 | public function enterNode(Node $node) { 25 | if ($node instanceof ClassLike) { 26 | $this->handleClassLike($node); 27 | } else if ($node instanceof ClassMethod) { 28 | $this->handleClassMethod($node); 29 | } else if ($node instanceof TraitUse) { 30 | foreach ($node->traits as $trait) { 31 | $this->handleTraitUse($trait); 32 | } 33 | } else if ($node instanceof Property) { 34 | $this->handleProperty($node); 35 | } 36 | } 37 | 38 | private function handleClassLike(ClassLike $node) { 39 | $parent = null; 40 | if ($node instanceof Class_) { 41 | $parent = $node->extends ? $node->extends->toString() : null; 42 | } 43 | 44 | $key = $this->context->getClassKey($node); 45 | $this->classNode = $node; 46 | $this->classInfo = new ClassInfo( 47 | isset($node->namespacedName) ? $node->namespacedName->toString() : '', 48 | $parent 49 | ); 50 | $this->context->addClassInfo($key, $this->classInfo); 51 | 52 | $this->context->parents[$key] = $this->getParentsOf($node); 53 | } 54 | 55 | private function handleClassMethod(ClassMethod $node) { 56 | $docComment = $node->getDocComment(); 57 | if ($docComment === null 58 | || strContains($docComment->getText(), '{@inheritDoc}') 59 | ) { 60 | // For our purposes an inherited comment is the same as no comment 61 | return; 62 | } 63 | 64 | $this->classInfo->funcInfos[$node->name->toLowerString()] 65 | = $this->extractor->extractFunctionInfo($node->params, $docComment->getText(), $this->classInfo); 66 | } 67 | 68 | private function handleTraitUse(Name $name) { 69 | $lowerName = $name->toLowerString(); 70 | 71 | // Treat parents of the using class as parents of the trait, because it 72 | // will have to satisfy signatures of those parent methods 73 | $this->context->parents[$lowerName] = array_unique(array_merge( 74 | $this->context->parents[$lowerName] ?? [], 75 | $this->getParentsOf($this->classNode) 76 | )); 77 | } 78 | 79 | private function handleProperty(Property $node) { 80 | $docComment = $node->getDocComment(); 81 | if ($docComment === null) { 82 | return; 83 | } 84 | 85 | if (count($node->props) !== 1) { 86 | return; 87 | } 88 | 89 | $prop = $node->props[0]; 90 | $this->classInfo->propTypes[$prop->name->toString()] 91 | = $this->extractor->getPropertyType($docComment->getText(), $this->classInfo); 92 | } 93 | 94 | private function getParentsOf(ClassLike $node) : array { 95 | $parents = []; 96 | if ($node instanceof Class_) { 97 | if (null !== $node->extends) { 98 | $parents[] = $node->extends->toString(); 99 | } 100 | foreach ($node->implements as $interface) { 101 | $parents[] = $interface->toString(); 102 | } 103 | } else if ($node instanceof Interface_) { 104 | foreach ($node->extends as $interface) { 105 | $parents[] = $interface->toString(); 106 | } 107 | } 108 | return $parents; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/FileContext.php: -------------------------------------------------------------------------------- 1 | path = $path; 17 | $this->code = $code; 18 | $this->stmts = $stmts; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/FunctionInfo.php: -------------------------------------------------------------------------------- 1 | paramTypes = $paramTypes; 15 | $this->returnType = $returnType; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/MutableString.php: -------------------------------------------------------------------------------- 1 | string = $string; 15 | } 16 | 17 | public function insert(int $pos, string $newString) : void { 18 | $this->modifications[] = [$pos, 0, $newString]; 19 | } 20 | 21 | public function remove(int $pos, int $len) : void { 22 | $this->modifications[] = [$pos, $len, '']; 23 | } 24 | 25 | public function indexOf(string $str, int $startPos) /* : int|false */ { 26 | return strpos($this->string, $str, $startPos); 27 | } 28 | 29 | public function getOrigString() : string { 30 | return $this->string; 31 | } 32 | 33 | public function getModifiedString() : string { 34 | // Sort by position 35 | usort($this->modifications, function($a, $b) { 36 | return $a[0] <=> $b[0]; 37 | }); 38 | 39 | $result = ''; 40 | $startPos = 0; 41 | foreach ($this->modifications as list($pos, $len, $newString)) { 42 | $result .= substr($this->string, $startPos, $pos - $startPos); 43 | $result .= $newString; 44 | $startPos = $pos + $len; 45 | } 46 | $result .= substr($this->string, $startPos); 47 | return $result; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/MutatingVisitor.php: -------------------------------------------------------------------------------- 1 | code = $code; 16 | } 17 | 18 | // TODO: Move this somewhere more appropriate 19 | protected function getReturnTypeHintPos(Node\FunctionLike $funcNode) : int { 20 | // Start looking at maximum known position in function signature 21 | $maxPos = $funcNode->getStartFilePos(); 22 | foreach ($funcNode->getParams() as $param) { 23 | $maxPos = $param->getEndFilePos() + 1; 24 | } 25 | 26 | // And find the closing parentheses of the signature 27 | $pos = $this->code->indexOf(')', $maxPos); 28 | assert(false !== $pos); 29 | return $pos + 1; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/NoDynamicProperties.php: -------------------------------------------------------------------------------- 1 | throwDynamicPropertyError($name); 8 | } 9 | public function __set($name, $value) { 10 | $this->throwDynamicPropertyError($name); 11 | } 12 | 13 | private function throwDynamicPropertyError(string $name) { 14 | throw new \RuntimeException("Property \"$name\" does not exist"); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/Options.php: -------------------------------------------------------------------------------- 1 | =')) { 28 | $options->strictTypes = true; 29 | } 30 | 31 | if (version_compare($version, '7.1', '>=')) { 32 | $options->nullableTypes = true; 33 | $options->iterable = true; 34 | } 35 | 36 | if (version_compare($version, '7.2', '>=')) { 37 | $options->object = true; 38 | } 39 | 40 | if (version_compare($version, '7.4', '>=')) { 41 | $options->propertyTypes = true; 42 | } 43 | 44 | return $options; 45 | } 46 | 47 | public function setCliOption(string $option): bool { 48 | $knownOptions = [ 49 | 'strict-types' => 'strictTypes', 50 | 'nullable-types' => 'nullableTypes', 51 | 'iterable' => 'iterable', 52 | 'object' => 'object', 53 | 'property-types' => 'propertyTypes', 54 | ]; 55 | 56 | if (!preg_match('/--(no-)?([a-z-]+)/', $option, $matches)) { 57 | return false; 58 | } 59 | 60 | $option = $matches[2]; 61 | if (!isset($knownOptions[$option])) { 62 | return false; 63 | } 64 | 65 | $this->{$knownOptions[$option]} = $matches[1] === ''; 66 | return true; 67 | } 68 | } -------------------------------------------------------------------------------- /src/Type.php: -------------------------------------------------------------------------------- 1 | name = $name; 17 | $this->resolvedName = $resolvedName; 18 | $this->isNullable = $isNullable; 19 | } 20 | 21 | public static function fromString(string $name, bool $isNullable) { 22 | return new self($name, $name, $isNullable); 23 | } 24 | 25 | public function isClassHint() : bool { 26 | switch ($this->name) { 27 | case 'bool': 28 | case 'int': 29 | case 'float': 30 | case 'string': 31 | case 'array': 32 | case 'callable': 33 | case 'iterable': 34 | case 'object': 35 | return false; 36 | default: 37 | return true; 38 | } 39 | } 40 | 41 | public function asNotNullable() : self { 42 | if (!$this->isNullable) { 43 | return $this; 44 | } 45 | return new self($this->name, $this->resolvedName, false); 46 | } 47 | 48 | public function asResolvedType() : self { 49 | if ($this->name === $this->resolvedName) { 50 | return $this; 51 | } 52 | return new self($this->resolvedName, $this->resolvedName, $this->isNullable); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/TypeAnnotationVisitor.php: -------------------------------------------------------------------------------- 1 | context = $context; 19 | $this->extractor = $extractor; 20 | $this->options = $options; 21 | } 22 | 23 | public function enterNode(Node $node) { 24 | if ($node instanceof Stmt\ClassLike) { 25 | $this->classNode = $node; 26 | return; 27 | } 28 | 29 | if ($node instanceof Node\FunctionLike) { 30 | $this->enterFunctionLike($node); 31 | return; 32 | } 33 | 34 | if ($node instanceof Stmt\Property) { 35 | $this->enterProperty($node); 36 | return; 37 | } 38 | } 39 | 40 | private function enterFunctionLike(Node\FunctionLike $node): void { 41 | $typeInfo = $this->getFunctionInfo($node); 42 | if (null === $typeInfo) { 43 | return; 44 | } 45 | 46 | $paramTypes = $typeInfo->paramTypes; 47 | foreach ($node->getParams() as $i => $param) { 48 | if ($param->type !== null) { 49 | // Already has a typehint, leave it alone 50 | continue; 51 | } 52 | 53 | $type = $paramTypes[$i]; 54 | if (null === $type) { 55 | // No type information for this param, or too complex type 56 | continue; 57 | } 58 | 59 | if ($type->isNullable) { 60 | $default = $param->default; 61 | if ($default !== null && $this->isNullConstant($default)) { 62 | // Type is nullable and has null default. We can add the type 63 | // as a non-nullable type in this case, which is compatible with PHP 7.0 64 | $type = $type->asNotNullable(); 65 | } else if (!$this->options->nullableTypes) { 66 | continue; 67 | } 68 | } 69 | 70 | if (!$this->isTypeSupported($type)) { 71 | continue; 72 | } 73 | 74 | $startPos = $param->getStartFilePos(); 75 | $this->code->insert($startPos, $this->extractor->getTypeDisplayName($type) . ' '); 76 | } 77 | 78 | $returnType = $typeInfo->returnType; 79 | if (null === $returnType || null !== $node->getReturnType()) { 80 | return; 81 | } 82 | 83 | if ($returnType->isNullable && !$this->options->nullableTypes) { 84 | return; 85 | } 86 | 87 | if (!$this->isTypeSupported($returnType)) { 88 | return; 89 | } 90 | 91 | $pos = $this->getReturnTypeHintPos($node); 92 | $this->code->insert($pos, ': ' . $this->extractor->getTypeDisplayName($returnType)); 93 | } 94 | 95 | private function enterProperty(Stmt\Property $node) { 96 | if ($node->type !== null || !$this->options->propertyTypes) { 97 | // Already has a type or property types not desired. 98 | return; 99 | } 100 | 101 | if (count($node->props) !== 1) { 102 | // We don't handle multi-properties 103 | return; 104 | } 105 | 106 | $prop = $node->props[0]; 107 | $type = $this->context->getPropertyType( 108 | $this->context->getClassKey($this->classNode), $prop->name->toString()); 109 | 110 | // callable is not allowed in property types 111 | if ($type === null || !$this->isTypeSupported($type) || $type->name === 'callable') { 112 | return; 113 | } 114 | 115 | $pos = $prop->getStartFilePos(); 116 | $this->code->insert($pos, $this->extractor->getTypeDisplayName($type) . ' '); 117 | } 118 | 119 | private function getFunctionInfo(Node\FunctionLike $node) : ?FunctionInfo { 120 | if ($node instanceof Stmt\ClassMethod) { 121 | return $this->context->getFunctionInfoForMethod( 122 | $this->context->getClassKey($this->classNode), (string) $node->name); 123 | } 124 | 125 | $docComment = $node->getDocComment(); 126 | if (null !== $docComment) { 127 | return $this->extractor->extractFunctionInfo($node->getParams(), $docComment->getText(), null); 128 | } 129 | 130 | return null; 131 | } 132 | 133 | private function isNullConstant(Node\Expr $node) : bool { 134 | return $node instanceof Node\Expr\ConstFetch 135 | && strtolower($node->name->toString()) === 'null'; 136 | } 137 | 138 | private function isTypeSupported(Type $type) { 139 | if ($type->name === 'iterable' && !$this->options->iterable) { 140 | return false; 141 | } 142 | if ($type->name === 'object' && !$this->options->object) { 143 | return false; 144 | } 145 | return true; 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /src/TypeExtractor.php: -------------------------------------------------------------------------------- 1 | nameResolver = $nameResolver; 17 | } 18 | 19 | public function extractFunctionInfo(array $params, string $docComment, ?ClassInfo $classInfo) : FunctionInfo { 20 | $namedParamTypes = $this->getNamedParamTypes($docComment, $classInfo); 21 | return new FunctionInfo( 22 | $this->getParamTypes($params, $namedParamTypes), 23 | $this->getReturnType($docComment, $classInfo) 24 | ); 25 | } 26 | 27 | public function getPropertyType(string $docComment, ClassInfo $classInfo) : ?Type { 28 | if (!preg_match('/@var\s+(\S+)/', $docComment, $matches)) { 29 | return null; 30 | } 31 | 32 | return $this->parseType($matches[1], $classInfo); 33 | } 34 | 35 | public function getTypeDisplayName(Type $type) : string { 36 | $prefix = $type->isNullable ? '?' : ''; 37 | return $prefix . $this->getBaseTypeDisplayName($type); 38 | } 39 | 40 | private function getBaseTypeDisplayName(Type $type) : string { 41 | if (!$type->isClassHint()) { 42 | return $type->name; 43 | } 44 | 45 | $nameContext = $this->nameResolver->getNameContext(); 46 | return $nameContext->getShortName($type->name, Use_::TYPE_NORMAL)->toCodeString(); 47 | } 48 | 49 | /** 50 | * @param Param[] $params 51 | * @param Type[] $namedParamTypes 52 | * @return Type[] 53 | */ 54 | private function getParamTypes(array $params, array $namedParamTypes) : array { 55 | $paramTypes = []; 56 | foreach ($params as $param) { 57 | $paramTypes[] = $namedParamTypes[$param->var->name] ?? null; 58 | } 59 | return $paramTypes; 60 | } 61 | 62 | private function getNamedParamTypes(string $docComment, ?ClassInfo $classInfo) : array { 63 | if (!preg_match_all('/@param\s+(\S+)\s+\$(\S+)/', $docComment, $matches, PREG_SET_ORDER)) { 64 | return []; 65 | } 66 | 67 | $paramTypes = []; 68 | foreach ($matches as list(, $typeString, $name)) { 69 | $typeInfo = $this->parseType($typeString, $classInfo); 70 | if (null !== $typeInfo) { 71 | $paramTypes[$name] = $typeInfo; 72 | } 73 | } 74 | 75 | return $paramTypes; 76 | } 77 | 78 | private function getReturnType(string $docComment, ?ClassInfo $classInfo) : ?Type { 79 | if (!preg_match('/@return\s+(\S+)/', $docComment, $matches)) { 80 | return null; 81 | } 82 | 83 | return $this->parseType($matches[1], $classInfo); 84 | } 85 | 86 | private function parseType(string $typeString, ?ClassInfo $classInfo) : ?Type { 87 | $types = explode('|', $typeString); 88 | $resultType = null; 89 | $isNullable = false; 90 | 91 | foreach ($types as $type) { 92 | if ($type === 'null') { 93 | $isNullable = true; 94 | continue; 95 | } 96 | 97 | if (substr($type, -2) === '[]') { 98 | $type = 'array'; 99 | } 100 | 101 | if (!$this->isSupportedTypeName($type)) { 102 | return null; 103 | } 104 | 105 | $type = $this->getCanonicalTypeName($type); 106 | 107 | if ($resultType !== null) { 108 | // Promote array|Traversable to iterable 109 | if (($resultType === 'array' && $type === 'Traversable') || 110 | ($type === 'array' && $resultType === 'Traversable')) { 111 | $resultType = 'iterable'; 112 | continue; 113 | } 114 | 115 | // Don't support union types 116 | return null; 117 | } 118 | 119 | $resultType = $type; 120 | } 121 | 122 | if (null === $resultType) { 123 | // Happens if type string is "null" 124 | return null; 125 | } 126 | 127 | $resolvedType = $resultType; 128 | if ($classInfo !== null) { 129 | if ($resultType === 'self') { 130 | $resolvedType = $classInfo->name; 131 | } else if ($resultType === 'parent' && $classInfo->parent !== null) { 132 | $resolvedType = $classInfo->parent; 133 | } 134 | } 135 | return new Type($resultType, $resolvedType, $isNullable); 136 | } 137 | 138 | private function isSupportedTypeName(string $name) : bool { 139 | return preg_match('/^[a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\\\\]*$/', $name) === 1 140 | && $name !== 'mixed' && $name !== 'void' && $name !== 'static'; 141 | } 142 | 143 | private function getCanonicalTypeName(string $name) : string { 144 | switch (strtolower($name)) { 145 | case 'bool': 146 | case 'boolean': 147 | return 'bool'; 148 | case 'int': 149 | case 'integer': 150 | return 'int'; 151 | case 'float': 152 | case 'double': 153 | return 'float'; 154 | case 'string': 155 | return 'string'; 156 | case 'array': 157 | return 'array'; 158 | case 'callable': 159 | return 'callable'; 160 | case 'iterable': 161 | return 'iterable'; 162 | case 'object': 163 | return 'object'; 164 | default: 165 | if ($name[0] === '\\') { 166 | return substr($name, 1); 167 | } 168 | 169 | $nameContext = $this->nameResolver->getNameContext(); 170 | $name = new Name($name); 171 | return $nameContext->getResolvedClassName($name)->toString(); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/TypeRemovalVisitor.php: -------------------------------------------------------------------------------- 1 | returnType) { 19 | $startPos = $this->getReturnTypeHintPos($node); 20 | $this->code->remove($startPos, $this->getTypeHintLength($startPos, false)); 21 | } 22 | 23 | foreach ($node->params as $param) { 24 | if (null === $param->type) { 25 | continue; 26 | } 27 | 28 | // Handle special case of nullable type that has null default 29 | // In this case it's enough to remove the explicit "?" 30 | if ($param->type instanceof Node\NullableType 31 | && $param->default && $this->isNullConstant($param->default) 32 | && !$this->isScalarType($param->type->type) 33 | ) { 34 | $startPos = $param->getStartFilePos(); 35 | $result = preg_match('/\?\s*/', $this->code->getOrigString(), $matches, 0, $startPos); 36 | assert($result === 1); 37 | $this->code->remove($startPos, strlen($matches[0])); 38 | continue; 39 | } 40 | 41 | if ($param->type instanceof Node\NullableType || $this->isScalarType($param->type)) { 42 | $startPos = $param->getStartFilePos(); 43 | $this->code->remove($startPos, $this->getTypeHintLength($startPos, true)); 44 | } 45 | } 46 | } 47 | 48 | private function isScalarType($type) : bool { 49 | return $type instanceof Node\Identifier 50 | && in_array((string) $type, ['bool', 'int', 'float', 'string']); 51 | } 52 | 53 | private function getTypeHintLength(int $startPos, bool $withTrailingWhitespace) : int { 54 | $code = $this->code->getOrigString(); 55 | // Capture typehint, skipping characters at the start 56 | $trailing = $withTrailingWhitespace ? '\s*' : ''; 57 | $result = preg_match( 58 | '/.*?(?:\?\s*)?[a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\\\\]*' . $trailing . '/', 59 | $code, $matches, 0, $startPos 60 | ); 61 | assert($result === 1); 62 | return strlen($matches[0]); 63 | } 64 | 65 | private function isNullConstant(Node\Expr $node) : bool { 66 | return $node instanceof Node\Expr\ConstFetch 67 | && strtolower($node->name->toString()) === 'null'; 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | isFile()) { 28 | continue; 29 | } 30 | 31 | if (!strEndsWith($file->getPathName(), '.' . $extension)) { 32 | continue; 33 | } 34 | 35 | yield $file; 36 | } 37 | } 38 | } 39 | 40 | function toFileContexts(PhpParser\Parser $parser, iterable $files) : \Generator { 41 | foreach ($files as $file) { 42 | $path = $file->getPathName(); 43 | $code = file_get_contents($path); 44 | 45 | try { 46 | $stmts = $parser->parse($code); 47 | } catch (PhpParser\Error $e) { 48 | echo "$path: {$e->getMessage()}\n"; 49 | continue; 50 | } 51 | 52 | yield new FileContext($path, $code, $stmts); 53 | } 54 | } 55 | 56 | function getContext( 57 | TypeExtractor $extractor, PhpParser\NodeVisitor\NameResolver $nameResolver, iterable $files 58 | ) : Context { 59 | $traverser = new NodeTraverser(); 60 | $traverser->addVisitor($nameResolver); 61 | 62 | $context = new Context(); 63 | $visitor = new ContextCollector($extractor, $context); 64 | $traverser->addVisitor($visitor); 65 | 66 | /** @var FileContext $file */ 67 | foreach ($files as $file) { 68 | $context->setFileContext($file); 69 | $traverser->traverse($file->stmts); 70 | } 71 | 72 | return $context; 73 | } 74 | 75 | function getAddModifier( 76 | PhpParser\NodeVisitor\NameResolver $nameResolver, TypeExtractor $extractor, Context $context, Options $options 77 | ) : callable { 78 | $traverser = new NodeTraverser(); 79 | $traverser->addVisitor($nameResolver); 80 | 81 | $visitor = new TypeAnnotationVisitor($context, $extractor, $options); 82 | $traverser->addVisitor($visitor); 83 | 84 | return function(FileContext $file) use($context, $visitor, $traverser, $options) { 85 | $mutableCode = new MutableString($file->code); 86 | $visitor->setCode($mutableCode); 87 | $context->setFileContext($file); 88 | $traverser->traverse($file->stmts); 89 | 90 | $newCode = $mutableCode->getModifiedString(); 91 | if (!$options->strictTypes) { 92 | return $newCode; 93 | } 94 | 95 | return preg_replace( 96 | '/^<\?php(?!\s+declare)/', 'addVisitor($visitor); 105 | 106 | return function(FileContext $file) use($visitor, $traverser) { 107 | $mutableCode = new MutableString($file->code); 108 | $visitor->setCode($mutableCode); 109 | $traverser->traverse($file->stmts); 110 | 111 | $newCode = $mutableCode->getModifiedString(); 112 | return preg_replace('/^<\?php declare\(strict_types=1\);/', 'code !== $newCode) { 121 | file_put_contents($file->path, $newCode); 122 | } 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /test/IntegrationTest.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'comments', 'startLine', 'startFilePos', 'endFilePos', 16 | ] 17 | ]); 18 | $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer); 19 | $fileContext = $this->codeToFileContext($parser, $code); 20 | 21 | switch ($type) { 22 | case 'add': 23 | $nameResolver = new NameResolver(); 24 | $extractor = new TypeExtractor($nameResolver); 25 | 26 | $context = getContext($extractor, $nameResolver, [$fileContext]); 27 | $context->setFileContext($fileContext); 28 | 29 | $modifier = getAddModifier($nameResolver, $extractor, $context, $options); 30 | $result = $modifier($fileContext); 31 | break; 32 | case 'remove': 33 | $modifier = getRemoveModifier(); 34 | $result = $modifier($fileContext); 35 | break; 36 | } 37 | 38 | $this->assertSame($expected, $result, $name); 39 | } 40 | 41 | public function provideTests() { 42 | $files = filesInDirs([__DIR__ . '/code'], 'php'); 43 | foreach ($files as $file) { 44 | $name = $file->getPathName(); 45 | $code = file_get_contents($name); 46 | list($orig, $expected) = explode('-----', $code); 47 | 48 | $ret = preg_match('/^#!([^\r\n]+)/', $code, $matches); 49 | assert($ret === 1); 50 | 51 | $cliParser = new CliParser(); 52 | [$options, $rest] = $cliParser->parseOptions(explode(' ', 'type-util --php 7.0 --no-strict-types ' . $matches[1])); 53 | assert(count($rest) === 1); 54 | 55 | $type = $rest[0]; 56 | $orig = substr($orig, strlen($matches[0])); 57 | 58 | yield [$name, $type, $options, $this->canonicalize($orig), $this->canonicalize($expected)]; 59 | } 60 | } 61 | 62 | private function codeToFileContext(Parser $parser, string $code) : FileContext { 63 | return new FileContext('file.php', $code, $parser->parse($code)); 64 | } 65 | 66 | private function canonicalize(string $string) { 67 | $string = str_replace("\r\n", "\n", $string); 68 | return trim($string); 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | 5 | ----- 6 | -------------------------------------------------------------------------------- /test/code/anon_class.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 10 | ----- 11 | -------------------------------------------------------------------------------- /test/code/basic.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 25 | ----- 26 | 49 | 50 | -------------------------------------------------------------------------------- /test/code/inheritance.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 30 | ----- 31 | 59 | 60 | -------------------------------------------------------------------------------- /test/code/iterable.php: -------------------------------------------------------------------------------- 1 | #!add --iterable --nullable-types 2 | 19 | ----- 20 | 37 | -------------------------------------------------------------------------------- /test/code/name_resolution.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 28 | ----- 29 | 55 | -------------------------------------------------------------------------------- /test/code/no_add_strict.php: -------------------------------------------------------------------------------- 1 | #!add --strict-types 2 | 7 | ----- 8 | -------------------------------------------------------------------------------- /test/code/null.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 19 | ----- 20 | 37 | -------------------------------------------------------------------------------- /test/code/nullable.php: -------------------------------------------------------------------------------- 1 | #!add --nullable-types 2 | 14 | ----- 15 | 27 | -------------------------------------------------------------------------------- /test/code/nullable_inheritance.php: -------------------------------------------------------------------------------- 1 | #!add --nullable-types 2 | 20 | ----- 21 | 39 | -------------------------------------------------------------------------------- /test/code/object.php: -------------------------------------------------------------------------------- 1 | #!add --object 2 | 13 | ----- 14 | 25 | -------------------------------------------------------------------------------- /test/code/php_version.php: -------------------------------------------------------------------------------- 1 | #!add --php 7.0 2 | 19 | ----- 20 | -------------------------------------------------------------------------------- /test/code/property_types.php: -------------------------------------------------------------------------------- 1 | #!add --property-types 2 | 22 | ----- 23 | -------------------------------------------------------------------------------- /test/code/remove.php: -------------------------------------------------------------------------------- 1 | #!remove 2 | 16 | ----- 17 | -------------------------------------------------------------------------------- /test/code/rename.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 18 | ----- 19 | 35 | -------------------------------------------------------------------------------- /test/code/return_type_position.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 8 | ----- 9 | 15 | -------------------------------------------------------------------------------- /test/code/self_inheritance.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 21 | ----- 22 | 41 | -------------------------------------------------------------------------------- /test/code/self_parent_static.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 18 | ----- 19 | 35 | -------------------------------------------------------------------------------- /test/code/unsupported_types.php: -------------------------------------------------------------------------------- 1 | #!add 2 | 14 | ----- 15 | 27 | -------------------------------------------------------------------------------- /type-util.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | parseOptions($argv); 45 | if (count($rest) < 2) { 46 | error('At least two arguments are required.'); 47 | } 48 | 49 | $mode = $rest[0]; 50 | if ($mode !== 'add' && $mode !== 'remove') { 51 | error('Mode must be one of "add" or "remove".'); 52 | } 53 | 54 | $dirs = array_slice($rest, 1); 55 | foreach ($dirs as $dir) { 56 | if (!is_dir($dir)) { 57 | error("$dir is not a directory."); 58 | } 59 | } 60 | 61 | $fileProvider = function() use($dirs) : \Traversable { 62 | return filesInDirs($dirs, 'php'); 63 | }; 64 | 65 | $lexer = new PhpParser\Lexer\Emulative([ 66 | 'usedAttributes' => [ 67 | 'comments', 'startLine', 'startFilePos', 'endFilePos', 68 | ] 69 | ]); 70 | $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer); 71 | 72 | $startTime = microtime(true); 73 | if ('add' === $mode) { 74 | $nameResolver = new PhpParser\NodeVisitor\NameResolver(); 75 | $extractor = new TypeExtractor($nameResolver); 76 | 77 | echo "Collecting context...\n"; 78 | $context = getContext($extractor, $nameResolver, 79 | toFileContexts($parser, $fileProvider())); 80 | 81 | echo "Adding type annotations...\n"; 82 | $asts = toFileContexts($parser, $fileProvider()); 83 | modifyFiles($asts, getAddModifier($nameResolver, $extractor, $context, $options)); 84 | } else if ('remove' === $mode) { 85 | $asts = toFileContexts($parser, $fileProvider()); 86 | modifyFiles($asts, getRemoveModifier()); 87 | } 88 | 89 | $endTime = microtime(true); 90 | echo "Took: ", $endTime - $startTime, "\n"; 91 | --------------------------------------------------------------------------------