remember_property_assignments_after_call = false; $stmts = self::$parser->parse('x === null) { $this->x = 0; } $this->modifyX(); return $this->x; } private function modifyX(): void { $this->x = null; } } '); $file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts); $file_checker->visitAndAnalyzeMethods(); } /** * @return array */ public function providerFileCheckerValidCodeParse() { return [ 'newVarInIf' => [ 'foo = []; } if (!is_array($this->foo)) { // do something } } }', ], 'propertyWithoutTypeSuppressingIssue' => [ 'foo;', 'assertions' => [], 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'propertyWithoutTypeSuppressingIssueAndAssertingNull' => [ 'foo === null && rand(0,1); echo $this->foo->baz; } }', 'assertions' => [], 'error_levels' => [ 'UndefinedThisPropertyFetch', 'MixedAssignment', 'MixedMethodCall', 'MixedPropertyFetch', ], ], 'sharedPropertyInIf' => [ 'foo; }', 'assertions' => [ '$b' => 'null|string|int', ], ], 'sharedPropertyInElseIf' => [ 'foo; }', 'assertions' => [ '$b' => 'null|string|int', ], ], 'nullablePropertyCheck' => [ 'bb) && $b->bb->aa === "aa") { echo $b->bb->aa; }', ], 'nullablePropertyAfterGuard' => [ 'aa) { $a->aa = "hello"; } echo substr($a->aa, 1);', ], 'nullableStaticPropertyWithIfCheck' => [ ' [ 'name . " - " . $a->class;', ], 'grandparentReflectedProperties' => [ 'ownerDocument;', 'assertions' => [ '$owner' => 'DOMDocument', ], ], 'goodArrayProperties' => [ ' */ public $is = []; } $c = new C1; $c->is = [new A1]; $c->is = [new A1, new A1]; $c->is = [new A1, new B1];', 'assertions' => [], 'error_levels' => ['MixedAssignment'], ], 'issetPropertyDoesNotExist' => [ 'bar)) { }', ], 'notSetInConstructorButHasDefault' => [ ' [ 'foo(); } private function foo() : void { $this->a = 5; } }', ], 'definedInTraitSetInConstructor' => [ 'a = "hello"; } }', ], 'propertySetInNestedPrivateMethod' => [ 'foo(); } private function foo() : void { $this->bar(); } private function bar() : void { $this->a = 5; } }', ], 'propertyArrayIssetAssertion' => [ ' */ public $a = []; private function foo() : void { if (isset($this->a["hello"])) { bar($this->a["hello"]); } } }', ], 'propertyArrayIssetAssertionWithVariableOffset' => [ ' */ public $a = []; private function foo() : void { $b = "hello"; if (!isset($this->a[$b])) { return; } bar($this->a[$b]); } }', ], 'staticPropertyArrayIssetAssertionWithVariableOffset' => [ ' */ public static $a = []; } function foo() : void { $b = "hello"; if (!isset(A::$a[$b])) { return; } bar(A::$a[$b]); }', ], 'traitConstructor' => [ 'foo = "hello"; } } class A { use T; }', ], 'abstractClassWithNoConstructor' => [ ' [ 'foo = ""; } } class B extends A { public function __construct() { parent::__construct(); } }', ], 'abstractClassConstructorAndImplicitChildConstructor' => [ 'foo = (string)$bar; } } class B extends A {} class E extends \Exception{}', ], ]; } /** * @return array */ public function providerFileCheckerInvalidCodeParse() { return [ 'undefinedPropertyAssignment' => [ 'foo = "cool";', 'error_message' => 'UndefinedPropertyAssignment', ], 'undefinedPropertyFetch' => [ 'foo;', 'error_message' => 'UndefinedPropertyFetch', ], 'undefinedThisPropertyAssignment' => [ 'foo = "cool"; } }', 'error_message' => 'UndefinedThisPropertyAssignment', ], 'undefinedThisPropertyFetch' => [ 'foo; } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'missingPropertyDeclaration' => [ 'foo = "cool"; }', 'error_message' => 'MissingPropertyDeclaration', ], 'missingPropertyType' => [ 'foo = 5; } }', 'error_message' => 'MissingPropertyType - somefile.php:3 - Property A::$foo does not have a ' . 'declared type - consider null|int', ], 'missingPropertyTypeWithConstructorInit' => [ 'foo = 5; } }', 'error_message' => 'MissingPropertyType - somefile.php:3 - Property A::$foo does not have a ' . 'declared type - consider int', ], 'missingPropertyTypeWithConstructorInitAndNull' => [ 'foo = 5; } public function makeNull() : void { $this->foo = null; } }', 'error_message' => 'MissingPropertyType - somefile.php:3 - Property A::$foo does not have a ' . 'declared type - consider null|int', ], // Skipped. Doesn't yet work. 'SKIPPED-missingPropertyTypeWithConstructorInitInPrivateMethod' => [ 'makeValue(); } private function makeValue() : void { $this->foo = 5; } }', 'error_message' => 'MissingPropertyType - somefile.php:3 - Property A::$foo does not have a ' . 'declared type - consider int', ], 'missingPropertyTypeWithConstructorInitAndNullDefault' => [ 'foo = 5; } }', 'error_message' => 'MissingPropertyType - somefile.php:3 - Property A::$foo does not have a ' . 'declared type - consider int|null', ], 'badAssignment' => [ 'foo = 5; } }', 'error_message' => 'InvalidPropertyAssignment', ], 'badAssignmentAsWell' => [ 'foo = "bar";', 'error_message' => 'InvalidPropertyAssignment', ], 'badFetch' => [ 'foo;', 'error_message' => 'InvalidPropertyFetch', ], 'mixedPropertyFetch' => [ 'foo;', 'error_message' => 'MixedPropertyFetch', 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'mixedPropertyAssignment' => [ 'foo = "hello";', 'error_message' => '', 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'possiblyNullablePropertyAssignment' => [ 'foo = "hello";', 'error_message' => 'PossiblyNullPropertyAssignment', ], 'nullablePropertyAssignment' => [ 'foo = "hello";', 'error_message' => 'NullPropertyAssignment', ], 'possiblyNullablePropertyFetch' => [ 'foo;', 'error_message' => 'PossiblyNullPropertyFetch', ], 'nullablePropertyFetch' => [ 'foo;', 'error_message' => 'NullPropertyFetch', ], 'badArrayProperty' => [ ' */ public $bb; } $c = new C; $c->bb = [new A, new B];', 'error_message' => 'InvalidPropertyAssignment', 'error_levels' => ['MixedAssignment'], ], 'notSetInEmptyConstructor' => [ ' 'PropertyNotSetInConstructor', ], 'noConstructor' => [ ' 'MissingConstructor', ], 'abstractClassInheritsNoConstructor' => [ ' 'MissingConstructor', ], 'abstractClassInheritsPrivateConstructor' => [ 'foo = "hello"; } } class B extends A {}', 'error_message' => 'InaccessibleMethod', ], 'classInheritsPrivateConstructorWithImplementedConstructor' => [ 'foo = "hello"; } } class B extends A { public function __construct() {} }', 'error_message' => 'PropertyNotSetInConstructor', ], 'notSetInAllBranchesOfIf' => [ 'a = 5; } } }', 'error_message' => 'PropertyNotSetInConstructor', ], 'propertySetInProtectedMethod' => [ 'foo(); } protected function foo() : void { $this->a = 5; } }', 'error_message' => 'PropertyNotSetInConstructor', ], 'definedInTraitNotSetInEmptyConstructor' => [ ' 'PropertyNotSetInConstructor', ], 'propertySetInPrivateMethodWithIf' => [ 'foo(); } } private function foo() : void { $this->a = 5; } }', 'error_message' => 'PropertyNotSetInConstructor', ], 'propertySetInPrivateMethodWithIfAndElse' => [ 'foo(); } else { $this->bar(); } } private function foo() : void { $this->a = 5; } private function bar() : void { $this->a = 5; } }', 'error_message' => 'PropertyNotSetInConstructor', ], 'undefinedPropertyClass' => [ ' 'UndefinedClass', ], 'abstractClassWithNoConstructorButChild' => [ ' 'PropertyNotSetInConstructor', ], ]; } }