diff --git a/.gitattributes b/.gitattributes
index 51b431136..9cd1bb7da 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,3 +15,4 @@
.travis.yml export-ignore
appveyor.yml export-ignore
phpunit.xml.dist export-ignore
+/phpstan/ export-ignore
diff --git a/.travis.yml b/.travis.yml
index 0cc13f91a..49822707f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,7 +25,9 @@ matrix:
- php: 7.1
- php: 7.2
- php: 7.3
- env: PHPSTAN=1
+ env:
+ - deps=high
+ - PHPSTAN=1
- php: 7.3
env:
- deps=high
@@ -64,8 +66,8 @@ script:
- ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
# Run PHPStan
- if [[ $PHPSTAN == "1" ]]; then
- bin/composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs &&
- vendor/bin/phpstan.phar analyse --configuration=phpstan/config.neon;
+ bin/composer require --dev phpstan/phpstan:^0.12 &&
+ vendor/bin/phpstan analyse --configuration=phpstan/config.neon;
fi
before_deploy:
diff --git a/composer.json b/composer.json
index d1f98a56a..435aa5c01 100644
--- a/composer.json
+++ b/composer.json
@@ -66,8 +66,13 @@
},
"autoload-dev": {
"psr-4": {
- "Composer\\Test\\": "tests/Composer/Test"
- }
+ "Composer\\Test\\": "tests/Composer/Test",
+ "Composer\\PHPStanRules\\": "phpstan/Rules/src",
+ "Composer\\PHPStanRulesTests\\": "phpstan/Rules/tests"
+ },
+ "classmap": [
+ "phpstan/Rules/tests/data"
+ ]
},
"bin": [
"bin/composer"
diff --git a/phpstan/Rules/src/AnonymousFunctionWithThisRule.php b/phpstan/Rules/src/AnonymousFunctionWithThisRule.php
new file mode 100644
index 000000000..d9a44ecb5
--- /dev/null
+++ b/phpstan/Rules/src/AnonymousFunctionWithThisRule.php
@@ -0,0 +1,46 @@
+
+ */
+final class AnonymousFunctionWithThisRule implements Rule
+{
+ /**
+ * @inheritDoc
+ */
+ public function getNodeType(): string
+ {
+ return \PhpParser\Node\Expr\Variable::class;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function processNode(Node $node, Scope $scope): array
+ {
+ if (!\is_string($node->name) || $node->name !== 'this') {
+ return [];
+ }
+
+ if ($scope->isInClosureBind()) {
+ return [];
+ }
+
+ if (!$scope->isInClass()) {
+ // reported in other standard rule on level 0
+ return [];
+ }
+
+ if ($scope->isInAnonymousFunction()) {
+ return ['Using $this inside anonymous function is prohibited because of PHP 5.3 support.'];
+ }
+
+ return [];
+ }
+}
diff --git a/phpstan/Rules/tests/AnonymousFunctionWithThisRuleTest.php b/phpstan/Rules/tests/AnonymousFunctionWithThisRuleTest.php
new file mode 100644
index 000000000..add285d17
--- /dev/null
+++ b/phpstan/Rules/tests/AnonymousFunctionWithThisRuleTest.php
@@ -0,0 +1,28 @@
+
+ */
+final class AnonymousFunctionWithThisRuleTest extends RuleTestCase
+{
+ /**
+ * @inheritDoc
+ */
+ protected function getRule(): \PHPStan\Rules\Rule
+ {
+ return new AnonymousFunctionWithThisRule();
+ }
+
+ public function testWithThis(): void
+ {
+ $this->analyse([__DIR__ . '/data/method-with-this.php'], [
+ ['Using $this inside anonymous function is prohibited because of PHP 5.3 support.', 13],
+ ['Using $this inside anonymous function is prohibited because of PHP 5.3 support.', 17],
+ ]);
+ }
+}
diff --git a/phpstan/Rules/tests/data/method-with-this.php b/phpstan/Rules/tests/data/method-with-this.php
new file mode 100644
index 000000000..e0b15bffa
--- /dev/null
+++ b/phpstan/Rules/tests/data/method-with-this.php
@@ -0,0 +1,34 @@
+firstProp;
+ };
+
+ call_user_func(function() {
+ $this->funMethod();
+ }, $this);
+
+ $bind = 'bind';
+ function() use($bind) {
+
+ };
+ }
+}
+
+function global_ok() {
+ $_SERVER['REMOTE_ADDR'];
+}
+
+function global_this() {
+ // not checked by our rule, it is checked with standard phpstan rule on level 0
+ $this['REMOTE_ADDR'];
+}
diff --git a/phpstan/config.neon b/phpstan/config.neon
index ae53ac531..599d27d51 100644
--- a/phpstan/config.neon
+++ b/phpstan/config.neon
@@ -1,24 +1,12 @@
parameters:
autoload_files:
- - phpstan/autoload.php
+ - autoload.php
level: 0
excludes_analyse:
- - 'tests/Composer/Test/Fixtures'
- - 'tests/Composer/Test/Autoload/Fixtures'
- - 'tests/Composer/Test/Plugin/Fixtures'
+ - '../tests/Composer/Test/Fixtures/*'
+ - '../tests/Composer/Test/Autoload/Fixtures/*'
+ - '../tests/Composer/Test/Plugin/Fixtures/*'
ignoreErrors:
- # unused parameters
- - '~^Constructor of class Composer\\Repository\\VcsRepository has an unused parameter \$dispatcher\.$~'
- - '~^Constructor of class Composer\\Repository\\PearRepository has an unused parameter \$dispatcher\.$~'
- - '~^Constructor of class Composer\\Util\\Http\\CurlDownloader has an unused parameter \$disableTls\.$~'
- - '~^Constructor of class Composer\\Util\\Http\\CurlDownloader has an unused parameter \$options\.$~'
- - '~^Constructor of class Composer\\Repository\\PearRepository has an unused parameter \$config\.$~'
-
- # unused uses
- - '~^Anonymous function has an unused use \$io\.$~'
- - '~^Anonymous function has an unused use \$cache\.$~'
- - '~^Anonymous function has an unused use \$path\.$~'
-
# ion cube is not installed
- '~^Function ioncube_loader_\w+ not found\.$~'
# rar is not installed
@@ -33,14 +21,17 @@ parameters:
# variable defined in eval
- '~^Undefined variable: \$res$~'
- # always checked whether the class exists
- - '~^Instantiated class Symfony\\Component\\Console\\Terminal not found\.$~'
- - '~^Class Symfony\\Component\\Console\\Input\\StreamableInputInterface not found\.$~'
- - '~^Call to an undefined static method Symfony\\Component\\Process\\Process::fromShellCommandline\(\).$~'
+ # we don't have different constructors for parent/child
+ - '~^Unsafe usage of new static\(\)\.$~'
- # parent call in test mocks
- - '~^Composer\\Test\\Mock\\HttpDownloaderMock::__construct\(\) does not call parent constructor from Composer\\Util\\HttpDownloader\.$~'
- - '~^Composer\\Test\\Mock\\InstallationManagerMock::__construct\(\) does not call parent constructor from Composer\\Installer\\InstallationManager\.$~'
+ # hhvm should have support for $this in closures
+ -
+ count: 1
+ message: '~^Using \$this inside anonymous function is prohibited because of PHP 5\.3 support\.$~'
+ path: '../tests/Composer/Test/Repository/PlatformRepositoryTest.php'
paths:
- - src
- - tests
+ - ../src
+ - ../tests
+
+rules:
+ - Composer\PHPStanRules\AnonymousFunctionWithThisRule
diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php
index 7c7e45354..9775c27d9 100644
--- a/src/Composer/Downloader/FileDownloader.php
+++ b/src/Composer/Downloader/FileDownloader.php
@@ -113,7 +113,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$accept = null;
$reject = null;
- $download = function () use ($io, $output, $httpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) {
+ $download = function () use ($io, $output, $httpDownloader, $cache, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject) {
$url = reset($urls);
if ($eventDispatcher) {
@@ -160,7 +160,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
});
};
- $accept = function ($response) use ($io, $cache, $package, $fileName, $path, $self, &$urls) {
+ $accept = function ($response) use ($cache, $package, $fileName, $self, &$urls) {
$url = reset($urls);
$cacheKey = $url['cacheKey'];
@@ -174,7 +174,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
return $fileName;
};
- $reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) {
+ $reject = function ($e) use ($io, &$urls, $download, $fileName, $package, &$retries, $filesystem, $self) {
// clean up
if (file_exists($fileName)) {
$filesystem->unlink($fileName);
diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php
index 9c3fb6884..8d1579b63 100644
--- a/src/Composer/Installer/InstallationManager.php
+++ b/src/Composer/Installer/InstallationManager.php
@@ -246,7 +246,7 @@ class InstallationManager
$dispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation);
}
}, function ($e) use ($jobType, $installer, $package, $initialPackage, $loop, $io) {
- $this->io->writeError(' ' . ucfirst($jobType) .' of '.$package->getPrettyName().' failed');
+ $io->writeError(' ' . ucfirst($jobType) .' of '.$package->getPrettyName().' failed');
$promise = $installer->cleanup($jobType, $package, $initialPackage);
if ($promise) {
diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php
index e4cfbba83..02f2a05fe 100644
--- a/src/Composer/Repository/ComposerRepository.php
+++ b/src/Composer/Repository/ComposerRepository.php
@@ -1119,7 +1119,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
return $data;
};
- $reject = function ($e) use (&$retries, $httpDownloader, $filename, $options, &$reject, $accept, $io, $url, $cache, &$degradedMode) {
+ $reject = function ($e) use (&$retries, $httpDownloader, $filename, $options, &$reject, $accept, $io, $url, &$degradedMode) {
if ($e instanceof TransportException && $e->getStatusCode() === 404) {
return false;
}
diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php
index c4b6c5424..8a117a232 100644
--- a/src/Composer/Util/HttpDownloader.php
+++ b/src/Composer/Util/HttpDownloader.php
@@ -190,7 +190,7 @@ class HttpDownloader
$downloader->scheduleNextJob();
return $response;
- }, function ($e) use ($io, &$job, $downloader) {
+ }, function ($e) use (&$job, $downloader) {
$job['status'] = HttpDownloader::STATUS_FAILED;
$job['exception'] = $e;