Skip to content

Commit

Permalink
Merge branch 'psalm:5.x' into symfony-forms-6.3-compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
zmitic authored Jun 11, 2024
2 parents 5e3fd85 + 58e1092 commit a459d4f
Show file tree
Hide file tree
Showing 27 changed files with 274 additions and 333 deletions.
27 changes: 12 additions & 15 deletions .github/workflows/integrate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
branches:
- master
schedule:
- cron: "0 10 * * *"
- cron: "0 10 * * 6"

jobs:
static-code-analysis:
Expand All @@ -18,16 +18,16 @@ jobs:
fail-fast: false
matrix:
php-version:
- 8.0
- 8.1
- 8.2

dependencies:
- highest
- lowest

steps:
- name: "Checkout"
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
Expand All @@ -36,12 +36,12 @@ jobs:
php-version: ${{ matrix.php-version }}

- name: Install Composer dependencies
uses: ramsey/composer-install@v1
uses: ramsey/composer-install@v3
with:
dependency-versions: ${{ matrix.dependencies }}

- name: "Cache cache directory for vimeo/psalm"
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: .build/psalm
key: php-${{ matrix.php-version }}-psalm-${{ github.sha }}
Expand All @@ -64,26 +64,23 @@ jobs:
fail-fast: false
matrix:
php-version:
- 8.0
- 8.1
- 8.2

symfony-version:
- 5
- 6
- 7
- 7.0
- 7.1

exclude:
- php-version: 8.0
symfony-version: 6
- php-version: 8.0
symfony-version: 7
- php-version: 8.1
symfony-version: 7
symfony-version: 7.0
- php-version: 8.1
symfony-version: 7.1

steps:
- name: "Checkout"
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
Expand All @@ -96,7 +93,7 @@ jobs:
run: composer config extra.symfony.require ${{ matrix.symfony-version }}.*

- name: Install Composer dependencies
uses: ramsey/composer-install@v1
uses: ramsey/composer-install@v3

- name: "Run unit tests with phpunit"
run: vendor/bin/phpunit --configuration=phpunit.xml
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ vendor/bin/psalm-plugin enable psalm/plugin-symfony

