diff --git a/UPGRADING.md b/UPGRADING.md index 55cb4f65119..2bf88c584a4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -44,6 +44,14 @@ - [BC] `Psalm\CodeLocation\Raw`, `Psalm\CodeLocation\ParseErrorLocation`, `Psalm\CodeLocation\DocblockTypeLocation`, `Psalm\Report\CountReport`, `Psalm\Type\Atomic\TNonEmptyArray` are now all final. +- [BC] `Psalm\Config` is now final. + +- [BC] The return type of `Psalm\Plugin\ArgTypeInferer::infer` changed from `Union|false` to `Union|null` + +- [BC] The `extra_types` property and `setIntersectionTypes` method of `Psalm\Type\Atomic\TTypeAlias` were removed. + +- [BC] Methods `convertSeverity` and `calculateFingerprint` of `Psalm\Report\CodeClimateReport` were removed. + # Upgrading from Psalm 4 to Psalm 5 ## Changed diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index d86d787c73b..8759c0ddbcd 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -69,7 +69,6 @@ use function flock; use function fopen; use function function_exists; -use function get_class; use function get_defined_constants; use function get_defined_functions; use function getcwd; @@ -98,7 +97,9 @@ use function scandir; use function sha1; use function simplexml_import_dom; +use function str_contains; use function str_replace; +use function str_starts_with; use function strlen; use function strpos; use function strrpos; @@ -127,13 +128,13 @@ * @psalm-suppress PropertyNotSetInConstructor * @psalm-consistent-constructor */ -class Config +final class Config { private const DEFAULT_FILE_NAME = 'psalm.xml'; - public const CONFIG_NAMESPACE = 'https://getpsalm.org/schema/config'; - public const REPORT_INFO = 'info'; - public const REPORT_ERROR = 'error'; - public const REPORT_SUPPRESS = 'suppress'; + final public const CONFIG_NAMESPACE = 'https://getpsalm.org/schema/config'; + final public const REPORT_INFO = 'info'; + final public const REPORT_ERROR = 'error'; + final public const REPORT_SUPPRESS = 'suppress'; /** * @var array @@ -172,7 +173,7 @@ class Config * * @var array */ - protected array $universal_object_crates; + private array $universal_object_crates; /** * @var static|null @@ -222,7 +223,7 @@ class Config protected ?ProjectFileFilter $project_files = null; - protected ?ProjectFileFilter $extra_files = null; + private ?ProjectFileFilter $extra_files = null; /** * The base directory of this config file @@ -426,7 +427,7 @@ class Config private ?IncludeCollector $include_collector = null; - protected ?TaintAnalysisFileFilter $taint_analysis_ignored_files = null; + private ?TaintAnalysisFileFilter $taint_analysis_ignored_files = null; /** * @var bool whether to emit a backtrace of emitted issues to stderr @@ -874,7 +875,6 @@ private static function processConfigDeprecations( /** * @param non-empty-string $file_contents * @psalm-suppress MixedAssignment - * @psalm-suppress MixedArgument * @psalm-suppress MixedPropertyFetch * @throws ConfigException */ @@ -963,15 +963,15 @@ private static function fromXmlAndPaths( if (file_exists($composer_json_path)) { $composer_json_contents = file_get_contents($composer_json_path); assert($composer_json_contents !== false); - $composer_json = json_decode($composer_json_contents, true); + $composer_json = json_decode($composer_json_contents, true, 512, JSON_THROW_ON_ERROR); if (!is_array($composer_json)) { throw new UnexpectedValueException('Invalid composer.json at ' . $composer_json_path); } } $required_extensions = []; foreach (($composer_json["require"] ?? []) as $required => $_) { - if (strpos($required, "ext-") === 0) { - $required_extensions[strtolower(substr($required, 4))] = true; + if (str_starts_with((string) $required, "ext-")) { + $required_extensions[strtolower(substr((string) $required, 4))] = true; } } foreach ($required_extensions as $required_ext => $_) { @@ -1649,7 +1649,7 @@ public function reportIssueInFile(string $issue_type, string $file_path): bool try { $file_storage = $codebase->file_storage_provider->get($file_path); $dependent_files += $file_storage->required_by_file_paths; - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // do nothing } } @@ -1700,7 +1700,7 @@ public function trackTaintsInPath(string $file_path): bool public function getReportingLevelForIssue(CodeIssue $e): string { - $fqcn_parts = explode('\\', get_class($e)); + $fqcn_parts = explode('\\', $e::class); $issue_type = array_pop($fqcn_parts); $reporting_level = null; @@ -1765,17 +1765,17 @@ public static function getParentIssueType(string $issue_type): ?string return null; } - if (strpos($issue_type, 'Possibly') === 0) { + if (str_starts_with($issue_type, 'Possibly')) { $stripped_issue_type = (string) preg_replace('/^Possibly(False|Null)?/', '', $issue_type, 1); - if (strpos($stripped_issue_type, 'Invalid') === false && strpos($stripped_issue_type, 'Un') !== 0) { + if (!str_contains($stripped_issue_type, 'Invalid') && !str_starts_with($stripped_issue_type, 'Un')) { $stripped_issue_type = 'Invalid' . $stripped_issue_type; } return $stripped_issue_type; } - if (strpos($issue_type, 'Tainted') === 0) { + if (str_starts_with($issue_type, 'Tainted')) { return 'TaintedInput'; } @@ -2298,7 +2298,7 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P $codebase->classlikes->forgetMissingClassLikes(); $this->include_collector->runAndCollect( - [$this, 'requireAutoloader'], + $this->requireAutoloader(...), ); } @@ -2324,7 +2324,8 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P } } - public function getComposerFilePathForClassLike(string $fq_classlike_name): string|false + /** @return string|false */ + public function getComposerFilePathForClassLike(string $fq_classlike_name): string|bool { if (!$this->composer_class_loader) { return false; @@ -2502,7 +2503,7 @@ public function getPHPVersionFromComposerJson(): ?string $composer_json_contents = file_get_contents($composer_json_path); assert($composer_json_contents !== false); $composer_json = json_decode($composer_json_contents, true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { + } catch (JsonException) { $composer_json = null; } diff --git a/src/Psalm/Plugin/ArgTypeInferer.php b/src/Psalm/Plugin/ArgTypeInferer.php index 0347eead24a..109769c8024 100644 --- a/src/Psalm/Plugin/ArgTypeInferer.php +++ b/src/Psalm/Plugin/ArgTypeInferer.php @@ -13,19 +13,16 @@ final class ArgTypeInferer { - private Context $context; - private StatementsAnalyzer $statements_analyzer; - /** * @internal */ - public function __construct(Context $context, StatementsAnalyzer $statements_analyzer) - { - $this->context = $context; - $this->statements_analyzer = $statements_analyzer; + public function __construct( + private readonly Context $context, + private readonly StatementsAnalyzer $statements_analyzer, + ) { } - public function infer(PhpParser\Node\Arg $arg): false|Union + public function infer(PhpParser\Node\Arg $arg): null|Union { $already_inferred_type = $this->statements_analyzer->node_data->getType($arg->value); @@ -34,7 +31,7 @@ public function infer(PhpParser\Node\Arg $arg): false|Union } if (ExpressionAnalyzer::analyze($this->statements_analyzer, $arg->value, $this->context) === false) { - return false; + return null; } return $this->statements_analyzer->node_data->getType($arg->value) ?? Type::getMixed(); diff --git a/src/Psalm/Report/CodeClimateReport.php b/src/Psalm/Report/CodeClimateReport.php index fb0cbf28689..61027d6371b 100644 --- a/src/Psalm/Report/CodeClimateReport.php +++ b/src/Psalm/Report/CodeClimateReport.php @@ -28,7 +28,7 @@ public function create(): string $options = $this->pretty ? Json::PRETTY : Json::DEFAULT; $issues_data = array_map( - [$this, 'mapToNewStructure'], + $this->mapToNewStructure(...), $this->issues_data, ); @@ -39,7 +39,7 @@ public function create(): string * convert our own severity to CodeClimate format * Values can be : info, minor, major, critical, or blocker */ - protected function convertSeverity(string $input): string + private function convertSeverity(string $input): string { if (Config::REPORT_INFO === $input) { return 'info'; @@ -58,7 +58,7 @@ protected function convertSeverity(string $input): string /** * calculate a unique fingerprint for a given issue */ - protected function calculateFingerprint(IssueData $issue): string + private function calculateFingerprint(IssueData $issue): string { return md5($issue->type.$issue->message.$issue->file_name.$issue->from.$issue->to); } diff --git a/src/Psalm/Type/Atomic/TTypeAlias.php b/src/Psalm/Type/Atomic/TTypeAlias.php index 52a2fe9037a..ebda8e4118b 100644 --- a/src/Psalm/Type/Atomic/TTypeAlias.php +++ b/src/Psalm/Type/Atomic/TTypeAlias.php @@ -6,55 +6,17 @@ use Psalm\Type\Atomic; -use function array_map; -use function implode; - /** * @psalm-immutable */ final class TTypeAlias extends Atomic { - /** - * @var array|null - * @deprecated type aliases are resolved within {@see TypeParser::resolveTypeAliases()} and therefore the - * referencing type(s) are part of other intersection types. The intersection types are not set anymore - * and with v6 this property along with its related methods will get removed. - */ - public ?array $extra_types = null; - - public string $declaring_fq_classlike_name; - - public string $alias_name; - - /** - * @param array|null $extra_types - */ - public function __construct(string $declaring_fq_classlike_name, string $alias_name, ?array $extra_types = null) - { - $this->declaring_fq_classlike_name = $declaring_fq_classlike_name; - $this->alias_name = $alias_name; - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - $this->extra_types = $extra_types; + public function __construct( + public string $declaring_fq_classlike_name, + public string $alias_name, + ) { parent::__construct(true); } - /** - * @param array|null $extra_types - * @deprecated type aliases are resolved within {@see TypeParser::resolveTypeAliases()} and therefore the - * referencing type(s) are part of other intersection types. This method will get removed with v6. - * @psalm-suppress PossiblyUnusedMethod For backwards compatibility, we have to keep this here. - */ - public function setIntersectionTypes(?array $extra_types): self - { - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - if ($extra_types === $this->extra_types) { - return $this; - } - return new self( - $this->declaring_fq_classlike_name, - $this->alias_name, - $extra_types, - ); - } public function getKey(bool $include_extra = true): string { @@ -63,17 +25,6 @@ public function getKey(bool $include_extra = true): string public function getId(bool $exact = true, bool $nested = false): string { - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - if ($this->extra_types) { - return $this->getKey() . '&' . implode( - '&', - array_map( - static fn(Atomic $type): string => $type->getId($exact, true), - $this->extra_types, - ), - ); - } - return $this->getKey(); } diff --git a/tests/EnumTest.php b/tests/EnumTest.php index 4015c3f7735..ebc67a05e1e 100644 --- a/tests/EnumTest.php +++ b/tests/EnumTest.php @@ -632,7 +632,7 @@ function noop(string $s): string $foo = FooEnum::Foo->value; noop($foo); noop(FooEnum::Foo->value); - PHP, + PHP, 'assertions' => [], 'ignored_issues' => [], 'php_version' => '8.1', diff --git a/tests/PsalmPluginTest.php b/tests/PsalmPluginTest.php index 64311a30122..5b97dc23136 100644 --- a/tests/PsalmPluginTest.php +++ b/tests/PsalmPluginTest.php @@ -25,9 +25,9 @@ class PsalmPluginTest extends TestCase { use MockeryPHPUnitIntegration; - private PluginList&MockInterface $plugin_list; + private MockInterface $plugin_list; - private PluginListFactory&MockInterface $plugin_list_factory; + private MockInterface $plugin_list_factory; private Application $app; diff --git a/tests/ReportOutputTest.php b/tests/ReportOutputTest.php index bee44b19d37..c17370a8106 100644 --- a/tests/ReportOutputTest.php +++ b/tests/ReportOutputTest.php @@ -712,6 +712,7 @@ public function testJsonReport(): void $issue_data = [ [ + 'link' => 'https://psalm.dev/024', 'severity' => 'error', 'line_from' => 3, 'line_to' => 3, @@ -727,13 +728,13 @@ public function testJsonReport(): void 'snippet_to' => 83, 'column_from' => 10, 'column_to' => 26, - 'error_level' => -1, 'shortcode' => 24, - 'link' => 'https://psalm.dev/024', + 'error_level' => -1, 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/138', 'severity' => 'error', 'line_from' => 3, 'line_to' => 3, @@ -749,13 +750,13 @@ public function testJsonReport(): void 'snippet_to' => 83, 'column_from' => 10, 'column_to' => 26, - 'error_level' => 1, 'shortcode' => 138, - 'link' => 'https://psalm.dev/138', + 'error_level' => 1, 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/047', 'severity' => 'error', 'line_from' => 2, 'line_to' => 2, @@ -771,13 +772,13 @@ public function testJsonReport(): void 'snippet_to' => 56, 'column_from' => 42, 'column_to' => 49, - 'error_level' => 1, 'shortcode' => 47, - 'link' => 'https://psalm.dev/047', + 'error_level' => 1, 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/020', 'severity' => 'error', 'line_from' => 8, 'line_to' => 8, @@ -793,13 +794,13 @@ public function testJsonReport(): void 'snippet_to' => 172, 'column_from' => 6, 'column_to' => 15, - 'error_level' => -1, 'shortcode' => 20, - 'link' => 'https://psalm.dev/020', + 'error_level' => -1, 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/126', 'severity' => 'info', 'line_from' => 17, 'line_to' => 17, @@ -815,9 +816,8 @@ public function testJsonReport(): void 'snippet_to' => 277, 'column_from' => 6, 'column_to' => 8, - 'error_level' => 3, 'shortcode' => 126, - 'link' => 'https://psalm.dev/126', + 'error_level' => 3, 'taint_trace' => null, 'other_references' => null, ], diff --git a/tests/TestConfig.php b/tests/TestConfig.php index 572deabbce6..87433cc67c6 100644 --- a/tests/TestConfig.php +++ b/tests/TestConfig.php @@ -61,7 +61,8 @@ protected function getContents(): string '; } - public function getComposerFilePathForClassLike(string $fq_classlike_name): string|false + /** @return false */ + public function getComposerFilePathForClassLike(string $fq_classlike_name): bool { return false; }