Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
*.stub linguist-language=PHP

tests/PHPStan/Command/ErrorFormatter/data/WindowsNewlines.php eol=crlf
tests/PHPStan/Command/Neon2Attributes/data/** text eol=lf

.github/workflows/*.lock.yml linguist-generated=true merge=ours
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ lint:
--exclude tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php \
--exclude tests/PHPStan/Levels/data/namedArguments.php \
--exclude tests/PHPStan/DependencyInjection/AttributeServices/data \
--exclude tests/PHPStan/Command/Neon2Attributes/data \
--exclude tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/symbol-finder/class-name-before-global-constant.php \
--exclude tests/PHPStan/Rules/Keywords/data/continue-break.php \
--exclude tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php \
Expand Down
2 changes: 2 additions & 0 deletions bin/phpstan
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use PHPStan\Command\ClearResultCacheCommand;
use PHPStan\Command\DiagnoseCommand;
use PHPStan\Command\DumpParametersCommand;
use PHPStan\Command\FixerWorkerCommand;
use PHPStan\Command\Neon2AttributesCommand;
use PHPStan\Command\ResultCacheInfoCommand;
use PHPStan\Command\WorkerCommand;
use PHPStan\Internal\ComposerHelper;
Expand Down Expand Up @@ -144,5 +145,6 @@ use Symfony\Component\Console\Helper\ProgressBar;
$application->add(new FixerWorkerCommand($reversedComposerAutoloaderProjectPaths));
$application->add(new DumpParametersCommand($reversedComposerAutoloaderProjectPaths));
$application->add(new DiagnoseCommand($reversedComposerAutoloaderProjectPaths));
$application->add(new Neon2AttributesCommand($reversedComposerAutoloaderProjectPaths));
$application->run();
})();
36 changes: 36 additions & 0 deletions build/baseline-pre-8.0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ parameters:
count: 1
path: ../src/DependencyInjection/AttributeServices/AttributeServicesDiscoverer.php

-
rawMessage: 'Call to an undefined method ReflectionClass<object>::getAttributes().'
identifier: method.notFound
count: 2
path: ../src/Command/Neon2Attributes/Neon2AttributesMigrator.php

-
rawMessage: 'Call to an undefined method ReflectionParameter::getAttributes().'
identifier: method.notFound
count: 1
path: ../src/Command/Neon2Attributes/Neon2AttributesMigrator.php

-
rawMessage: 'Call to method is() of internal class Symfony\Polyfill\Php80\PhpToken from outside its root namespace Symfony.'
identifier: method.internalClass
count: 5
path: ../src/DependencyInjection/AttributeServices/AttributeServicesDiscoverer.php

-
rawMessage: 'Access to property $text of internal class Symfony\Polyfill\Php80\PhpToken from outside its root namespace Symfony.'
identifier: property.internalClass
count: 2
path: ../src/DependencyInjection/AttributeServices/AttributeServicesDiscoverer.php

-
rawMessage: 'Call to method getTokenName() of internal class Symfony\Polyfill\Php80\PhpToken from outside its root namespace Symfony.'
identifier: method.internalClass
count: 1
path: ../src/DependencyInjection/AttributeServices/AttributeServicesDiscoverer.php

-
rawMessage: 'Call to method isIgnorable() of internal class Symfony\Polyfill\Php80\PhpToken from outside its root namespace Symfony.'
identifier: method.internalClass
count: 1
path: ../src/DependencyInjection/AttributeServices/AttributeServicesDiscoverer.php

-
rawMessage: 'Class AttributeServicesFixtures\DiscoveredConditionalRuleNotMet not found.'
identifier: class.notFound
Expand Down
1 change: 1 addition & 0 deletions build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ parameters:
paths:
- ../src/Neon/*
- ../src/Analyser/Ignore/IgnoreErrorsConfigFixer.php
- ../src/Command/Neon2Attributes/Neon2AttributesMigrator.php
- ../tests/PHPStan/Neon/*
-
rawMessage: 'Property PHPStan\Command\CommandHelper::$reservedMemory is never read' # intentional
Expand Down
4 changes: 4 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ parameters:
autowiredAttributeServices:
level: null

# vendor interfaces cannot carry the #[PHPStan\DependencyInjection\ExtensionInterface] attribute
extensionInterfaces:
PhpParser\NodeVisitor: phpstan.parser.richParserNodeVisitor

# autowired services are registered with the help of attributes
# like #[PHPStan\DependencyInjection\AutowiredService] or #[PHPStan\DependencyInjection\GenerateFactory],
# optionally wrapped in #[PHPStan\DependencyInjection\Conditional]
Expand Down
20 changes: 19 additions & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,18 @@ parameters:
count: 1
path: src/Command/ErrorsConsoleStyle.php

-
rawMessage: 'Call to method __construct() of internal class PhpParser\Internal\TokenStream from outside its root namespace PhpParser.'
identifier: method.internalClass
count: 1
path: src/Command/Neon2Attributes/PhpClassAttributesWriter.php

-
rawMessage: Instantiation of internal class PhpParser\Internal\TokenStream.
identifier: new.internalClass
count: 1
path: src/Command/Neon2Attributes/PhpClassAttributesWriter.php

-
rawMessage: 'Call to static method escape() of internal class Nette\DI\Helpers from outside its root namespace Nette.'
identifier: staticMethod.internalClass
Expand All @@ -244,7 +256,13 @@ parameters:
-
rawMessage: 'Call to static method expand() of internal class Nette\DI\Helpers from outside its root namespace Nette.'
identifier: staticMethod.internalClass
count: 1
count: 2
path: src/DependencyInjection/AttributeServices/AttributeServicesRegistrar.php

-
rawMessage: 'Call to static method filterArguments() of internal class Nette\DI\Helpers from outside its root namespace Nette.'
identifier: staticMethod.internalClass
count: 2
path: src/DependencyInjection/AttributeServices/AttributeServicesRegistrar.php

-
Expand Down
38 changes: 23 additions & 15 deletions src/Command/CommandHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,21 +183,9 @@ public static function begin(
})($autoloadFile);
}
if ($projectConfigFile === null) {
$discoverableConfigNames = [
'.phpstan.neon',
'phpstan.neon',
'.phpstan.neon.dist',
'phpstan.neon.dist',
'.phpstan.dist.neon',
'phpstan.dist.neon',
];
foreach ($discoverableConfigNames as $discoverableConfigName) {
$discoverableConfigFile = $currentWorkingDirectory . DIRECTORY_SEPARATOR . $discoverableConfigName;
if (is_file($discoverableConfigFile)) {
$projectConfigFile = $discoverableConfigFile;
$errorOutput->writeLineFormatted(sprintf('Note: Using configuration file %s.', $projectConfigFile));
break;
}
$projectConfigFile = self::findDiscoverableConfigFile($currentWorkingDirectory);
if ($projectConfigFile !== null) {
$errorOutput->writeLineFormatted(sprintf('Note: Using configuration file %s.', $projectConfigFile));
}
} else {
$projectConfigFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($projectConfigFile));
Expand Down Expand Up @@ -664,4 +652,24 @@ public static function executeBootstrapFile(
}
}

public static function findDiscoverableConfigFile(string $currentWorkingDirectory): ?string
{
$discoverableConfigNames = [
'.phpstan.neon',
'phpstan.neon',
'.phpstan.neon.dist',
'phpstan.neon.dist',
'.phpstan.dist.neon',
'phpstan.dist.neon',
];
foreach ($discoverableConfigNames as $discoverableConfigName) {
$discoverableConfigFile = $currentWorkingDirectory . DIRECTORY_SEPARATOR . $discoverableConfigName;
if (is_file($discoverableConfigFile)) {
return $discoverableConfigFile;
}
}

return null;
}

}
22 changes: 22 additions & 0 deletions src/Command/Neon2Attributes/AttributeSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);

namespace PHPStan\Command\Neon2Attributes;

/**
* An attribute from the PHPStan\DependencyInjection namespace to write into a PHP file.
*/
final class AttributeSpec
{

/**
* @param non-empty-string $name short name of the attribute class
* @param array<non-empty-string, mixed> $arguments named arguments; the values are scalars, lists and maps of them, ClassReference and AttributeSpec for `new`
*/
public function __construct(
public string $name,
public array $arguments = [],
)
{
}

}
31 changes: 31 additions & 0 deletions src/Command/Neon2Attributes/AttributesPrinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);

