diff --git a/config.xsd b/config.xsd index 9f8676ce823..c119abdda45 100644 --- a/config.xsd +++ b/config.xsd @@ -95,6 +95,7 @@ + diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index 6a84f20742e..21a253e0be0 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -305,6 +305,16 @@ This defaults to `false`. When `true`, Psalm will treat all classes as if they had sealed methods, meaning that if you implement the magic method `__call`, you also have to add `@method` for each magic method. Defaults to false. +#### sealAllProperties + +```xml + +``` + +When `true`, Psalm will treat all classes as if they had sealed properties, meaning that Psalm will disallow getting and setting any properties not contained in a list of `@property` (or `@property-read`/`@property-write`) annotations and not explicitly defined as a `property`. Defaults to false. + #### runTaintAnalysis ```xml diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index db8f8f06df1..a8310c21120 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -346,6 +346,11 @@ class Config */ public $seal_all_methods = false; + /** + * @var bool + */ + public $seal_all_properties = false; + /** * @var bool */ @@ -907,6 +912,7 @@ private static function fromXmlAndPaths( 'reportMixedIssues' => 'show_mixed_issues', 'skipChecksOnUnresolvableIncludes' => 'skip_checks_on_unresolvable_includes', 'sealAllMethods' => 'seal_all_methods', + 'sealAllProperties' => 'seal_all_properties', 'runTaintAnalysis' => 'run_taint_analysis', 'usePhpStormMetaPath' => 'use_phpstorm_meta_path', 'allowInternalNamedArgumentsCalls' => 'allow_internal_named_arg_calls', diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php index 1fda720d0c2..26faf4e593e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php @@ -548,7 +548,7 @@ private static function getMagicGetterOrSetterProperty( case '__set': // If `@psalm-seal-properties` is set, the property must be defined with // a `@property` annotation - if ($class_storage->sealed_properties + if (($class_storage->sealed_properties || $codebase->config->seal_all_properties) && !isset($class_storage->pseudo_property_set_types['$' . $prop_name]) && IssueBuffer::accepts( new UndefinedThisPropertyAssignment( @@ -647,7 +647,7 @@ private static function getMagicGetterOrSetterProperty( case '__get': // If `@psalm-seal-properties` is set, the property must be defined with // a `@property` annotation - if ($class_storage->sealed_properties + if (($class_storage->sealed_properties || $codebase->config->seal_all_properties) && !isset($class_storage->pseudo_property_get_types['$' . $prop_name]) && IssueBuffer::accepts( new UndefinedThisPropertyFetch( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php index ee9f642c186..b0961eec718 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php @@ -676,7 +676,9 @@ private static function propertyFetchCanBeAnalyzed( * If we have an explicit list of all allowed magic properties on the class, and we're * not in that list, fall through */ - if (!$class_storage->sealed_properties && !$override_property_visibility) { + if (!($class_storage->sealed_properties || $codebase->config->seal_all_properties) + && !$override_property_visibility + ) { return false; } diff --git a/tests/MagicPropertyTest.php b/tests/MagicPropertyTest.php index 62d0dd9773b..f142bb1bd65 100644 --- a/tests/MagicPropertyTest.php +++ b/tests/MagicPropertyTest.php @@ -4,6 +4,7 @@ use Psalm\Config; use Psalm\Context; +use Psalm\Exception\CodeException; use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; @@ -1159,4 +1160,28 @@ class A { ], ]; } + + public function testSealAllMethodsWithoutFoo(): void + { + Config::getInstance()->seal_all_properties = true; + + $this->addFile( + 'somefile.php', + 'foo; + ' + ); + + $error_message = 'UndefinedMagicPropertyFetch'; + $this->expectException(CodeException::class); + $this->expectExceptionMessage($error_message); + $this->analyzeFile('somefile.php', new Context()); + } }