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());
+ }
}