Skip to content

Commit 509ac53

Browse files
authored
Merge branch 'master' into phpunit
2 parents 8ba6f26 + 42c2de2 commit 509ac53

File tree

11 files changed

+83
-71
lines changed

11 files changed

+83
-71
lines changed

.travis.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,26 @@ php:
55
- 7.3
66

77
before_install:
8-
- composer self-update
98
- composer global require "hirak/prestissimo:^0.3"
109
install:
1110
- composer install --no-interaction
1211
script:
12+
# Inspections
1313
- ./vendor/bin/phpcs src
1414
- ./vendor/bin/phpstan analyze src
1515
- ./vendor/bin/phpunit
16+
17+
# Try to add to a Drupal setup.
18+
- composer create-project drupal-composer/drupal-project:8.x-dev $TRAVIS_BUILD_DIR/../drupal --no-interaction
19+
- cd $TRAVIS_BUILD_DIR/../drupal
20+
21+
# Install dependency.
22+
- composer config repositories.0 path $TRAVIS_BUILD_DIR
23+
- cat composer.json
24+
- composer require mglaman/phpstan-drupal *@dev
25+
- cp $TRAVIS_BUILD_DIR/tests/fixtures/drupal-phpstan.neon phpstan.neon
26+
- ./vendor/bin/phpstan analyze web/core/install.php
27+
1628
cache:
1729
directories:
1830
- $HOME/.composer/cache

composer.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"require": {
1212
"php": "^7.1",
1313
"phpstan/phpstan": "^0.10.6",
14-
"symfony/yaml": "^4.2"
14+
"symfony/yaml": "~3.4.5|^4.2",
15+
"webflo/drupal-finder": "^1.1"
1516
},
1617
"require-dev": {
1718
"phpstan/phpstan-strict-rules": "^0.10.1",
@@ -28,5 +29,10 @@
2829
},
2930
"autoload-dev": {
3031
"classmap": ["tests/"]
32+
},
33+
"extra": {
34+
"branch-alias": {
35+
"dev-master": "0.11-dev"
36+
}
3137
}
3238
}

src/DependencyInjection/DrupalExtension.php

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\DependencyInjection;
44

5+
use DrupalFinder\DrupalFinder;
56
use Nette\DI\CompilerExtension;
67
use Nette\DI\Config\Helpers;
78
use PHPStan\Drupal\ExtensionDiscovery;
@@ -19,11 +20,6 @@ class DrupalExtension extends CompilerExtension
1920
'themes' => [],
2021
];
2122

22-
/**
23-
* @var string
24-
*/
25-
private $autoloaderPath;
26-
2723
/**
2824
* @var string
2925
*/
@@ -55,24 +51,9 @@ class DrupalExtension extends CompilerExtension
5551

