├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── VERSIONING.md ├── bin └── php7-checker ├── composer.json ├── doc └── 01-errors-checked.md ├── phpunit.xml.dist ├── src ├── Checker │ ├── AbstractChecker.php │ ├── CheckerInterface.php │ ├── ClassAddedChecker.php │ ├── FunctionAddedChecker.php │ ├── FunctionParametersSameNameChecker.php │ ├── FunctionRemovedChecker.php │ ├── GlobalVariableRemovedChecker.php │ ├── IntegerHandlingChangedChecker.php │ ├── ListHandlingChangedChecker.php │ ├── NewAssignmentByReferenceChecker.php │ ├── Php4ConstructorChecker.php │ └── TypeReservedChecker.php ├── Console │ ├── Application.php │ └── Command │ │ └── CheckCommand.php ├── Error │ ├── Error.php │ └── ErrorCollection.php ├── Factory.php ├── Lexer │ └── KeepOriginalValueLexer.php ├── PHPUnit │ └── SameErrorsConstraint.php ├── Parser.php └── ParserContext.php └── tests ├── Functional ├── AbstractFunctionalTestCase.php ├── ClassAddedTest.php ├── FunctionAddedTest.php ├── FunctionParametersSameNameTest.php ├── FunctionRemovedTest.php ├── GlobalVariableRemovedTest.php ├── IntegerHandlingChangedTest.php ├── ListHandlingChangedTest.php ├── NewAssignmentByReferenceTest.php ├── Php4ConstructorTest.php └── TypeReservedTest.php ├── PHPUnit └── SameErrorsConstraintTest.php └── fixtures └── Checker ├── ClassAdded ├── Error.php ├── Foo.php ├── IntlChar.php └── Throwable.php ├── FunctionAdded ├── test1.php ├── test2.php ├── test3.php └── test4.php ├── FunctionParametersSameName ├── test1.php ├── test2.php ├── test3.php ├── test4.php ├── test5.php ├── test6.php └── test7.php ├── FunctionRemoved ├── test1.php ├── test2.php ├── test3.php └── test4.php ├── GlobalVariableRemoved ├── test1.php ├── test10.php ├── test11.php ├── test12.php ├── test2.php ├── test3.php ├── test4.php ├── test5.php ├── test6.php ├── test7.php ├── test8.php └── test9.php ├── IntegerHandlingChanged ├── test1.php ├── test2.php ├── test3.php ├── test4.php ├── test5.php └── test6.php ├── ListHandlingChanged ├── test1.php ├── test2.php ├── test3.php ├── test4.php └── test5.php ├── NewAssignmentByReference ├── test1.php ├── test2.php ├── test3.php └── test4.php ├── Php4Constructor ├── Bulbasaur.php ├── Charmander.php ├── Jigglypuff.php ├── Mew.php ├── Pikachu.php └── Squirtle.php └── TypeReserved ├── Bar.php ├── Baz.php ├── Float_.php ├── Foo.php ├── Int └── Foo.php ├── Integer.php ├── Object.php ├── Toto1.php └── Toto2.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /vendor 3 | 4 | # Composer 5 | composer.phar 6 | composer.lock 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache 8 | 9 | php: 10 | - 5.4 11 | - 5.5 12 | - 5.6 13 | - 7.0 14 | - hhvm 15 | 16 | matrix: 17 | allow_failures: 18 | - php: hhvm 19 | 20 | before_script: 21 | - composer install --no-interaction 22 | 23 | script: 24 | - ./vendor/bin/phpunit 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolicode/php7-checker/11ad01bd844d35f6be14ad48d7dbb96351786d00/CHANGELOG.md -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, **thank you** for contributing, **you are awesome**! 4 | 5 | Everybody should be able to help. Here's how you can do it: 6 | 7 | 1. [Fork it](https://github.com/jolicode/php7-checker/fork_select) 8 | 2. improve it 9 | 3. submit a [pull request](https://help.github.com/articles/creating-a-pull-request) 10 | 11 | Here's some tips to make you the best contributor ever: 12 | 13 | * [Rules](#rules) 14 | * [Green tests](#green-tests) 15 | * [Standard code](#standard-code) 16 | * [Keeping your fork up-to-date](#keeping-your-fork-up-to-date) 17 | 18 | ## Rules 19 | 20 | Here are a few rules to follow in order to ease code reviews, and discussions 21 | before maintainers accept and merge your work. 22 | 23 | * You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and 24 | [PSR-2](http://www.php-fig.org/psr/2/) (see [Rules](#rules)). 25 | * You MUST run the test suite (see [Green tests](#green-tests)). 26 | * You MUST write (or update) unit tests. 27 | * You SHOULD write documentation. 28 | 29 | Please, write [commit messages that make 30 | sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), 31 | and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) 32 | before submitting your Pull Request (see also how to [keep your 33 | fork up-to-date](#keeping-your-fork-up-to-date)). 34 | 35 | One may ask you to [squash your 36 | commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) 37 | too. This is used to "clean" your Pull Request before merging it (we don't want 38 | commits such as `fix tests`, `fix 2`, `fix 3`, etc.). 39 | 40 | Also, while creating your Pull Request on GitHub, you MUST write a description 41 | which gives the context and/or explains why you are creating it. 42 | 43 | Your work will then be reviewed as soon as possible (suggestions about some 44 | changes, improvements or alternatives may be given). 45 | 46 | ## Green tests 47 | 48 | Run the tests using the following script: 49 | 50 | ```shell 51 | vendor/bin/phpunit 52 | ``` 53 | 54 | ## Standard code 55 | 56 | Use [PHP CS fixer](http://cs.sensiolabs.org/) to make your code compliant with 57 | php7-checker's coding standards: 58 | 59 | ```shell 60 | vendor/bin/php-cs-fixer fix . 61 | ``` 62 | 63 | ## Keeping your fork up-to-date 64 | 65 | To keep your fork up-to-date, you should track the upstream (original) one 66 | using the following command: 67 | 68 | 69 | ```shell 70 | git remote add upstream https://github.com/jolicode/php7-checker.git 71 | ``` 72 | 73 | Then get the upstream changes: 74 | 75 | ```shell 76 | git checkout master 77 | git pull --rebase origin master 78 | git pull --rebase upstream master 79 | git checkout 80 | git rebase master 81 | ``` 82 | 83 | Finally, publish your changes: 84 | 85 | ```shell 86 | git push -f origin 87 | ``` 88 | 89 | Your pull request will be automatically updated. 90 | 91 | 92 | Thank you! 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Loïck Piera 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php7-checker 2 | 3 | **This project is now deprecated in favor of [php7cc](https://github.com/sstalle/php7cc)**. 4 | 5 | ~~php7-checker is a PHP library that parses your code and statically detects 6 | some errors that could prevent it to run on PHP7.~~ 7 | 8 | ~~*Disclamer*: this tool is a static analyzer. As it doesn't run your code it's 9 | far from being 100% reliable. If you need to ensure that some code will run on 10 | PHP 7, nothing will do a better job than a complete test suite run on the 11 | targeted version of PHP. If you want to test it locally (f.e. because your code 12 | is not open source), you can still have a look to 13 | [JoliCi](https://github.com/jolicode/JoliCi).~~ 14 | 15 | ## Installation 16 | 17 | #### Globally (Composer) 18 | 19 | To install php7-checker, install Composer and issue the following command: 20 | ```bash 21 | ./composer.phar global require jolicode/php7-checker 22 | ``` 23 | 24 | Then, make sure you have ``~/.composer/vendor/bin`` in your ``PATH``, and 25 | you're good to go: 26 | ```bash 27 | export PATH="$PATH:$HOME/.composer/vendor/bin" 28 | ``` 29 | 30 | ## Usage 31 | 32 | You can run the checker on a given file or directory: 33 | 34 | ```bash 35 | php7-checker /path/to/dir 36 | php7-checker /path/to/file 37 | ``` 38 | 39 | ## Further documentation 40 | 41 | Discover more by reading the docs: 42 | 43 | * [Errors checked](doc/01-errors-checked.md) 44 | 45 | You can see the current and past versions using one of the following: 46 | 47 | * the `git tag` command 48 | * the [releases page on Github](https://github.com/jolicode/php7-checker/releases) 49 | * the file listing the [changes between versions](CHANGELOG.md) 50 | 51 | And finally some meta documentation: 52 | 53 | * [versioning and branching models](VERSIONING.md) 54 | * [contribution instructions](CONTRIBUTING.md) 55 | 56 | ## Credits 57 | 58 | * [All contributors](https://github.com/jolicode/php7-checker/graphs/contributors) 59 | 60 | ## License 61 | 62 | php7-checker is licensed under the MIT License - see the [LICENSE](LICENSE) file 63 | for details. 64 | -------------------------------------------------------------------------------- /VERSIONING.md: -------------------------------------------------------------------------------- 1 | # Versioning and branching models 2 | 3 | This file explains the versioning and branching models of this project. 4 | 5 | ## Versioning 6 | 7 | The versioning is inspired by [Semantic Versioning](http://semver.org/): 8 | 9 | > Given a version number MAJOR.MINOR.PATCH, increment the: 10 | > 11 | > 1. MAJOR version when you make incompatible API changes 12 | > 2. MINOR version when you add functionality in a backwards-compatible manner 13 | > 3. PATCH version when you make backwards-compatible bug fixes 14 | 15 | ## Branching Model 16 | 17 | The branching is inspired by [@jbenet](https://github.com/jbenet) 18 | [simple git branching model](https://gist.github.com/jbenet/ee6c9ac48068889b0912): 19 | 20 | > 1. `master` must always be deployable. 21 | > 2. **all changes** are made through feature branches (pull-request + merge) 22 | > 3. rebase to avoid/resolve conflicts; merge in to `master` 23 | -------------------------------------------------------------------------------- /bin/php7-checker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jolicode/php7-checker", 3 | "description": "Detect parts of your code that should be updated to be PHP7 compatible", 4 | "keywords": ["php7", "check", "compatible"], 5 | "license": "MIT", 6 | "type": "library", 7 | "authors": [ 8 | { 9 | "name": "Loïck Piera", 10 | "email": "pyrech@gmail.com" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "Joli\\Php7Checker\\": "src/" 16 | } 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Joli\\Php7Checker\\tests\\": "tests/" 21 | } 22 | }, 23 | "require": { 24 | "nikic/php-parser": "^1.4", 25 | "symfony/console": "^2.7", 26 | "symfony/filesystem": "^2.7", 27 | "symfony/finder": "^2.7" 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "~4.5", 31 | "fabpot/php-cs-fixer": "^1.10" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /doc/01-errors-checked.md: -------------------------------------------------------------------------------- 1 | # Errors checked 2 | 3 | This project can detect several errors: 4 | 5 | ## Reserved types 6 | 7 | PHP 7 prohibits the usage of some types as class, interface and trait names. 8 | It also prevents them from being used in class_alias(). 9 | 10 | The full list of reserved types is: 11 | * int 12 | * float 13 | * bool 14 | * string 15 | * true 16 | * false 17 | * null 18 | * resource 19 | * object 20 | * mixed 21 | * numeric 22 | 23 | ## PHP 4 constructor 24 | 25 | PHP 7 emits E_DEPRECATED whenever a PHP 4 constructor is defined. 26 | 27 | A PHP 4 constructor is a method that have the same name than its class. The 28 | only few cases were this method would be considered as a constructor is when no 29 | PHP 5 constructor (__construct) is defined and when the class is in the global 30 | namespace. 31 | 32 | ## New assignment by reference 33 | 34 | New objects cannot be assigned by reference. 35 | 36 | The result of the new statement can no longer be assigned to a variable by 37 | reference. In PHP 5, this behavior triggered an E_DEPRECATED error but in 38 | PHP 7, it will trigger a parse error. 39 | 40 | ## Integer handling changes 41 | 42 | Invalid octal literal now trigger a parse error instead of silently truncated 43 | (0128 was taken as 012). 44 | 45 | ## List handling changes 46 | 47 | Empty list() are no longer supported. 48 | 49 | ## Function with mote than one parameter with the same name 50 | 51 | PHP 7 no longer allows function to have more than one parameter with the same 52 | name. 53 | 54 | ## Function removed 55 | 56 | PHP 7 removed a bunch of functions that you can no longer use. 57 | 58 | Most removed functions are a consequence of a extensions removal (mysql, ereg, 59 | mssql, etc). Some are simply removed in favor of an alternative function. 60 | 61 | This tool cannot detects class method calls so only simple functions are 62 | checked. 63 | 64 | ## Class added 65 | 66 | New classes were added in the global namespace so you cannot redefine them. 67 | 68 | ## Function added 69 | 70 | Some functions were added in the global namespace so you cannot redefine them. 71 | 72 | This tool cannot detects class method calls so only simple functions are 73 | checked. 74 | 75 | ## Global variable removed 76 | 77 | The global variable $HTTP_RAW_POST_DATA was removed. 78 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./ 22 | 23 | ./vendor 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Checker/AbstractChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\ErrorCollection; 15 | use Joli\Php7Checker\ParserContext; 16 | use PhpParser\Node; 17 | 18 | class AbstractChecker implements CheckerInterface 19 | { 20 | /** @var ErrorCollection */ 21 | protected $errorCollection; 22 | 23 | /** @var ParserContext */ 24 | protected $parserContext; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function setErrorCollection(ErrorCollection $errorCollection) 30 | { 31 | $this->errorCollection = $errorCollection; 32 | } 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function setParserContext(ParserContext $parserContext) 37 | { 38 | $this->parserContext = $parserContext; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function beforeTraverse(array $nodes) 45 | { 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function enterNode(Node $node) 52 | { 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function leaveNode(Node $node) 59 | { 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function afterTraverse(array $nodes) 66 | { 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Checker/CheckerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\ErrorCollection; 15 | use Joli\Php7Checker\ParserContext; 16 | use PhpParser\NodeVisitor; 17 | 18 | /** 19 | * The interface all checkers should implement. 20 | */ 21 | interface CheckerInterface extends NodeVisitor 22 | { 23 | /** 24 | * Set the ErrorCollection that will be used to store errors found by checker. 25 | * 26 | * @param ErrorCollection $errors 27 | */ 28 | public function setErrorCollection(ErrorCollection $errors); 29 | 30 | /** 31 | * Set the ParserContext to keep some information about the current parsed snippet of code. 32 | * 33 | * @param ParserContext $parserContext 34 | */ 35 | public function setParserContext(ParserContext $parserContext); 36 | } 37 | -------------------------------------------------------------------------------- /src/Checker/ClassAddedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | use PhpParser\Node\Stmt; 17 | 18 | /** 19 | * PHP 7 added several classes. 20 | * 21 | * In consequence, you can no longer use class/interface/trait in the root 22 | * namespace that has the same name than a class added. 23 | */ 24 | class ClassAddedChecker extends AbstractChecker 25 | { 26 | private static $addedClasses = array( 27 | 'IntlChar', 28 | 29 | 'ReflectionGenerator', 30 | 'ReflectionType', 31 | 32 | 'SessionUpdateTimestampHandlerInterface', 33 | 34 | 'Throwable', 35 | 'Error', 36 | 'TypeError', 37 | 'ParseError', 38 | 'AssertionError', 39 | 'ArithmeticError', 40 | 'DivisionByZeroError', 41 | ); 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function leaveNode(Node $node) 47 | { 48 | if ($node instanceof Stmt\Class_ 49 | || $node instanceof Stmt\Interface_ 50 | || $node instanceof Stmt\Trait_) { 51 | 52 | // Check that class is not namespaced and is in the list of added classes 53 | if ( 54 | isset($node->namespacedName) 55 | && count($node->namespacedName->parts) === 1 56 | && in_array($node->name, self::$addedClasses) 57 | ) { 58 | $this->errorCollection->add(new Error( 59 | $this->parserContext->getFilename(), 60 | $node->getLine(), 61 | sprintf('"%s" cannot be used as class/interface/trait name.', $node->name), 62 | 'You should either rename your class/trait/interface or put it in a namespace.' 63 | )); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Checker/FunctionAddedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | use PhpParser\Node\Stmt; 17 | 18 | /** 19 | * PHP 7 added some new functions. 20 | * 21 | * In consequence, you can no longer use function in the root namespace that 22 | * has the same name than a function added. 23 | * 24 | * This checker only detects simple functions, not class methods because it 25 | * supposes to check if a class extends the given class, then if it defines a 26 | * method that could overrides a new added one. 27 | */ 28 | class FunctionAddedChecker extends AbstractChecker 29 | { 30 | private static $addedFunctions = array( 31 | //'Closure::call', 32 | 33 | 'random_bytes', 34 | 'random_int', 35 | 36 | 'error_clear_last', 37 | 38 | 'gmp_random_seed', 39 | 40 | 'intdiv', 41 | 42 | 'preg_replace_callback_array', 43 | 44 | 'posix_setrlimit', 45 | 46 | //'ReflectionParameter::getType', 47 | //'ReflectionFunctionAbstract::getReturnType', 48 | 49 | 'inflate_add', 50 | 'deflate_add', 51 | 'inflate_init', 52 | 'deflate_init', 53 | 54 | ); 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function leaveNode(Node $node) 60 | { 61 | if ($node instanceof Stmt\Function_) { 62 | // Check that class is not namespaced and is in the list of added functions 63 | if ( 64 | isset($node->namespacedName) 65 | && count($node->namespacedName->parts) === 1 66 | && in_array($node->name, self::$addedFunctions) 67 | ) { 68 | $this->errorCollection->add(new Error( 69 | $this->parserContext->getFilename(), 70 | $node->getLine(), 71 | sprintf('"%s" cannot be used as function name.', $node->name), 72 | 'You should either rename your function or put it in a namespace.' 73 | )); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Checker/FunctionParametersSameNameChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | 17 | /** 18 | * PHP 7 no longer allows function to have more than one parameter with the 19 | * same name. 20 | */ 21 | class FunctionParametersSameNameChecker extends AbstractChecker 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function leaveNode(Node $node) 27 | { 28 | if ($node instanceof Node\FunctionLike) { 29 | $params = $node->getParams(); 30 | $paramNames = array(); 31 | $hasParameterWithSameName = false; 32 | 33 | array_walk( 34 | $params, 35 | function (Node\Param $param) use (&$paramNames, &$hasParameterWithSameName) { 36 | if (array_key_exists($param->name, $paramNames)) { 37 | $hasParameterWithSameName = true; 38 | } 39 | $paramNames[$param->name] = true; 40 | } 41 | ); 42 | 43 | if ($hasParameterWithSameName) { 44 | $this->errorCollection->add(new Error( 45 | $this->parserContext->getFilename(), 46 | $node->getLine(), 47 | 'Functions can no longer have more than one parameter with the same name.' 48 | )); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Checker/FunctionRemovedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | 17 | /** 18 | * PHP 7 removed a bunch of functions that you can no longer use. 19 | * 20 | * Most removed functions are a consequence of a deprecated extension removal. 21 | * Some are simply removed in favor of an alternative function. 22 | * 23 | * This checker cannot detects class method calls because it's not that easy to 24 | * determine the class of a variable. 25 | */ 26 | class FunctionRemovedChecker extends AbstractChecker 27 | { 28 | /** @var string[string] */ 29 | private static $removedFunctions = array( 30 | 'call_user_method' => 'Use the call_user_func() function instead.', 31 | 'call_user_method_array' => 'Use the call_user_func_array() function instead.', 32 | 33 | 'mcrypt_generic_end' => 'Use the mcrypt_generic_deinit() function instead.', 34 | 35 | 'mcrypt_ecb' => 'Use the mcrypt_decrypt() function with a MCRYPT_MODE_* constant instead.', 36 | 'mcrypt_cbc' => 'Use the mcrypt_decrypt() function with a MCRYPT_MODE_* constant instead.', 37 | 'mcrypt_cfb' => 'Use the mcrypt_decrypt() function with a MCRYPT_MODE_* constant instead.', 38 | 'mcrypt_ofb' => 'Use the mcrypt_decrypt() function with a MCRYPT_MODE_* constant instead.', 39 | 40 | 'datefmt_set_timezone_id' => 'Use the datefmt_set_timezone() function instead.', 41 | // Cannot checks call of class method 42 | //'IntlDateFormatter::setTimeZoneID' => 'Use the IntlDateFormatter::setTimeZone() function instead.', 43 | 44 | 'set_magic_quotes_runtime' => '', 45 | 'magic_quotes_runtime' => '', 46 | 47 | 'set_socket_blocking' => 'Use the stream_set_blocking() function instead.', 48 | 49 | // dl() can no longer be used in PHP-FPM but it remains functional in the CLI and embed SAPIs. 50 | //'dl' => 'The function was removed on fpm-fcgi.', 51 | 52 | 'imagepsbbox' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 53 | 'imagepsencodefont' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 54 | 'imagepsextendedfont' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 55 | 'imagepsfreefont' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 56 | 'imagepsloadfont' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 57 | 'imagepsslantfont' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 58 | 'imagepstext' => 'Support for PostScript Type1 fonts has been removed from the GD extension, you should use TrueType fonts and their associated functions instead.', 59 | 60 | 'ereg_replace' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 61 | 'ereg' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 62 | 'eregi_replace' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 63 | 'eregi' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 64 | 'split' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 65 | 'spliti' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 66 | 'sql_regcase' => 'The ereg extension was removed, you should use the PCRE extension and preg_*() functions instead.', 67 | 68 | 'mysql_affected_rows' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 69 | 'mysql_client_encoding' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 70 | 'mysql_close' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 71 | 'mysql_connect' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 72 | 'mysql_create_db' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 73 | 'mysql_data_seek' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 74 | 'mysql_db_name' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 75 | 'mysql_db_query' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 76 | 'mysql_drop_db' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 77 | 'mysql_errno' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 78 | 'mysql_error' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 79 | 'mysql_escape_string' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 80 | 'mysql_fetch_array' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 81 | 'mysql_fetch_assoc' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 82 | 'mysql_fetch_field' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 83 | 'mysql_fetch_lengths' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 84 | 'mysql_fetch_object' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 85 | 'mysql_fetch_row' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 86 | 'mysql_field_flags' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 87 | 'mysql_field_len' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 88 | 'mysql_field_name' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 89 | 'mysql_field_seek' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 90 | 'mysql_field_table' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 91 | 'mysql_field_type' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 92 | 'mysql_free_result' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 93 | 'mysql_get_client_info' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 94 | 'mysql_get_host_info' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 95 | 'mysql_get_proto_info' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 96 | 'mysql_get_server_info' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 97 | 'mysql_info' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 98 | 'mysql_insert_id' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 99 | 'mysql_list_dbs' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 100 | 'mysql_list_fields' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 101 | 'mysql_list_processes' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 102 | 'mysql_list_tables' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 103 | 'mysql_num_fields' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 104 | 'mysql_num_rows' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 105 | 'mysql_pconnect' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 106 | 'mysql_ping' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 107 | 'mysql_query' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 108 | 'mysql_real_escape_string' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 109 | 'mysql_result' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 110 | 'mysql_select_db' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 111 | 'mysql_set_charset' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 112 | 'mysql_stat' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 113 | 'mysql_tablename' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 114 | 'mysql_thread_id' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 115 | 'mysql_unbuffered_query' => 'The mysql extension was removed, you should use the mysqli extension with its mysqli_*() function or the PDO extension.', 116 | 117 | 'mssql_bind' => 'The mssql extension was removed.', 118 | 'mssql_close' => 'The mssql extension was removed.', 119 | 'mssql_connect' => 'The mssql extension was removed.', 120 | 'mssql_data_seek' => 'The mssql extension was removed.', 121 | 'mssql_execute' => 'The mssql extension was removed.', 122 | 'mssql_fetch_array' => 'The mssql extension was removed.', 123 | 'mssql_fetch_assoc' => 'The mssql extension was removed.', 124 | 'mssql_fetch_batch' => 'The mssql extension was removed.', 125 | 'mssql_fetch_field' => 'The mssql extension was removed.', 126 | 'mssql_fetch_object' => 'The mssql extension was removed.', 127 | 'mssql_fetch_row' => 'The mssql extension was removed.', 128 | 'mssql_field_length' => 'The mssql extension was removed.', 129 | 'mssql_field_name' => 'The mssql extension was removed.', 130 | 'mssql_field_seek' => 'The mssql extension was removed.', 131 | 'mssql_field_type' => 'The mssql extension was removed.', 132 | 'mssql_free_result' => 'The mssql extension was removed.', 133 | 'mssql_free_statement' => 'The mssql extension was removed.', 134 | 'mssql_get_last_message' => 'The mssql extension was removed.', 135 | 'mssql_guid_string' => 'The mssql extension was removed.', 136 | 'mssql_init' => 'The mssql extension was removed.', 137 | 'mssql_min_error_severity' => 'The mssql extension was removed.', 138 | 'mssql_min_message_severity' => 'The mssql extension was removed.', 139 | 'mssql_next_result' => 'The mssql extension was removed.', 140 | 'mssql_num_fields' => 'The mssql extension was removed.', 141 | 'mssql_num_rows' => 'The mssql extension was removed.', 142 | 'mssql_pconnect' => 'The mssql extension was removed.', 143 | 'mssql_query' => 'The mssql extension was removed.', 144 | 'mssql_result' => 'The mssql extension was removed.', 145 | 'mssql_rows_affected' => 'The mssql extension was removed.', 146 | 'mssql_select_db' => 'The mssql extension was removed.', 147 | ); 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function leaveNode(Node $node) 153 | { 154 | if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { 155 | $name = strtolower(end($node->name->parts)); 156 | if (array_key_exists($name, self::$removedFunctions)) { 157 | $this->errorCollection->add(new Error( 158 | $this->parserContext->getFilename(), 159 | $node->getLine(), 160 | sprintf('The function %s() was removed.', $name), 161 | self::$removedFunctions[$name] 162 | )); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Checker/GlobalVariableRemovedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | 17 | /** 18 | * PHP 7 removed the pre-defined variable HTTP_RAW_POST_DATA. 19 | */ 20 | class GlobalVariableRemovedChecker extends AbstractChecker 21 | { 22 | /** @var string[string] */ 23 | private static $removedGlobals = array( 24 | 'HTTP_RAW_POST_DATA' => 'You can use the php://input stream instead.', 25 | ); 26 | 27 | /** @var bool */ 28 | private $isInGlobalScope = true; 29 | 30 | /** @var string[] */ 31 | private $globalsInCurrentLocalScope = array(); 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function enterNode(Node $node) 37 | { 38 | // Check if we leave the global scope 39 | if ($node instanceof Node\FunctionLike) { 40 | $this->isInGlobalScope = false; 41 | } 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function leaveNode(Node $node) 48 | { 49 | // Keep traces of global variable 50 | if ($node instanceof Node\Stmt\Global_) { 51 | foreach ($node->vars as $variable) { 52 | $this->globalsInCurrentLocalScope[] = $variable->name; 53 | } 54 | } 55 | 56 | // Check if the variable is marked as global or used in the global scope 57 | if ($node instanceof Node\Expr\Variable) { 58 | if ($this->isInGlobalScope || in_array($node->name, $this->globalsInCurrentLocalScope)) { 59 | $this->checkIfGlobalVariableWasRemoved($node->name, $node->getLine()); 60 | } 61 | } 62 | 63 | // Check if the variable is used from the $GLOBALS variable 64 | if ($node instanceof Node\Expr\ArrayDimFetch) { 65 | if ($node->var instanceof Node\Expr\Variable 66 | && $node->var->name === 'GLOBALS' 67 | && $node->dim instanceof Node\Scalar\String_) { 68 | $this->checkIfGlobalVariableWasRemoved($node->dim->value, $node->dim->getLine()); 69 | } 70 | } 71 | 72 | // Check if we re-enter in the global scope 73 | if ($node instanceof Node\FunctionLike) { 74 | $this->isInGlobalScope = true; 75 | $this->globalsInCurrentLocalScope = array(); 76 | } 77 | } 78 | 79 | /** 80 | * @param string $variableName 81 | * @param int $line 82 | */ 83 | private function checkIfGlobalVariableWasRemoved($variableName, $line) 84 | { 85 | if (array_key_exists($variableName, self::$removedGlobals)) { 86 | $this->errorCollection->add(new Error( 87 | $this->parserContext->getFilename(), 88 | $line, 89 | sprintf('The global variable $%s was removed.', $variableName), 90 | self::$removedGlobals[$variableName] 91 | )); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Checker/IntegerHandlingChangedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | 17 | /** 18 | * PHP 7 changed a bit integer handling: 19 | * - Invalid octal literal now trigger a parse error instead of silently 20 | * truncated (0128 was taken as 012) 21 | * - Bitwise shifts by negative numbers will now throw an ArithmeticError 22 | * - Bitwise shifts (in either direction) beyond the bit width of an integer 23 | * will always result in 0 (no longer architecture dependent). 24 | * 25 | * This checker only checks octal literal. 26 | */ 27 | class IntegerHandlingChangedChecker extends AbstractChecker 28 | { 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function enterNode(Node $node) 33 | { 34 | if ($node instanceof Node\Scalar\LNumber) { 35 | $originalValue = $node->getAttribute('originalValue'); 36 | 37 | if ($originalValue && $this->isInvalidOctalLiteral($originalValue)) { 38 | $this->errorCollection->add(new Error( 39 | $this->parserContext->getFilename(), 40 | $node->getLine(), 41 | sprintf( 42 | '"%s" is an invalid octal literal and will now triggers a parse error.', 43 | $originalValue 44 | ) 45 | )); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Check if the given string is a valid octal literal. 52 | * 53 | * @param string $str 54 | * 55 | * @return bool 56 | */ 57 | private function isInvalidOctalLiteral($str) 58 | { 59 | // Handle plain 0 specially 60 | if ('0' === $str) { 61 | return false; 62 | } 63 | 64 | // If first char is 0 (and number isn't 0) it's a special syntax 65 | if ('0' === $str[0]) { 66 | // hex 67 | if ('x' === $str[1] || 'X' === $str[1]) { 68 | return false; 69 | } 70 | 71 | // bin 72 | if ('b' === $str[1] || 'B' === $str[1]) { 73 | return false; 74 | } 75 | 76 | for ($i = 1; $i < strlen($str); ++$i) { 77 | if ($str[$i] > 7) { 78 | return true; 79 | } 80 | } 81 | } 82 | 83 | return false; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Checker/ListHandlingChangedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | 17 | /** 18 | * PHP 7 changed a bit list() handling: 19 | * - Empty list() are no longer supported 20 | * - list() cannot unpack string anymore (str_split() should be used instead) 21 | * - list() no longer assigns variables in reverse order. 22 | * 23 | * This checker only detects usage of empty list(). 24 | */ 25 | class ListHandlingChangedChecker extends AbstractChecker 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function leaveNode(Node $node) 31 | { 32 | if ($node instanceof Node\Expr\List_) { 33 | $isEmpty = true; 34 | 35 | foreach ($node->vars as $var) { 36 | if ($var) { 37 | $isEmpty = false; 38 | } 39 | } 40 | 41 | if ($isEmpty) { 42 | $this->errorCollection->add(new Error( 43 | $this->parserContext->getFilename(), 44 | $node->getLine(), 45 | 'Empty list() assignments are no longer supported.', 46 | 'You should set at least one assignment in the list.' 47 | )); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Checker/NewAssignmentByReferenceChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | 17 | /** 18 | * New objects cannot be assigned by reference. 19 | * 20 | * The result of the new statement can no longer be assigned to a variable by 21 | * reference. In PHP 5, this behavior triggered an E_DEPRECATED error but in 22 | * PHP 7, it will trigger a parse error. 23 | */ 24 | class NewAssignmentByReferenceChecker extends AbstractChecker 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function leaveNode(Node $node) 30 | { 31 | if ($node instanceof Node\Expr\AssignRef) { 32 | if ($node->expr instanceof Node\Expr\New_) { 33 | $this->errorCollection->add(new Error( 34 | $this->parserContext->getFilename(), 35 | $node->getLine(), 36 | 'New objects cannot be assigned by reference.' 37 | )); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Checker/Php4ConstructorChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | use PhpParser\Node\Stmt; 17 | 18 | /** 19 | * PHP 7 emits E_DEPRECATED whenever a PHP 4 constructor is defined. 20 | * 21 | * A PHP 4 constructor is a method that have the same name than its class. The 22 | * only few cases were this method would be considered as a constructor is when 23 | * no PHP 5 constructor (__construct) is defined and when the class is in the 24 | * global namespace. 25 | * 26 | * The related RFC is: https://wiki.php.net/rfc/remove_php4_constructors 27 | */ 28 | class Php4ConstructorChecker extends AbstractChecker 29 | { 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function leaveNode(Node $node) 34 | { 35 | if ($node instanceof Stmt\Class_) { 36 | $currentClassName = $node->name; 37 | $hasNamespace = false; 38 | $hasPhp4Constructor = false; 39 | $hasPhp5Constructor = false; 40 | $php4ConstructorLine = null; 41 | 42 | if (count($node->namespacedName->parts) > 1) { 43 | $hasNamespace = true; 44 | } 45 | 46 | foreach ($node->stmts as $stmt) { 47 | // Check for constructors 48 | if ($stmt instanceof Stmt\ClassMethod) { 49 | if ($stmt->name === '__construct') { 50 | $hasPhp5Constructor = true; 51 | } 52 | 53 | if ($stmt->name === $currentClassName) { 54 | $hasPhp4Constructor = true; 55 | $php4ConstructorLine = $stmt->getLine(); 56 | } 57 | } 58 | } 59 | 60 | if (!$hasNamespace && $hasPhp4Constructor && !$hasPhp5Constructor) { 61 | $this->errorCollection->add(new Error( 62 | $this->parserContext->getFilename(), 63 | $php4ConstructorLine, 64 | 'Using a PHP 4 constructor is now deprecated.', 65 | 'You should use the __construct() method instead.' 66 | )); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Checker/TypeReservedChecker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Checker; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use PhpParser\Node; 16 | use PhpParser\Node\Stmt; 17 | 18 | /** 19 | * PHP 7 prohibits the usage of some types as class, interface and trait names. 20 | * It also prevents them from being used in class_alias(). 21 | * 22 | * The related RFC are: 23 | * - https://wiki.php.net/rfc/reserve_more_types_in_php_7 24 | * - https://wiki.php.net/rfc/reserve_even_more_types_in_php_7 25 | */ 26 | class TypeReservedChecker extends AbstractChecker 27 | { 28 | private static $reservedTypes = array( 29 | 'int', 30 | 'float', 31 | 'bool', 32 | 'string', 33 | 'true', 34 | 'false', 35 | 'null', 36 | 37 | // The following types are just reserved but do not yet throw an error. 38 | // Should we allow to use them? Let's said no for now ;) 39 | 'resource', 40 | 'object', 41 | 'mixed', 42 | 'numeric', 43 | ); 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function leaveNode(Node $node) 49 | { 50 | if ($node instanceof Stmt\Class_ 51 | || $node instanceof Stmt\Interface_ 52 | || $node instanceof Stmt\Trait_) { 53 | $this->check( 54 | $node->name, 55 | $node->getLine(), 56 | '"%s" is now a reserved type and can no longer be used as the name of a class/interface/trait.' 57 | ); 58 | } elseif ($node instanceof Stmt\Use_ && $node->type === Stmt\Use_::TYPE_NORMAL) { 59 | $this->check( 60 | $node->uses[0]->alias, 61 | $node->getLine(), 62 | '"%s" is now a reserved type and can no longer be used as an alias.' 63 | ); 64 | } elseif ($node instanceof Node\Expr\FuncCall 65 | && $node->name instanceof Node\Name 66 | && $node->name->parts === array('class_alias')) { 67 | $args = $node->args; 68 | 69 | if (!empty($args[1]) && $args[1]->value instanceof Node\Scalar\String_) { 70 | $fqcn = $args[1]->value->value; 71 | $parts = explode('\\', $fqcn); 72 | $className = end($parts); 73 | 74 | $this->check( 75 | $className, 76 | $node->getLine(), 77 | '"%s" is now a reserved type and can no longer be used in class_alias().' 78 | ); 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * @param string $name 85 | * @param int $line 86 | * @param string $message 87 | */ 88 | private function check($name, $line, $message) 89 | { 90 | $name = strtolower($name); 91 | 92 | if (in_array($name, self::$reservedTypes)) { 93 | $this->errorCollection->add(new Error( 94 | $this->parserContext->getFilename(), 95 | $line, 96 | sprintf($message, $name) 97 | )); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Console/Application.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Console; 13 | 14 | use Joli\Php7Checker\Console\Command\CheckCommand; 15 | use Joli\Php7Checker\Parser; 16 | use Symfony\Component\Console\Application as BaseApplication; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | 19 | class Application extends BaseApplication 20 | { 21 | /** 22 | * Constructor. 23 | */ 24 | public function __construct() 25 | { 26 | error_reporting(-1); 27 | 28 | parent::__construct('PHP 7 Checker', Parser::VERSION); 29 | 30 | $this->add(new CheckCommand()); 31 | 32 | $this->setDefaultCommand('check'); 33 | } 34 | 35 | public function getLongVersion() 36 | { 37 | $version = parent::getLongVersion().' by Loïck Piera'; 38 | $commit = '@git-commit@'; 39 | 40 | if ('@'.'git-commit@' !== $commit) { 41 | $version .= ' ('.substr($commit, 0, 7).')'; 42 | } 43 | 44 | return $version; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | protected function getCommandName(InputInterface $input) 51 | { 52 | return 'check'; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | * 58 | * Overridden so that the application does not expect the command 59 | * name to be the first argument. 60 | */ 61 | public function getDefinition() 62 | { 63 | $inputDefinition = parent::getDefinition(); 64 | // clear out the normal first argument, which is the command name 65 | $inputDefinition->setArguments(); 66 | 67 | return $inputDefinition; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Console/Command/CheckCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Console\Command; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use Joli\Php7Checker\Factory; 16 | use Symfony\Component\Console\Command\Command; 17 | use Symfony\Component\Console\Helper\FormatterHelper; 18 | use Symfony\Component\Console\Input\InputArgument; 19 | use Symfony\Component\Console\Input\InputInterface; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | use Symfony\Component\Filesystem\Filesystem; 22 | use Symfony\Component\Finder\Finder; 23 | 24 | /** 25 | * This command use the parser to check if the targeted code can run on PHP 7. 26 | */ 27 | class CheckCommand extends Command 28 | { 29 | /** 30 | * @see Command 31 | */ 32 | protected function configure() 33 | { 34 | $this 35 | ->setName('check') 36 | ->setDefinition( 37 | array( 38 | new InputArgument('path', InputArgument::OPTIONAL, 'The path', null), 39 | ) 40 | ) 41 | ->setDescription('Check a directory or a file') 42 | ->setHelp(<<%command.name% command parse php files to detect some errors that will prevent your code to run on PHP 7: 44 | 45 | php %command.full_name% /path/to/dir 46 | php %command.full_name% /path/to/file 47 | 48 | EOF 49 | ); 50 | } 51 | 52 | /** 53 | * @see Command 54 | */ 55 | protected function execute(InputInterface $input, OutputInterface $output) 56 | { 57 | /** @var FormatterHelper $formatter */ 58 | $formatter = $this->getHelperSet()->get('formatter'); 59 | 60 | $path = $input->getArgument('path') ?: ''; 61 | 62 | $filesystem = new Filesystem(); 63 | if (!$filesystem->isAbsolutePath($path)) { 64 | $path = getcwd().DIRECTORY_SEPARATOR.$path; 65 | } 66 | 67 | $path = rtrim(realpath($path), DIRECTORY_SEPARATOR); 68 | 69 | if (is_file($path)) { 70 | $files = new \ArrayIterator(array(new \SplFileInfo($path))); 71 | } else { 72 | $path .= DIRECTORY_SEPARATOR; 73 | $files = new Finder(); 74 | 75 | $files 76 | ->files() 77 | ->name('*.php') 78 | ->ignoreDotFiles(true) 79 | ->ignoreVCS(true) 80 | ->exclude('vendor') 81 | ->in($path) 82 | ; 83 | } 84 | 85 | $output->writeln(sprintf("\rChecking %s", $path)); 86 | $total = $files->count(); 87 | $parser = Factory::createParser(); 88 | 89 | $index = 1; 90 | /** @var \SplFileInfo $file */ 91 | foreach ($files as $file) { 92 | $output->write(sprintf("\rParsing file %s/%s", $index, $total)); 93 | $parser->parse($file); 94 | ++$index; 95 | } 96 | 97 | $output->writeln(PHP_EOL); 98 | 99 | $errorCollection = $parser->getErrorCollection(); 100 | $errorCount = count($errorCollection); 101 | 102 | if ($errorCount > 0) { 103 | $message = $formatter->formatBlock( 104 | sprintf( 105 | '%s %s found on your code.', 106 | $errorCount, 107 | $errorCount > 1 ? 'errors were' : 'error was' 108 | ), 109 | 'error', 110 | true 111 | ); 112 | $output->writeln($message); 113 | $output->writeln(''); 114 | 115 | /** @var Error $error */ 116 | foreach ($errorCollection as $error) { 117 | $output->writeln(sprintf(' %s', $error->getMessage())); 118 | if ($error->getHelp()) { 119 | $output->writeln(sprintf(' > %s', $error->getHelp())); 120 | } 121 | $output->writeln(sprintf( 122 | ' %s on line %s', 123 | str_replace($path, '', $error->getFilename()), 124 | $error->getLine() 125 | )); 126 | $output->writeln(''); 127 | } 128 | 129 | return 1; 130 | } 131 | 132 | $message = $formatter->formatBlock('No error was found when checking your code.', 'info', false); 133 | $output->writeln($message); 134 | $output->writeln(''); 135 | 136 | $message = $formatter->formatBlock(array( 137 | 'Note:', 138 | '> As this tool only do a static analyze of your code, you cannot blindly rely on it.', 139 | '> The best way to ensure that your code can run on PHP 7 is still to run a complete test suite on the targeted version of PHP.', 140 | ), 'comment', false); 141 | $output->writeln($message); 142 | $output->writeln(PHP_EOL); 143 | 144 | return 0; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Error/Error.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Error; 13 | 14 | /** 15 | * An error found by a Checker. 16 | */ 17 | class Error 18 | { 19 | /** @var string */ 20 | private $filename; 21 | 22 | /** @var int */ 23 | private $line; 24 | 25 | /** @var string */ 26 | private $message; 27 | 28 | /** @var string */ 29 | private $help; 30 | 31 | /** 32 | * Error constructor. 33 | * 34 | * @param string $filename 35 | * @param int $line 36 | * @param string $message 37 | * @param string $help 38 | */ 39 | public function __construct($filename, $line, $message, $help = '') 40 | { 41 | $this->filename = $filename; 42 | $this->line = $line; 43 | $this->message = $message; 44 | $this->help = $help; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getFilename() 51 | { 52 | return $this->filename; 53 | } 54 | 55 | /** 56 | * @return int 57 | */ 58 | public function getLine() 59 | { 60 | return $this->line; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getMessage() 67 | { 68 | return $this->message; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getHelp() 75 | { 76 | return $this->help; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Error/ErrorCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Error; 13 | 14 | /** 15 | * A collection of Errors. 16 | */ 17 | class ErrorCollection implements \Countable, \IteratorAggregate 18 | { 19 | /** @var Error[] */ 20 | private $errors = array(); 21 | 22 | /** 23 | * @param Error $e 24 | */ 25 | public function add(Error $e) 26 | { 27 | $this->errors[] = $e; 28 | } 29 | 30 | /** 31 | * @return Error[] 32 | */ 33 | public function all() 34 | { 35 | return $this->errors; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function count() 42 | { 43 | return count($this->errors); 44 | } 45 | 46 | /** 47 | * Get the error at the given index. 48 | * 49 | * @return Error 50 | */ 51 | public function get($index) 52 | { 53 | if (!isset($this->errors[$index])) { 54 | throw new \OutOfBoundsException(sprintf('No error exists at the index "%s', $index)); 55 | } 56 | 57 | return $this->errors[$index]; 58 | } 59 | 60 | /** 61 | * Reset the errors list. 62 | */ 63 | public function reset() 64 | { 65 | $this->errors = array(); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function getIterator() 72 | { 73 | return new \ArrayIterator($this->errors); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker; 13 | 14 | use Joli\Php7Checker\Checker\CheckerInterface; 15 | 16 | class Factory 17 | { 18 | /** 19 | * @return CheckerInterface[] 20 | */ 21 | public static function createDefaultCheckers() 22 | { 23 | return array( 24 | new \Joli\Php7Checker\Checker\TypeReservedChecker(), 25 | new \Joli\Php7Checker\Checker\Php4ConstructorChecker(), 26 | new \Joli\Php7Checker\Checker\FunctionParametersSameNameChecker(), 27 | new \Joli\Php7Checker\Checker\GlobalVariableRemovedChecker(), 28 | new \Joli\Php7Checker\Checker\FunctionRemovedChecker(), 29 | new \Joli\Php7Checker\Checker\ListHandlingChangedChecker(), 30 | new \Joli\Php7Checker\Checker\NewAssignmentByReferenceChecker(), 31 | new \Joli\Php7Checker\Checker\ClassAddedChecker(), 32 | new \Joli\Php7Checker\Checker\FunctionAddedChecker(), 33 | new \Joli\Php7Checker\Checker\IntegerHandlingChangedChecker(), 34 | ); 35 | } 36 | 37 | /** 38 | * @return Parser 39 | */ 40 | public static function createParser() 41 | { 42 | $parser = new Parser( 43 | static::createDefaultCheckers() 44 | ); 45 | 46 | return $parser; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Lexer/KeepOriginalValueLexer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\Lexer; 13 | 14 | use PhpParser\Lexer\Emulative; 15 | use PhpParser\Parser; 16 | 17 | class KeepOriginalValueLexer extends Emulative 18 | { 19 | public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) 20 | { 21 | $tokenId = parent::getNextToken($value, $startAttributes, $endAttributes); 22 | 23 | if ($tokenId == Parser::T_CONSTANT_ENCAPSED_STRING // non-interpolated string 24 | || $tokenId == Parser::T_LNUMBER // integer 25 | || $tokenId == Parser::T_DNUMBER // floating point number 26 | ) { 27 | // could also use $startAttributes, doesn't really matter here 28 | $endAttributes['originalValue'] = $value; 29 | } 30 | 31 | return $tokenId; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/PHPUnit/SameErrorsConstraint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\PHPUnit; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use Joli\Php7Checker\Error\ErrorCollection; 16 | 17 | class SameErrorsConstraint extends \PHPUnit_Framework_Constraint 18 | { 19 | /** @var ErrorCollection */ 20 | private $expectedErrors; 21 | 22 | /** 23 | * @param ErrorCollection $expectedErrors 24 | */ 25 | public function __construct(ErrorCollection $expectedErrors) 26 | { 27 | $this->expectedErrors = $expectedErrors; 28 | 29 | parent::__construct(); 30 | } 31 | 32 | public function matches($other) 33 | { 34 | if (!$other instanceof ErrorCollection) { 35 | return false; 36 | } 37 | 38 | if (count($other) !== count($this->expectedErrors)) { 39 | return false; 40 | } 41 | 42 | foreach ($this->expectedErrors as $i => $expectedError) { 43 | if (!$this->matchError($expectedError, $other->get($i))) { 44 | return false; 45 | } 46 | } 47 | 48 | return true; 49 | } 50 | 51 | /** 52 | * @param Error $expectedError 53 | * @param Error $actualError 54 | * 55 | * @return bool 56 | */ 57 | private function matchError(Error $expectedError, Error $actualError) 58 | { 59 | return $expectedError->getFilename() === $actualError->getFilename() 60 | && $expectedError->getLine() === $actualError->getLine() 61 | && $expectedError->getMessage() === $actualError->getMessage(); 62 | } 63 | 64 | /** 65 | * @param ErrorCollection $errors 66 | * 67 | * @return string 68 | */ 69 | private function exportErrors(ErrorCollection $errors) 70 | { 71 | $string = ''; 72 | 73 | if (count($errors) < 1) { 74 | $string .= 'an empty ErrorCollection '; 75 | } else { 76 | $string .= 'an ErrorCollection with:'.PHP_EOL; 77 | /** @var Error $error */ 78 | foreach ($errors as $error) { 79 | $string .= ' > '.$error->getFilename().':'.$error->getLine().PHP_EOL 80 | .' "'.$error->getMessage().'"'.PHP_EOL; 81 | } 82 | } 83 | 84 | return $string; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | protected function failureDescription($other) 91 | { 92 | if (!$other instanceof ErrorCollection) { 93 | return 'errors is an instance of ErrorCollection'; 94 | } 95 | 96 | return $this->exportErrors($other).$this->toString(); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function toString() 103 | { 104 | return 'is identical to '.trim($this->exportErrors($this->expectedErrors)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Parser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker; 13 | 14 | use Joli\Php7Checker\Checker\CheckerInterface; 15 | use Joli\Php7Checker\Error\ErrorCollection; 16 | use Joli\Php7Checker\Lexer\KeepOriginalValueLexer; 17 | use PhpParser\Error as PhpParserError; 18 | use PhpParser\NodeTraverser; 19 | use PhpParser\NodeTraverserInterface; 20 | use PhpParser\NodeVisitor\NameResolver; 21 | use PhpParser\Parser as PhpParser; 22 | 23 | class Parser 24 | { 25 | const VERSION = '0.1.0'; 26 | 27 | /** @var PhpParser */ 28 | private $parser; 29 | 30 | /** @var NodeTraverserInterface */ 31 | private $traverser; 32 | 33 | /** @var ParserContext */ 34 | private $parserContext; 35 | 36 | /** @var ErrorCollection */ 37 | private $errorCollection; 38 | 39 | /** 40 | * @param CheckerInterface[] $checkers 41 | */ 42 | public function __construct(array $checkers) 43 | { 44 | $this->parser = new PhpParser(new KeepOriginalValueLexer()); 45 | $this->traverser = new NodeTraverser(); 46 | $this->parserContext = new ParserContext(); 47 | $this->errorCollection = new ErrorCollection(); 48 | 49 | // Resolve fully qualified name (class, interface, function, etc) to ease some process 50 | $this->traverser->addVisitor(new NameResolver()); 51 | 52 | // Register all the checker that should visit the parsed files 53 | foreach ($checkers as $checker) { 54 | $checker->setParserContext($this->parserContext); 55 | $checker->setErrorCollection($this->errorCollection); 56 | $this->traverser->addVisitor($checker); 57 | } 58 | } 59 | 60 | /** 61 | * Parse the given file to check potential errors. 62 | * 63 | * @param \SplFileInfo $file 64 | */ 65 | public function parse(\SplFileInfo $file) 66 | { 67 | try { 68 | $content = file_get_contents($file->getRealPath()); 69 | 70 | if (strlen($content) > 0) { 71 | // Keep the context up to date 72 | $this->parserContext->setFilename($file->getRealPath()); 73 | 74 | // Parse the file content 75 | $stmts = $this->parser->parse($content); 76 | 77 | // Traverse the statements 78 | $this->traverser->traverse($stmts); 79 | } 80 | } catch (PhpParserError $e) { 81 | echo 'Parse Error: ', $e->getMessage(); 82 | } 83 | } 84 | 85 | /** 86 | * @return ErrorCollection 87 | */ 88 | public function getErrorCollection() 89 | { 90 | return $this->errorCollection; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/ParserContext.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker; 13 | 14 | /** 15 | * Context about the current parsed snippet of code. 16 | */ 17 | class ParserContext 18 | { 19 | /** @var string */ 20 | private $filename; 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getFilename() 26 | { 27 | return $this->filename; 28 | } 29 | 30 | /** 31 | * @param string $filename 32 | */ 33 | public function setFilename($filename) 34 | { 35 | $this->filename = $filename; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Functional/AbstractFunctionalTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | use Joli\Php7Checker\Factory; 15 | use Joli\Php7Checker\Error\Error; 16 | use Joli\Php7Checker\Error\ErrorCollection; 17 | use Joli\Php7Checker\Parser; 18 | use Joli\Php7Checker\PHPUnit\SameErrorsConstraint; 19 | use PHPUnit_Framework_TestCase; 20 | 21 | class AbstractFunctionalTestCase extends PHPUnit_Framework_TestCase 22 | { 23 | /** @var Parser */ 24 | private static $parser; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public static function setUpBeforeClass() 30 | { 31 | self::$parser = Factory::createParser(); 32 | } 33 | 34 | /** 35 | * @param array $expectedErrorsAsArray 36 | * @param string $filename 37 | * @param string $message 38 | */ 39 | protected function assertErrors($expectedErrorsAsArray, $filename, $message = '') 40 | { 41 | $errorCollection = self::$parser->getErrorCollection(); 42 | $filename = dirname(__DIR__).'/fixtures/'.$filename; 43 | $file = new \SplFileInfo($filename); 44 | 45 | $errorCollection->reset(); 46 | self::$parser->parse($file); 47 | 48 | $expectedErrors = new ErrorCollection(); 49 | foreach ($expectedErrorsAsArray as $errorAsArray) { 50 | $expectedErrors->add(new Error($filename, $errorAsArray[0], $errorAsArray[1])); 51 | } 52 | 53 | self::assertThat($errorCollection, new SameErrorsConstraint($expectedErrors), $message); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Functional/ClassAddedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class ClassAddedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenUsingNotNamespacedClassWithNameDifferentThanNewClasses() 17 | { 18 | $this->assertErrors([], 'Checker/ClassAdded/Foo.php'); 19 | } 20 | 21 | public function testItFindsNoErrorWhenUsingNamespacedClassWithNameIdenticalToNewClasses() 22 | { 23 | $this->assertErrors([], 'Checker/ClassAdded/Error.php'); 24 | } 25 | 26 | public function testItFindsErrorWhenUsingNotNamespacedClassWithNameIdenticalToNewClasses() 27 | { 28 | $this->assertErrors([ 29 | [3, '"Throwable" cannot be used as class/interface/trait name.'], 30 | ], 'Checker/ClassAdded/Throwable.php'); 31 | 32 | $this->assertErrors([ 33 | [4, '"IntlChar" cannot be used as class/interface/trait name.'], 34 | ], 'Checker/ClassAdded/IntlChar.php'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Functional/FunctionAddedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class FunctionAddedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenUsingNotNamespacedFunctionWithNameDifferentThanNewFunctions() 17 | { 18 | $this->assertErrors([], 'Checker/FunctionAdded/test1.php'); 19 | } 20 | 21 | public function testItFindsNoErrorWhenUsingNamespacedFunctionWithNameIdenticalToNewFunctions() 22 | { 23 | $this->assertErrors([], 'Checker/FunctionAdded/test2.php'); 24 | } 25 | 26 | public function testItFindsErrorWhenUsingNotNamespacedFunctionWithNameIdenticalToNewFunctions() 27 | { 28 | $this->assertErrors([ 29 | [3, '"random_int" cannot be used as function name.'], 30 | ], 'Checker/FunctionAdded/test3.php'); 31 | } 32 | 33 | public function testItFindsNoErrorWhenUsingClassMethodWithNameIdenticalToNewFunctions() 34 | { 35 | $this->assertErrors([], 'Checker/FunctionAdded/test4.php'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Functional/FunctionParametersSameNameTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class FunctionParametersSameNameTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorForFunctionWithDifferentParameters() 17 | { 18 | $this->assertErrors([], 'Checker/FunctionParametersSameName/test1.php'); 19 | } 20 | 21 | public function testItFindsErrorForFunctionWithParametersHavingSameName() 22 | { 23 | $this->assertErrors([ 24 | [3, 'Functions can no longer have more than one parameter with the same name.'], 25 | ], 'Checker/FunctionParametersSameName/test2.php'); 26 | 27 | $this->assertErrors([ 28 | [3, 'Functions can no longer have more than one parameter with the same name.'], 29 | ], 'Checker/FunctionParametersSameName/test3.php'); 30 | 31 | $this->assertErrors([ 32 | [3, 'Functions can no longer have more than one parameter with the same name.'], 33 | ], 'Checker/FunctionParametersSameName/test4.php'); 34 | } 35 | 36 | public function testItFindsNoErrorForClassMethodWithDifferentParameters() 37 | { 38 | $this->assertErrors([], 'Checker/FunctionParametersSameName/test5.php'); 39 | } 40 | 41 | public function testItFindsErrorForClassMethodWithParametersHavingSameName() 42 | { 43 | $this->assertErrors([ 44 | [5, 'Functions can no longer have more than one parameter with the same name.'], 45 | ], 'Checker/FunctionParametersSameName/test6.php'); 46 | } 47 | 48 | public function testItFindsNoErrorForFunctionWithNoParameters() 49 | { 50 | $this->assertErrors([], 'Checker/FunctionParametersSameName/test7.php'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Functional/FunctionRemovedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class FunctionRemovedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenNotUsingRemovedFunction() 17 | { 18 | $this->assertErrors([], 'Checker/FunctionRemoved/test1.php'); 19 | } 20 | 21 | public function testItFindsErrorWhenUsingRemovedFunction() 22 | { 23 | $this->assertErrors([ 24 | [5, 'The function call_user_method() was removed.'], 25 | ], 'Checker/FunctionRemoved/test3.php'); 26 | } 27 | 28 | // Cannot determine call of classMethod 29 | /* 30 | public function testItFindsNoErrorWhenNotUsingRemovedClassMethod() 31 | { 32 | $this->assertErrors([], 'Checker/FunctionRemoved/test2.php'); 33 | } 34 | 35 | public function testItFindsErrorWhenUsingRemovedClassMethod() 36 | { 37 | $this->assertErrors([ 38 | [5, 'The function IntlDateFormatter::setTimeZoneId() was removed.'], 39 | ], 'Checker/FunctionRemoved/test4.php'); 40 | } 41 | */ 42 | } 43 | -------------------------------------------------------------------------------- /tests/Functional/GlobalVariableRemovedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class GlobalVariableRemovedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenNotUsingRemovedGlobalVariableInGlobalScope() 17 | { 18 | $this->assertErrors([], 'Checker/GlobalVariableRemoved/test1.php'); 19 | } 20 | 21 | public function testItFindsErrorsWhenUsingRemovedGlobalVariableInGlobalScope() 22 | { 23 | $this->assertErrors([ 24 | [3, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 25 | ], 'Checker/GlobalVariableRemoved/test2.php'); 26 | 27 | $this->assertErrors([ 28 | [3, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 29 | ], 'Checker/GlobalVariableRemoved/test3.php'); 30 | 31 | $this->assertErrors([ 32 | [3, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 33 | ], 'Checker/GlobalVariableRemoved/test4.php'); 34 | 35 | $this->assertErrors([ 36 | [3, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 37 | ], 'Checker/GlobalVariableRemoved/test5.php'); 38 | } 39 | 40 | public function testItFindsNoErrorWhenNotUsingRemovedGlobalVariableInLocalScope() 41 | { 42 | $this->assertErrors([], 'Checker/GlobalVariableRemoved/test6.php'); 43 | } 44 | 45 | public function testItFindsNoErrorWhenUsingVariableInLocalScopeHavingTheSameNameThanRemovedGlobalVariable() 46 | { 47 | $this->assertErrors([], 'Checker/GlobalVariableRemoved/test7.php'); 48 | 49 | $this->assertErrors([], 'Checker/GlobalVariableRemoved/test8.php'); 50 | } 51 | 52 | public function testItFindsErrorsWhenUsingRemovedGlobalVariableInLocalScope() 53 | { 54 | $this->assertErrors([ 55 | [7, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 56 | ], 'Checker/GlobalVariableRemoved/test9.php'); 57 | 58 | $this->assertErrors([ 59 | [7, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 60 | ], 'Checker/GlobalVariableRemoved/test10.php'); 61 | 62 | $this->assertErrors([ 63 | [5, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 64 | ], 'Checker/GlobalVariableRemoved/test11.php'); 65 | 66 | $this->assertErrors([ 67 | [5, 'The global variable $HTTP_RAW_POST_DATA was removed.'], 68 | ], 'Checker/GlobalVariableRemoved/test12.php'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Functional/IntegerHandlingChangedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class IntegerHandlingChangedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenNotUsingOctalLiteral() 17 | { 18 | $this->assertErrors([], 'Checker/IntegerHandlingChanged/test5.php'); 19 | $this->assertErrors([], 'Checker/IntegerHandlingChanged/test6.php'); 20 | } 21 | 22 | public function testItFindsNoErrorWhenUsingValidOctalLiteral() 23 | { 24 | $this->assertErrors([], 'Checker/IntegerHandlingChanged/test1.php'); 25 | $this->assertErrors([], 'Checker/IntegerHandlingChanged/test2.php'); 26 | } 27 | 28 | public function testItFindsErrorWhenUsingInvalidOctalLiteral() 29 | { 30 | $this->assertErrors([ 31 | [3, '"0128" is an invalid octal literal and will now triggers a parse error.'], 32 | ], 'Checker/IntegerHandlingChanged/test3.php'); 33 | 34 | $this->assertErrors([ 35 | [3, '"0128" is an invalid octal literal and will now triggers a parse error.'], 36 | ], 'Checker/IntegerHandlingChanged/test4.php'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Functional/ListHandlingChangedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class ListHandlingChangedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenNotUsingEmptyList() 17 | { 18 | $this->assertErrors([], 'Checker/ListHandlingChanged/test1.php'); 19 | $this->assertErrors([], 'Checker/ListHandlingChanged/test5.php'); 20 | } 21 | 22 | public function testItFindsErrorWhenUsingEmptyList() 23 | { 24 | $this->assertErrors([ 25 | [3, 'Empty list() assignments are no longer supported.'], 26 | ], 'Checker/ListHandlingChanged/test2.php'); 27 | 28 | $this->assertErrors([ 29 | [3, 'Empty list() assignments are no longer supported.'], 30 | ], 'Checker/ListHandlingChanged/test3.php'); 31 | 32 | $this->assertErrors([ 33 | [3, 'Empty list() assignments are no longer supported.'], 34 | ], 'Checker/ListHandlingChanged/test4.php'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Functional/NewAssignmentByReferenceTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class NewAssignmentByReferenceTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenUsingNewAssignmentNotByReference() 17 | { 18 | $this->assertErrors([], 'Checker/NewAssignmentByReference/test1.php'); 19 | } 20 | 21 | public function testItFindsNoErrorWhenUsingStandardAssignmentByReference() 22 | { 23 | $this->assertErrors([], 'Checker/NewAssignmentByReference/test2.php'); 24 | } 25 | 26 | public function testItFindsErrorWhenUsingNewAssignmentByReference() 27 | { 28 | $this->assertErrors([ 29 | [3, 'New objects cannot be assigned by reference.'], 30 | ], 'Checker/NewAssignmentByReference/test3.php'); 31 | 32 | $this->assertErrors([ 33 | [5, 'New objects cannot be assigned by reference.'], 34 | ], 'Checker/NewAssignmentByReference/test4.php'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Functional/Php4ConstructorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class Php4ConstructorTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWithPhp5Constructor() 17 | { 18 | $this->assertErrors([], 'Checker/Php4Constructor/Squirtle.php'); 19 | } 20 | 21 | public function testItFindsNoErrorWithPhp4ConstructorAndNamespace() 22 | { 23 | $this->assertErrors([], 'Checker/Php4Constructor/Bulbasaur.php'); 24 | } 25 | 26 | public function testItFindsNoErrorWithPhp4AndPhp5Constructors() 27 | { 28 | $this->assertErrors([], 'Checker/Php4Constructor/Charmander.php'); 29 | } 30 | 31 | public function testItFindsNoErrorWithPhp4AndPhp5ConstructorsAndNamespace() 32 | { 33 | $this->assertErrors([], 'Checker/Php4Constructor/Mew.php'); 34 | } 35 | 36 | public function testItFindsErrorWithPhp4ConstructorOnly() 37 | { 38 | $this->assertErrors([ 39 | [5, 'Using a PHP 4 constructor is now deprecated.'], 40 | ], 'Checker/Php4Constructor/Pikachu.php'); 41 | } 42 | 43 | public function testItFindsErrorWithPhp4ConstructorAndRootNamespace() 44 | { 45 | $this->assertErrors([ 46 | [6, 'Using a PHP 4 constructor is now deprecated.'], 47 | ], 'Checker/Php4Constructor/Jigglypuff.php'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Functional/TypeReservedTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\Functional; 13 | 14 | class TypeReservedTest extends AbstractFunctionalTestCase 15 | { 16 | public function testItFindsNoErrorWhenNotUsingReservedTypeAsClassNameOrInNamespace() 17 | { 18 | $this->assertErrors([], 'Checker/TypeReserved/Float_.php'); 19 | $this->assertErrors([], 'Checker/TypeReserved/Integer.php'); 20 | } 21 | 22 | public function testItFindsErrorWhenUsingReservedTypeAsClassName() 23 | { 24 | $this->assertErrors([ 25 | [5, '"object" is now a reserved type and can no longer be used as the name of a class/interface/trait.'], 26 | ], 'Checker/TypeReserved/Object.php'); 27 | } 28 | 29 | public function testItFindsNoErrorWhenNotUsingReservedTypeAsAlias() 30 | { 31 | $this->assertErrors([], 'Checker/TypeReserved/Bar.php'); 32 | } 33 | 34 | public function testItFindsErrorWhenUsingReservedTypeAsAlias() 35 | { 36 | $this->assertErrors([ 37 | [7, '"string" is now a reserved type and can no longer be used as an alias.'], 38 | ], 'Checker/TypeReserved/Foo.php'); 39 | 40 | $this->assertErrors([ 41 | [3, '"int" is now a reserved type and can no longer be used as an alias.'], 42 | ], 'Checker/TypeReserved/Baz.php'); 43 | } 44 | 45 | public function testItFindsNoErrorWhenNotUsingReservedTypeInClassAlias() 46 | { 47 | $this->assertErrors([], 'Checker/TypeReserved/Toto1.php'); 48 | } 49 | 50 | public function testItFindsErrorWhenUsingReservedTypeInClassAlias() 51 | { 52 | $this->assertErrors([ 53 | [8, '"true" is now a reserved type and can no longer be used in class_alias().'], 54 | ], 'Checker/TypeReserved/Toto2.php'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/PHPUnit/SameErrorsConstraintTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Joli\Php7Checker\tests\PHPUnit; 13 | 14 | use Joli\Php7Checker\Error\Error; 15 | use Joli\Php7Checker\Error\ErrorCollection; 16 | use Joli\Php7Checker\PHPUnit\SameErrorsConstraint; 17 | 18 | class SameErrorsConstraintTest extends \PHPUnit_Framework_TestCase 19 | { 20 | public function testNotAnErrorCollectionAndErrorCollectionDontMatch() 21 | { 22 | $constraint = new SameErrorsConstraint(new ErrorCollection()); 23 | $this->assertFalse($constraint->matches(new \stdClass())); 24 | } 25 | 26 | public function testEmptyErrorCollectionsMatche() 27 | { 28 | $constraint = new SameErrorsConstraint(new ErrorCollection()); 29 | $this->assertTrue($constraint->matches(new ErrorCollection())); 30 | } 31 | 32 | public function testErrorCollectionsWithDifferentSizesDontMatch() 33 | { 34 | $error = new Error('toto.php', 1, 'info'); 35 | 36 | $errorCollection1 = new ErrorCollection(); 37 | $errorCollection2 = new ErrorCollection(); 38 | 39 | $errorCollection1->add($error); 40 | 41 | $constraint = new SameErrorsConstraint($errorCollection1); 42 | $this->assertFalse($constraint->matches($errorCollection2)); 43 | 44 | $errorCollection1 = new ErrorCollection(); 45 | $errorCollection2 = new ErrorCollection(); 46 | 47 | $errorCollection2->add($error); 48 | 49 | $constraint = new SameErrorsConstraint($errorCollection1); 50 | $this->assertFalse($constraint->matches($errorCollection2)); 51 | 52 | $errorCollection1 = new ErrorCollection(); 53 | $errorCollection2 = new ErrorCollection(); 54 | 55 | $errorCollection1->add($error); 56 | $errorCollection2->add($error); 57 | $errorCollection2->add($error); 58 | 59 | $constraint = new SameErrorsConstraint($errorCollection1); 60 | $this->assertFalse($constraint->matches($errorCollection2)); 61 | 62 | $errorCollection1 = new ErrorCollection(); 63 | $errorCollection2 = new ErrorCollection(); 64 | 65 | $errorCollection1->add($error); 66 | $errorCollection1->add($error); 67 | $errorCollection2->add($error); 68 | 69 | $constraint = new SameErrorsConstraint($errorCollection1); 70 | $this->assertFalse($constraint->matches($errorCollection2)); 71 | } 72 | 73 | public function testErrorCollectionsWithDifferentErrorsDontMatch() 74 | { 75 | $error1 = new Error('toto1.php', 1, 'info1'); 76 | $error2 = new Error('toto1.php', 1, 'info2'); 77 | $error3 = new Error('toto1.php', 2, 'info1'); 78 | $error4 = new Error('toto2.php', 1, 'info1'); 79 | 80 | $errorCollection1 = new ErrorCollection(); 81 | $errorCollection2 = new ErrorCollection(); 82 | 83 | $errorCollection1->add($error1); 84 | $errorCollection2->add($error2); 85 | 86 | $constraint = new SameErrorsConstraint($errorCollection1); 87 | $this->assertFalse($constraint->matches($errorCollection2)); 88 | 89 | $errorCollection1 = new ErrorCollection(); 90 | $errorCollection2 = new ErrorCollection(); 91 | 92 | $errorCollection1->add($error1); 93 | $errorCollection2->add($error3); 94 | 95 | $constraint = new SameErrorsConstraint($errorCollection1); 96 | $this->assertFalse($constraint->matches($errorCollection2)); 97 | 98 | $errorCollection1 = new ErrorCollection(); 99 | $errorCollection2 = new ErrorCollection(); 100 | 101 | $errorCollection1->add($error1); 102 | $errorCollection2->add($error4); 103 | 104 | $constraint = new SameErrorsConstraint($errorCollection1); 105 | $this->assertFalse($constraint->matches($errorCollection2)); 106 | 107 | $errorCollection1 = new ErrorCollection(); 108 | $errorCollection2 = new ErrorCollection(); 109 | 110 | $errorCollection1->add($error1); 111 | $errorCollection1->add($error2); 112 | $errorCollection2->add($error1); 113 | $errorCollection2->add($error3); 114 | 115 | $constraint = new SameErrorsConstraint($errorCollection1); 116 | $this->assertFalse($constraint->matches($errorCollection2)); 117 | } 118 | 119 | public function testErrorCollectionsWithSameErrorsNotInTheSameOrderDontMatch() 120 | { 121 | $error1 = new Error('toto1.php', 1, 'info1'); 122 | $error2 = new Error('toto2.php', 2, 'info2'); 123 | 124 | $errorCollection1 = new ErrorCollection(); 125 | $errorCollection2 = new ErrorCollection(); 126 | 127 | $errorCollection1->add($error1); 128 | $errorCollection1->add($error2); 129 | $errorCollection2->add($error2); 130 | $errorCollection2->add($error1); 131 | 132 | $constraint = new SameErrorsConstraint($errorCollection1); 133 | $this->assertFalse($constraint->matches($errorCollection2)); 134 | } 135 | 136 | public function testErrorCollectionsWithSameErrorsInMatch() 137 | { 138 | $error1 = new Error('toto1.php', 1, 'info1'); 139 | $error2 = new Error('toto2.php', 2, 'info2'); 140 | 141 | $errorCollection1 = new ErrorCollection(); 142 | $errorCollection2 = new ErrorCollection(); 143 | 144 | $errorCollection1->add($error1); 145 | $errorCollection2->add($error1); 146 | 147 | $constraint = new SameErrorsConstraint($errorCollection1); 148 | $this->assertTrue($constraint->matches($errorCollection2)); 149 | 150 | $errorCollection1 = new ErrorCollection(); 151 | $errorCollection2 = new ErrorCollection(); 152 | 153 | $errorCollection1->add($error1); 154 | $errorCollection1->add($error2); 155 | $errorCollection2->add($error1); 156 | $errorCollection2->add($error2); 157 | 158 | $constraint = new SameErrorsConstraint($errorCollection1); 159 | $this->assertTrue($constraint->matches($errorCollection2)); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/fixtures/Checker/ClassAdded/Error.php: -------------------------------------------------------------------------------- 1 | format('d/m/Y'); 6 | -------------------------------------------------------------------------------- /tests/fixtures/Checker/FunctionRemoved/test3.php: -------------------------------------------------------------------------------- 1 | setTimeZoneId('Europe/Paris'); 6 | -------------------------------------------------------------------------------- /tests/fixtures/Checker/GlobalVariableRemoved/test1.php: -------------------------------------------------------------------------------- 1 |