namespace PHPStan\Command\Neon2Attributes;

use Override;
use PhpParser\Node;
use PhpParser\PrettyPrinter\Standard;

/**
* Prints attributes of parameters in a one-line parameter list on the same line,
* other attributes go on their own line.
*/
final class AttributesPrinter extends Standard
{

public const INLINE_ATTRIBUTES = 'neon2attributesInline';

#[Override]
protected function pParam(Node\Param $node): string
{
if ($node->getAttribute(self::INLINE_ATTRIBUTES) !== true) {
return parent::pParam($node);
}

$param = clone $node;
$param->attrGroups = [];

return $this->pAttrGroups($node->attrGroups, true) . parent::pParam($param);
}

}
22 changes: 22 additions & 0 deletions src/Command/Neon2Attributes/ClassAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);

namespace PHPStan\Command\Neon2Attributes;

use ReflectionClass;

final class ClassAttributes
{

/**
* @param ReflectionClass<object> $reflection
* @param array<string, AttributeSpec> $parameterAttributes by constructor parameter name
*/
public function __construct(
public ReflectionClass $reflection,
public AttributeSpec $attribute,
public array $parameterAttributes,
)
{
}

}
15 changes: 15 additions & 0 deletions src/Command/Neon2Attributes/ClassReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace PHPStan\Command\Neon2Attributes;

