allow_phpstorm_generics = true; $this->addFile( 'somefile.php', 'offsetGet("a"); takesString($s); foreach ($i as $s2) { takesString($s2); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testPhpStormGenericsWithClassProperty() { Config::getInstance()->allow_phpstorm_generics = true; $this->addFile( 'somefile.php', 'bar; } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testPhpStormGenericsWithValidIterableArgument() { Config::getInstance()->allow_phpstorm_generics = true; $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage InvalidScalarArgument * * @return void */ public function testPhpStormGenericsInvalidArgument() { Config::getInstance()->allow_phpstorm_generics = true; $this->addFile( 'somefile.php', 'offsetGet("a"); takesInt($s); }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage PossiblyInvalidMethodCall * * @return void */ public function testPhpStormGenericsNoTypehint() { Config::getInstance()->allow_phpstorm_generics = true; $this->addFile( 'somefile.php', 'offsetGet("a"); }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage InvalidArgument * * @return void */ public function testDontAllowStringConstCoercion() { Config::getInstance()->allow_coercion_from_string_to_class_const = false; $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage InvalidStringClass * * @return void */ public function testDontAllowStringStandInForNewClass() { Config::getInstance()->allow_string_standin_for_class = false; $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage InvalidStringClass * * @return void */ public function testDontAllowStringStandInForStaticMethodCall() { Config::getInstance()->allow_string_standin_for_class = false; $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return array */ public function providerFileCheckerValidCodeParse() { return [ 'nopType' => [ ' [ '$a' => 'int', ], ], 'deprecatedMethod' => [ ' [ ' */ function foo2(): array { return ["hello"]; } /** * @return array */ function foo3(): array { return ["hello"]; }', ], 'reassertWithIs' => [ ' [], 'error_level' => ['RedundantConditionGivenDocblockType'], ], 'checkArrayWithIs' => [ ' [], 'error_level' => ['RedundantConditionGivenDocblockType'], ], 'checkArrayWithIsInsideLoop' => [ '> $data */ function foo($data): void { foreach ($data as $key => $val) { if (!\is_array($data)) { $data = [$key => null]; } else { $data[$key] = !empty($val); } } }', 'assertions' => [], 'error_level' => ['LoopInvalidation', 'MixedArrayOffset', 'RedundantConditionGivenDocblockType'], ], 'goodDocblock' => [ ' [ ' [ 'foo = "hello"; $a->bar = "hello"; // not a property', ], 'propertyOfTypeClassDocblock' => [ 'foo = new PropertyType();', ], 'propertySealedDocblockDefinedPropertyFetch' => [ 'foo;', ], 'ignoreNullableReturn' => [ 'foo(); $a->bar = 7; takeA($a);', ], 'invalidDocblockParamSuppress' => [ ' [ ' [ ' */ $a = []; $a[0]->getMessage();', ], 'mixedDocblockParamTypeDefinedInParent' => [ ' [ ' [ 'foo(); } }', ], 'psalmVar' => [ ' */ public $foo = []; public function updateFoo(): void { $this->foo[5] = "hello"; } }', ], 'psalmParam' => [ ' $a * @param string[] $a */ function foo(array $a): void { foreach ($a as $key => $value) { takesInt($key); } }', ], 'returnDocblock' => [ ' [ ' new stdClass, "goodbye" => new stdClass]; } $a = null; $b = null; /** * @var string $key * @var stdClass $value */ foreach (foo() as $key => $value) { $a = $key; $b = $value; }', 'assertions' => [ '$a' => 'null|string', '$b' => 'null|stdClass', ], ], /** * With a magic setter and no annotations specifying properties or types, we can * set anything we want on any variable name. The magic setter is trusted to figure * it out. */ 'magicSetterUndefinedPropertyNoAnnotation' => [ '__set("foo", new stdClass()); } }', ], /** * With a magic getter and no annotations specifying properties or types, we can * get anything we want with any variable name. The magic getter is trusted to figure * it out. */ 'magicGetterUndefinedPropertyNoAnnotation' => [ '__get("foo"); } }', ], /** * The property $foo is defined as a string with the `@property` annotation. We * use the magic setter to set it to a string, so everything is cool. */ 'magicSetterValidAssignmentType' => [ '__set("foo", "value"); } }', ], 'propertyDocblockAssignmentToMixed' => [ '__set("foo", $b); }', 'assertions' => [], 'error_level' => ['MixedAssignment', 'MixedTypeCoercion'], ], 'arrayOfClassConstants' => [ ' $arr */ function takesClassConstants(array $arr) : void {} class A {} class B {} takesClassConstants([A::class, B::class]);', ], 'arrayOfStringClasses' => [ ' $arr */ function takesClassConstants(array $arr) : void {} class A {} class B {} takesClassConstants(["A", "B"]);', 'annotations' => [], 'error_levels' => ['TypeCoercion'], ], 'singleClassConstant' => [ ' [ ' [], 'error_levels' => ['TypeCoercion'], ], 'returnClassConstant' => [ ' [ ' [], 'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'], ], 'returnClassConstantArray' => [ ' */ function takesClassConstants() : array { return [A::class, B::class]; }', ], 'returnClassConstantArrayAllowCoercion' => [ ' */ function takesClassConstants() : array { return ["A", "B"]; }', 'annotations' => [], 'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'], ], 'allowOptionalParamsToBeEmptyArray' => [ ' [ ' [ ' [ ' 'MissingDocblockType', ], 'invalidReturnClass' => [ ' 'InvalidDocblock', ], 'invalidReturnBrackets' => [ ' 'InvalidDocblock', ], 'invalidPropertyClass' => [ ' 'InvalidDocblock', ], 'invalidPropertyBrackets' => [ ' 'InvalidDocblock', ], 'invalidReturnClassWithComma' => [ ' 'InvalidDocblock', ], 'returnClassWithComma' => [ ' 'InvalidDocblock', ], 'deprecatedMethodWithCall' => [ ' 'DeprecatedMethod', ], 'deprecatedClassWithStaticCall' => [ ' 'DeprecatedClass', ], 'deprecatedClassWithNew' => [ ' 'DeprecatedClass', ], 'deprecatedClassWithExtends' => [ ' 'DeprecatedClass', ], 'deprecatedPropertyGet' => [ 'foo;', 'error_message' => 'DeprecatedProperty', ], 'deprecatedPropertySet' => [ 'foo = 5;', 'error_message' => 'DeprecatedProperty', ], 'missingParamType' => [ ' 'TooManyArguments - src/somefile.php:8 - Too many arguments for method fooBar ' . '- expecting 0 but saw 1', ], 'missingParamVar' => [ ' 'InvalidDocblock - src/somefile.php:5 - Badly-formatted @param', ], 'invalidDocblockReturn' => [ ' 'MismatchingDocblockReturnType', ], 'propertyDocblockInvalidAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertyInvalidClassAssignment' => [ 'foo = new SomeOtherPropertyType();', 'error_message' => 'InvalidPropertyAssignmentValue - src/somefile.php:27 - $a->foo with declared type' . ' \'Bar\PropertyType\' cannot', ], 'propertyWriteDocblockInvalidAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertySealedDocblockUndefinedPropertyAssignment' => [ 'bar = 5;', 'error_message' => 'UndefinedPropertyAssignment', ], 'propertySealedDocblockDefinedPropertyAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertyReadInvalidFetch' => [ 'foo);', 'error_message' => 'InvalidArgument', ], 'propertySealedDocblockUndefinedPropertyFetch' => [ 'bar;', 'error_message' => 'UndefinedPropertyFetch', ], 'noStringParamType' => [ ' 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,' . ' should be string', 'error_levels' => ['MixedArgument'], ], 'noParamTypeButConcat' => [ ' 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,' . ' should be string', 'error_levels' => ['MixedOperand'], ], 'noParamTypeButAddition' => [ ' 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,' . ' should be int|float', 'error_levels' => ['MixedOperand', 'MixedArgument'], ], 'noParamTypeButDivision' => [ ' 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,' . ' should be int|float', 'error_levels' => ['MixedOperand', 'MixedArgument'], ], 'noParamTypeButTemplatedString' => [ ' 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,' . ' should be string', 'error_levels' => ['MixedOperand'], ], 'noStringIntParamType' => [ ' 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,' . ' should be int|string', 'error_levels' => ['MixedArgument'], ], 'intParamTypeDefinedInParent' => [ ' 'MissingParamType', 'error_levels' => ['MethodSignatureMismatch'], ], 'alreadyHasCheck' => [ ' 'MissingParamType - src/somefile.php:4 - Parameter $s has no provided type,' . ' could not infer', 'error_levels' => ['MixedArgument'], ], 'isSetBeforeInferrence' => [ 'input' => ' 'MissingParamType - src/somefile.php:7 - Parameter $s has no provided type,' . ' could not infer', 'error_levels' => ['MixedArgument', 'InvalidReturnType', 'MixedAssignment'], ], 'psalmInvalidVar' => [ ' */ public $foo = []; public function updateFoo(): void { $this->foo["boof"] = "hello"; } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'incorrectDocblockOrder' => [ ' 'MissingDocblockType', ], 'badlyFormattedVar' => [ ' 'InvalidDocblock', ], 'badlyWrittenVar' => [ 'conn()->method(); $myVar->otherMethod(); }', 'error_message' => 'MissingDocblockType', ], 'dontOverrideSameType' => [ ' 'InvalidReturnType', ], 'alwaysCheckReturnType' => [ ' 'UndefinedClass', ], 'preventBadBoolean' => [ ' 'UndefinedClass', ], 'preventBadObjectLikeFormat' => [ ' 'InvalidDocblock', ], 'noPhpStormAnnotationsThankYou' => [ ' 'MismatchingDocblockParamType', ], 'noPhpStormAnnotationsPossiblyInvalid' => [ 'offsetGet("a"); }', 'error_message' => 'PossiblyInvalidMethodCall', ], /** * The property $foo is not defined on the object, but accessed with the magic setter. * This is an error because `@psalm-seal-properties` is specified on the class block. */ 'magicSetterUndefinedProperty' => [ '__set("foo", "value"); } }', 'error_message' => 'UndefinedThisPropertyAssignment', ], /** * The property $foo is not defined on the object, but accessed with the magic getter. * This is an error because `@psalm-seal-properties` is specified on the class block. */ 'magicGetterUndefinedProperty' => [ '__get("foo"); } }', 'error_message' => 'UndefinedThisPropertyFetch', ], /** * The property $foo is defined as a string with the `@property` annotation, but * the magic setter is used to set it to an object. */ 'magicSetterInvalidAssignmentType' => [ '__set("foo", new stdClass()); } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertyDocblockAssignmentToMixed' => [ '__set("foo", $b); }', 'error_message' => 'MixedTypeCoercion', 'error_levels' => ['MixedAssignment'], ], 'arrayOfStringClasses' => [ ' $arr */ function takesClassConstants(array $arr) : void {} class A {} class B {} takesClassConstants(["A", "B"]);', 'error_message' => 'TypeCoercion', ], 'arrayOfNonExistentStringClasses' => [ ' $arr */ function takesClassConstants(array $arr) : void {} takesClassConstants(["A", "B"]);', 'error_message' => 'UndefinedClass', 'error_levels' => ['TypeCoercion'], ], 'singleClassConstantWithInvalidDocblock' => [ ' 'InvalidDocblock', ], 'returnClassConstantDisallowCoercion' => [ ' 'LessSpecificReturnStatement', ], 'returnClassConstantArrayDisallowCoercion' => [ ' */ function takesClassConstants() : array { return ["A", "B"]; }', 'error_message' => 'LessSpecificReturnStatement', ], 'returnClassConstantArrayAllowCoercionWithUndefinedClass' => [ ' */ function takesClassConstants() : array { return ["A", "B"]; }', 'error_message' => 'UndefinedClass', 'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'], ], 'badStaticVar' => [ ' 'InvalidDocblock', ], 'doubleBar' => [ ' 'InvalidDocblock', ], ]; } }