| Symfony Psalm Plugin | PHP | Symfony | Psalm |
|----------------------|------------|---------|-------|
| 5.x | ^7.4, ^8.0 | 5, 6 | 5 |
| 5.x | ^8.0 | 5, 6, 7 | 5 |
| 4.x | ^7.4, ^8.0 | 4, 5, 6 | 4 |
| 3.x | ^7.1, ^8.0 | 4, 5, 6 | 4 |
| 2.x | ^7.1, ^8.0 | 4, 5 | 4 |
Expand Down Expand Up @@ -131,7 +131,7 @@ To leverage the real Twig file analyzer, you have to configure a checker for the
```xml
<fileExtensions>
<extension name=".php" />
<extension name=".twig" checker="./vendor/psalm/plugin-symfony/src/Twig/TemplateFileAnalyzer.php"/>
<extension name=".twig" checker="/vendor/psalm/plugin-symfony/src/Twig/TemplateFileAnalyzer.php"/>
</fileExtensions>
```

Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@
}
],
"require": {
"php": "^7.4 || ^8.0",
"php": "^8.1",
"ext-simplexml": "*",
"symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0",
"vimeo/psalm": "^5.1"
"vimeo/psalm": "^5.24"
},
"require-dev": {
"symfony/form": "^5.0 || ^6.0 || ^7.0",
"doctrine/annotations": "^1.8|^2",
"doctrine/orm": "^2.9",
"phpunit/phpunit": "~7.5 || ~9.5",
"symfony/cache-contracts": "^1.0 || ^2.0",
"symfony/console": "*",
"symfony/form": "^5.0 || ^6.0 || ^7.0",
"symfony/messenger": "^5.0 || ^6.0 || ^7.0",
"symfony/security-guard": "*",
"symfony/security-core": "*",
"symfony/serializer": "^5.0 || ^6.0 || ^7.0",
"symfony/validator": "*",
"twig/twig": "^2.10 || ^3.0",
Expand Down
8 changes: 4 additions & 4 deletions src/Handler/ConsoleHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
break;
case 'Symfony\Component\Console\Input\InputInterface::getargument':
$identifier = self::getNodeIdentifier($args[0]->value);
if (!$identifier) {
if (null === $identifier) {
break;
}

Expand All @@ -70,7 +70,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
break;
case 'Symfony\Component\Console\Input\InputInterface::getoption':
$identifier = self::getNodeIdentifier($args[0]->value);
if (!$identifier) {
if (null === $identifier) {
break;
}

Expand Down Expand Up @@ -126,7 +126,7 @@ private static function analyseArgument(array $args, StatementsSource $statement
$normalizedParams = self::normalizeArgumentParams($args);

$identifier = self::getNodeIdentifier($normalizedParams['name']->value);
if (!$identifier) {
if (null === $identifier) {
return;
}

Expand Down Expand Up @@ -170,7 +170,7 @@ private static function analyseOption(array $args, StatementsSource $statements_
$normalizedParams = self::normalizeOptionParams($args);

$identifier = self::getNodeIdentifier($normalizedParams['name']->value);
if (!$identifier) {
if (null === $identifier) {
return;
}

Expand Down
19 changes: 8 additions & 11 deletions src/Handler/ContainerHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ class ContainerHandler implements AfterMethodCallAnalysisInterface, AfterClassLi
'Symfony\Bundle\FrameworkBundle\Test\TestContainer',
];

/**
* @var ContainerMeta|null
*/
private static $containerMeta;
private static ?ContainerMeta $containerMeta = null;

/**
* @var array<string> collection of cower-cased class names that are present in the container
Expand Down Expand Up @@ -105,7 +102,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
if ('self' === $className) {
$className = $event->getStatementsSource()->getSource()->getFQCLN();
}
if (!$idArgument->name instanceof Identifier || !$className) {
if (!$idArgument->name instanceof Identifier || null === $className) {
return;
}

Expand All @@ -114,7 +111,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
} else {
try {
$serviceId = \constant($className.'::'.$idArgument->name->name);
} catch (\Exception $e) {
} catch (\Exception) {
return;
}
}
Expand All @@ -133,15 +130,15 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
}

$class = $service->getClass();
if ($class) {
if (null !== $class) {
$codebase->classlikes->addFullyQualifiedClassName($class);
$event->setReturnTypeCandidate(new Union([new TNamedObject($class)]));
}

if (!$service->isPublic()) {
/** @var class-string $kernelTestCaseClass */
$kernelTestCaseClass = 'Symfony\Bundle\FrameworkBundle\Test\KernelTestCase';
$isTestContainer = $context->parent
$isTestContainer = null !== $context->parent
&& ($kernelTestCaseClass === $context->parent
|| is_subclass_of($context->parent, $kernelTestCaseClass)
);
Expand All @@ -152,15 +149,15 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
);
}
}
} catch (ServiceNotFoundException $e) {
} catch (ServiceNotFoundException) {
IssueBuffer::accepts(
new ServiceNotFound($serviceId, new CodeLocation($statements_source, $firstArg->value)),
$statements_source->getSuppressedIssues()
);
}
}

public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void
{
$codebase = $event->getCodebase();
$statements_source = $event->getStatementsSource();
Expand Down Expand Up @@ -221,7 +218,7 @@ function ($c) use ($methodName) {

private static function followsParameterNamingConvention(string $name): bool
{
if (0 === strpos($name, 'env(')) {
if (str_starts_with($name, 'env(')) {
return true;
}

Expand Down
3 changes: 1 addition & 2 deletions src/Handler/HeaderBagHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use PhpParser\Node\Scalar\String_;
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
use Psalm\Type;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TNull;
Expand All @@ -24,7 +23,7 @@ public static function getClassLikeNames(): array
];
}

public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Type\Union
public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union
{
$fq_classlike_name = $event->getFqClasslikeName();
$method_name_lowercase = $event->getMethodNameLowercase();
Expand Down
26 changes: 7 additions & 19 deletions src/Handler/ParameterBagHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,18 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve
}

$argument = $expr->args[0]->value->value;

try {
$parameter = self::$containerMeta->getParameter($argument);
} catch (ParameterNotFoundException $e) {
$parameterTypes = self::$containerMeta->guessParameterType($argument);
} catch (ParameterNotFoundException) {
// maybe emit ParameterNotFound issue
return;
}

// @todo find a better way to calculate return type
switch (gettype($parameter)) {
case 'string':
$event->setReturnTypeCandidate(new Union([Atomic::create('string')]));
break;
case 'boolean':
$event->setReturnTypeCandidate(new Union([Atomic::create('bool')]));
break;
case 'integer':
$event->setReturnTypeCandidate(new Union([Atomic::create('int')]));
break;
case 'double':
$event->setReturnTypeCandidate(new Union([Atomic::create('float')]));
break;
case 'array':
$event->setReturnTypeCandidate(new Union([Atomic::create('array')]));
break;
if (null === $parameterTypes || [] === $parameterTypes) {
return;
}

$event->setReturnTypeCandidate(new Union(array_map(fn (string $parameterType): Atomic => Atomic::create($parameterType), $parameterTypes)));
}
}
40 changes: 20 additions & 20 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/
class Plugin implements PluginEntryPointInterface
{
public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config = null): void
public function __invoke(RegistrationInterface $registration, ?\SimpleXMLElement $config = null): void
{
require_once __DIR__.'/Handler/HeaderBagHandler.php';
require_once __DIR__.'/Handler/ContainerHandler.php';
Expand All @@ -39,13 +39,13 @@ public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config =
require_once __DIR__.'/Handler/DoctrineQueryBuilderHandler.php';
require_once __DIR__.'/Provider/FormGetErrorsReturnTypeProvider.php';

$api->registerHooksFromClass(HeaderBagHandler::class);
$api->registerHooksFromClass(ConsoleHandler::class);
$api->registerHooksFromClass(ContainerDependencyHandler::class);
$api->registerHooksFromClass(RequiredSetterHandler::class);
$registration->registerHooksFromClass(HeaderBagHandler::class);
$registration->registerHooksFromClass(ConsoleHandler::class);
$registration->registerHooksFromClass(ContainerDependencyHandler::class);
$registration->registerHooksFromClass(RequiredSetterHandler::class);

if (class_exists(\Doctrine\ORM\QueryBuilder::class)) {
$api->registerHooksFromClass(DoctrineQueryBuilderHandler::class);
$registration->registerHooksFromClass(DoctrineQueryBuilderHandler::class);
}

if (class_exists(AnnotationRegistry::class)) {
Expand All @@ -54,10 +54,10 @@ public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config =
/** @psalm-suppress DeprecatedMethod */
AnnotationRegistry::registerLoader('class_exists');
}
$api->registerHooksFromClass(DoctrineRepositoryHandler::class);
$registration->registerHooksFromClass(DoctrineRepositoryHandler::class);

require_once __DIR__.'/Handler/AnnotationHandler.php';
$api->registerHooksFromClass(AnnotationHandler::class);
$registration->registerHooksFromClass(AnnotationHandler::class);
}

if (isset($config->containerXml)) {
Expand All @@ -83,14 +83,14 @@ public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config =

require_once __DIR__.'/Handler/ParameterBagHandler.php';
ParameterBagHandler::init($containerMeta);
$api->registerHooksFromClass(ParameterBagHandler::class);
$registration->registerHooksFromClass(ParameterBagHandler::class);
}

$api->registerHooksFromClass(ContainerHandler::class);
$registration->registerHooksFromClass(ContainerHandler::class);

$this->addStubs($api, __DIR__.'/Stubs/common');
$this->addStubs($api, __DIR__.'/Stubs/'.Kernel::MAJOR_VERSION);
$this->addStubs($api, __DIR__.'/Stubs/php');
$this->addStubs($registration, __DIR__.'/Stubs/common');
$this->addStubs($registration, __DIR__.'/Stubs/'.Kernel::MAJOR_VERSION);
$this->addStubs($registration, __DIR__.'/Stubs/php');

if (isset($config->twigCachePath)) {
$twig_cache_path = getcwd().DIRECTORY_SEPARATOR.ltrim((string) $config->twigCachePath, DIRECTORY_SEPARATOR);
Expand All @@ -99,15 +99,15 @@ public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config =
}

require_once __DIR__.'/Twig/CachedTemplatesTainter.php';
$api->registerHooksFromClass(CachedTemplatesTainter::class);
$registration->registerHooksFromClass(CachedTemplatesTainter::class);

require_once __DIR__.'/Twig/CachedTemplatesMapping.php';
$api->registerHooksFromClass(CachedTemplatesMapping::class);
$registration->registerHooksFromClass(CachedTemplatesMapping::class);
CachedTemplatesMapping::setCachePath($twig_cache_path);
}

require_once __DIR__.'/Twig/AnalyzedTemplatesTainter.php';
$api->registerHooksFromClass(AnalyzedTemplatesTainter::class);
$registration->registerHooksFromClass(AnalyzedTemplatesTainter::class);

if (isset($config->twigRootPath)) {
$twig_root_path = trim((string) $config->twigRootPath, DIRECTORY_SEPARATOR);
Expand All @@ -119,20 +119,20 @@ public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config =
TemplateFileAnalyzer::setTemplateRootPath($twig_root_path);
}

$api->registerHooksFromClass(FormGetErrorsReturnTypeProvider::class);
$registration->registerHooksFromClass(FormGetErrorsReturnTypeProvider::class);
}

private function addStubs(RegistrationInterface $api, string $path): void
private function addStubs(RegistrationInterface $registration, string $path): void
{
if (!is_dir($path)) {
// e.g. looking for stubs for version 3, but there aren't any at time of writing, so don't try and load them.
// e.g., looking for stubs for version 3, but there aren't any at time of writing, so don't try and load them.
return;
}

$a = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
foreach ($a as $file) {
if (!$file->isDir()) {
$api->addStubFile($file->getPathname());
$registration->addStubFile($file->getPathname());
}
}
}
Expand Down
Loading

0 comments on commit a459d4f

Please sign in to comment.