/**
* Written as `ClassName::class`.
*/
final class ClassReference
{

public function __construct(public string $className)
{
}

}
70 changes: 70 additions & 0 deletions src/Command/Neon2Attributes/ComposerConfigIncludes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php declare(strict_types = 1);

namespace PHPStan\Command\Neon2Attributes;

use Nette\FileNotFoundException;
use Nette\InvalidStateException;
use Nette\Utils\Json;
use Nette\Utils\JsonException;
use PHPStan\DependencyInjection\LoaderFactory;
use PHPStan\File\CouldNotReadFileException;
use PHPStan\File\FileHelper;
use PHPStan\File\FileReader;
use function array_map;
use function dirname;
use function in_array;
use function is_array;
use function is_file;
use function is_string;

/**
* Finds the config files of an extension that phpstan/extension-installer would include
* for the projects that install it, listed in `extra.phpstan.includes` of composer.json.
*/
final class ComposerConfigIncludes
{

/**
* @return list<string> the files not included by the project config file already
*/
public static function findMissing(string $directory, ?string $projectConfigFile): array
{
$fileHelper = new FileHelper($directory);
try {
$composer = Json::decode(FileReader::read($directory . '/composer.json'), Json::FORCE_ARRAY);
} catch (CouldNotReadFileException | JsonException) {
return [];
}
$includes = $composer['extra']['phpstan']['includes'] ?? null;
if (!is_array($includes)) {
return [];
}

$included = [];
if ($projectConfigFile !== null) {
$loader = (new LoaderFactory($fileHelper, dirname(__DIR__, 3), $directory, null, []))->createLoader();
try {
$loader->load($projectConfigFile, null);
} catch (InvalidStateException | FileNotFoundException) {
// reported when the container is created
return [];
}
$included = array_map(static fn (string $file): string => $fileHelper->normalizePath($file), $loader->getDependencies());
}

$missing = [];
foreach ($includes as $include) {
if (!is_string($include)) {
continue;
}
$file = $fileHelper->normalizePath($fileHelper->absolutizePath($include));
if (!is_file($file) || in_array($file, $included, true)) {
continue;
}
$missing[] = $file;
}

return $missing;
}

}
22 changes: 22 additions & 0 deletions src/Command/Neon2Attributes/ConvertedService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);

namespace PHPStan\Command\Neon2Attributes;

/**
* A service entry of a NEON config file replaced by attributes on its class.
*/
final class ConvertedService
{

/**
* @param list<string> $attributes short names of the added attributes
*/
public function __construct(
public string $className,
public array $attributes,
public string $configFile,
)
{
}

}
Loading
Loading