5652
public function loadConfiguration(): void
5753
{
58-
59-
$this->autoloaderPath = $GLOBALS['autoloaderInWorkingDirectory'];
60-
$realpath = realpath($this->autoloaderPath);
61-
if ($realpath === false) {
62-
throw new \InvalidArgumentException('Cannot determine the realpath of the autoloader.');
63-
}
64-
$project_root = dirname($realpath, 2);
65-
if (is_dir($project_root . '/core')) {
66-
$this->drupalRoot = $project_root;
67-
}
68-
foreach (['web', 'docroot'] as $possible_docroot) {
69-
if (is_dir("$project_root/$possible_docroot/core")) {
70-
$this->drupalRoot = "$project_root/$possible_docroot";
71-
}
72-
}
73-
if ($this->drupalRoot === null) {
74-
throw new \InvalidArgumentException('Unable to determine the Drupal root');
75-
}
54+
$finder = new DrupalFinder();
55+
$finder->locateRoot(getcwd());
56+
$this->drupalRoot = $finder->getDrupalRoot();
7657

7758
$builder = $this->getContainerBuilder();
7859
$builder->parameters['drupalRoot'] = $this->drupalRoot;
@@ -99,7 +80,7 @@ public function loadConfiguration(): void
9980
$extensionDiscovery = new ExtensionDiscovery($this->drupalRoot);
10081
$extensionDiscovery->setProfileDirectories([]);
10182
$profiles = $extensionDiscovery->scan('profile');
102-
$profile_directories = array_map(function ($profile) {
83+
$profile_directories = array_map(function (\PHPStan\Drupal\Extension $profile) : string {
10384
return $profile->getPath();
10485
}, $profiles);
10586
$extensionDiscovery->setProfileDirectories($profile_directories);
@@ -139,8 +120,13 @@ public function loadConfiguration(): void
139120
// Prevent \Nette\DI\ContainerBuilder::completeStatement from array_walk_recursive into the arguments
140121
// and thinking these are real services for PHPStan's container.
141122
if (isset($serviceDefinition['arguments']) && is_array($serviceDefinition['arguments'])) {
142-
array_walk($serviceDefinition['arguments'], function (&$argument) {
143-
$argument = str_replace('@', '', $argument);
123+
array_walk($serviceDefinition['arguments'], function (&$argument) : void {
124+
if (is_array($argument)) {
125+
// @todo fix for @http_kernel.controller.argument_metadata_factory
126+
$argument = '';
127+
} else {
128+
$argument = str_replace('@', '', $argument);
129+
}
144130
});
145131
}
146132
unset($serviceDefinition['tags']);

src/Drupal/Bootstrap.php

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace PHPStan\Drupal;
44

5-
use Composer\Autoload\ClassLoader;
5+
use DrupalFinder\DrupalFinder;
66
use Nette\Utils\Finder;
77

88
class Bootstrap
@@ -15,11 +15,6 @@ class Bootstrap
1515
'themes' => [],
1616
];
1717

18-
/**
19-
* @var string
20-
*/
21-
private $autoloaderPath;
22-
2318
/**
2419
* @var \Composer\Autoload\ClassLoader
2520
*/
@@ -55,7 +50,7 @@ class Bootstrap
5550
private $themes = [];
5651

5752
/**
58-
* @var \PHPStan\Drupal\ExtensionDiscovery
53+
* @var ?\PHPStan\Drupal\ExtensionDiscovery
5954
*/
6055
private $extensionDiscovery;
6156

@@ -66,34 +61,15 @@ class Bootstrap
6661

6762
public function register(): void
6863
{
69-
70-
$this->autoloaderPath = $GLOBALS['autoloaderInWorkingDirectory'];
71-
/** @noinspection PhpIncludeInspection */
72-
$this->autoloader = require $this->autoloaderPath;
73-
if (!$this->autoloader instanceof ClassLoader) {
74-
throw new \InvalidArgumentException('Unable to determine the Composer class loader for Drupal');
75-
}
76-
$realpath = realpath($this->autoloaderPath);
77-
if ($realpath === false) {
78-
throw new \InvalidArgumentException('Cannot determine the realpath of the autoloader.');
79-
}
80-
$project_root = dirname($realpath, 2);
81-
if (is_dir($project_root . '/core')) {
82-
$this->drupalRoot = $project_root;
83-
}
84-
foreach (['web', 'docroot'] as $possible_docroot) {
85-
if (is_dir("$project_root/$possible_docroot/core")) {
86-
$this->drupalRoot = "$project_root/$possible_docroot";
87-
}
88-
}
89-
if ($this->drupalRoot === null) {
90-
throw new \InvalidArgumentException('Unable to determine the Drupal root');
91-
}
64+
$finder = new DrupalFinder();
65+
$finder->locateRoot(getcwd());
66+
$this->autoloader = include $finder->getVendorDir() . '/autoload.php';
67+
$this->drupalRoot = $finder->getDrupalRoot();
9268

9369
$this->extensionDiscovery = new ExtensionDiscovery($this->drupalRoot);
9470
$this->extensionDiscovery->setProfileDirectories([]);
9571
$profiles = $this->extensionDiscovery->scan('profile');
96-
$profile_directories = array_map(function ($profile) {
72+
$profile_directories = array_map(function (\PHPStan\Drupal\Extension $profile) : string {
9773
return $profile->getPath();
9874
}, $profiles);
9975
$this->extensionDiscovery->setProfileDirectories($profile_directories);

src/Drupal/Extension.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Extension
3737
*
3838
* Note that SplFileInfo is a PHP resource and resources cannot be serialized.
3939
*
40-
* @var \SplFileInfo
40+
* @var ?\SplFileInfo
4141
*/
4242
protected $splFileInfo;
4343

@@ -51,12 +51,12 @@ class Extension
5151
/**
5252
* @var string
5353
*/
54-
public $subpath;
54+
public $subpath = '';
5555

5656
/**
5757
* @var string
5858
*/
59-
public $origin;
59+
public $origin = '';
6060

6161
/**
6262
* Constructs a new Extension object.
@@ -139,6 +139,8 @@ public function getExtensionPathname(): ?string
139139
if ($this->filename !== null) {
140140
return $this->getPath() . '/' . $this->filename;
141141
}
142+
143+
return null;
142144
}
143145

144146
/**

src/Drupal/ExtensionDiscovery.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ protected function filterByProfileDirectories(array $all_files)
197197
return $all_files;
198198
}
199199

200-
$all_files = array_filter($all_files, function ($file) {
200+
$all_files = array_filter($all_files, function (\PHPStan\Drupal\Extension $file) : bool {
201201
if (strpos($file->subpath, 'profiles') !== 0) {
202202
// This extension doesn't belong to a profile, ignore it.
203203
return true;
@@ -358,6 +358,13 @@ protected function scanDirectory($dir): array
358358
continue;
359359
}
360360

361+
// This test module has a function declaration that conflicts with another module. Explicitly skip it.
362+
// @see https://www.drupal.org/project/drupal/issues/3020142
363+
// @todo remove when Drupal core fixed.
364+
if ($fileinfo->getBasename('.info.yml') === 'no_transitions_css') {
365+
continue;
366+
}
367+
361368
// Determine extension type from info file.
362369
$type = false;
363370
$file = $fileinfo->openFile('r');

src/Drupal/ServiceMap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class ServiceMap
1313
*/
1414
public function __construct(array $drupalServices)
1515
{
16+
$this->services = [];
17+
1618
foreach ($drupalServices as $serviceId => $serviceDefinition) {
1719
// @todo support factories
1820
if (!isset($serviceDefinition['class'])) {

src/Rules/Classes/EnhancedRequireParentConstructCallRule.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ public function processNode(Node $node, Scope $scope): array
3131
return [];
3232
}
3333

34+
$scopeClassReflection = $scope->getClassReflection();
35+
3436
// Provides specific handling for Drupal instances where not calling the parent __construct is "okay."
35-
if ($scope->getClassReflection() === null) {
37+
if ($scopeClassReflection === null) {
3638
throw new ShouldNotHappenException();
3739
}
38-
$classReflection = $scope->getClassReflection()->getNativeReflection();
40+
$classReflection = $scopeClassReflection->getNativeReflection();
3941
if (!$classReflection->isInterface()
4042
&& !$classReflection->isAnonymous()
4143
&& $classReflection->implementsInterface('Drupal\Component\Plugin\PluginManagerInterface')

src/Rules/Drupal/GlobalDrupalDependencyInjectionRule.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public function processNode(Node $node, Scope $scope): array
2626
if (!$scope->isInClass() && !$scope->isInTrait()) {
2727
return [];
2828
}
29-
if ($scope->getClassReflection() === null) {
29+
$scopeClassReflection = $scope->getClassReflection();
30+
if ($scopeClassReflection === null) {
3031
throw new ShouldNotHappenException();
3132
}
3233

@@ -37,19 +38,27 @@ public function processNode(Node $node, Scope $scope): array
3738
'Drupal\Core\Render\Element\ElementInterface',
3839
'Drupal\Core\Render\Element\FormElementInterface',
3940
'Drupal\config_translation\FormElement\ElementInterface',
41+
// Entities don't use services for now
42+
// @see https://www.drupal.org/project/drupal/issues/2913224
43+
'Drupal\Core\Entity\EntityInterface',
4044
];
41-
$classReflection = $scope->getClassReflection()->getNativeReflection();
45+
$classReflection = $scopeClassReflection->getNativeReflection();
4246
foreach ($whitelist as $item) {
4347
if ($classReflection->implementsInterface($item)) {
4448
return [];
4549
}
4650
}
4751

48-
if ($scope->getFunctionName() === null || !($scope->getFunction() instanceof MethodReflection)) {
52+
if ($scope->getFunctionName() === null) {
53+
throw new ShouldNotHappenException();
54+
}
55+
56+
$scopeFunction = $scope->getFunction();
57+
if (!($scopeFunction instanceof MethodReflection)) {
4958
throw new ShouldNotHappenException();
5059
}
5160
// Static methods have to invoke \Drupal.
52-
if ($scope->getFunction()->isStatic()) {
61+
if ($scopeFunction->isStatic()) {
5362
return [];
5463
}
5564

src/Rules/Drupal/PluginManager/PluginManagerSetsCacheBackendRule.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ public function processNode(Node $node, Scope $scope): array
3636
return [];
3737
}
3838

39-
if ($scope->getClassReflection() === null) {
39+
$scopeClassReflection = $scope->getClassReflection();
40+
41+
if ($scopeClassReflection === null) {
4042
throw new ShouldNotHappenException();
4143
}
4244

43-
$classReflection = $scope->getClassReflection()->getNativeReflection();
45+
$classReflection = $scopeClassReflection->getNativeReflection();
4446

4547
if (!$this->isPluginManager($classReflection)) {
4648
return [];
@@ -71,6 +73,7 @@ public function processNode(Node $node, Scope $scope): array
7173
$cacheTags = $setCacheBackendArgs[2]->value;
7274
if (count($cacheTags->items) > 0) {
7375
$hasCacheTags = true;
76+
/** @var \PhpParser\Node\Expr\ArrayItem $item */
7477
foreach ($cacheTags->items as $item) {
7578
if (($item->value instanceof Node\Scalar\String_) &&
7679
strpos($item->value->value, $cacheKey->value) === false) {

0 commit comments

Comments
 (0)