addFile( 'somefile.php', ' $arguments * @param array $options default null * @param array|SoapHeader $input_headers default null * @param array $output_headers default null * @return mixed */ public function __soapCall( $function_name, $arguments, $options = [], $input_headers = [], &$output_headers = [] ) { return $_GET["foo"]; } }', ); $this->analyzeFile('somefile.php', new Context()); } public function testExtendSoapClientWithNoDocblockTypes(): void { $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testExtendSoapClientWithParamType(): void { $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testMismatchingCovariantReturnIn73(): void { $this->expectExceptionMessage('MethodSignatureMismatch'); $this->expectException(CodeException::class); $this->project_analyzer->setPhpVersion('7.3', 'tests'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testMismatchingCovariantReturnIn74(): void { $this->project_analyzer->setPhpVersion('7.4', 'tests'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testMismatchingCovariantReturnIn73WithSelf(): void { $this->expectExceptionMessage('MethodSignatureMismatch'); $this->expectException(CodeException::class); $this->project_analyzer->setPhpVersion('7.3', 'tests'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testMismatchingCovariantReturnIn74WithSelf(): void { $this->project_analyzer->setPhpVersion('7.4', 'tests'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testMismatchingCovariantParamIn73(): void { $this->expectExceptionMessage('MethodSignatureMismatch'); $this->expectException(CodeException::class); $this->project_analyzer->setPhpVersion('7.3', 'tests'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testMismatchingCovariantParamIn74(): void { $this->project_analyzer->setPhpVersion('7.4', 'tests'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testExtendDocblockParamTypeWithWrongDocblockParam(): void { $this->expectExceptionMessage('ImplementedParamTypeMismatch'); $this->expectException(CodeException::class); $this->addFile( 'somefile.php', ' $options default null * @param array $input_headers default null * @param array $output_headers default null * @return mixed */ public function __soapCall( $function_name, $arguments, $options = [], $input_headers = [], &$output_headers = [] ) { } }', ); $this->analyzeFile('somefile.php', new Context()); } public function testExtendDocblockParamTypeWithWrongParam(): void { $this->expectException(CodeException::class); $this->expectExceptionMessage('MethodSignatureMismatch'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function providerValidCodeParse(): iterable { return [ 'privateArgs' => [ 'code' => ' [ 'code' => 'foo(null);', ], 'nullableSubclassParamWithDefault' => [ 'code' => 'foo();', ], 'allowSubclassesForNonInheritedMethodParams' => [ 'code' => 'bar(); }', ], 'allowNoReturnInSubclassWithNullableReturnType' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ '$b' => 'B', ], ], 'returnIgnoresInlineComments' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => 'id, ] = (array) \unserialize($serialized); } public function serialize() : string { return serialize([$this->id]); } }', ], 'clashWithCallMapClass' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => 'boo(new A()); (new Z())->boo(new A());', ], 'allowMixedExtensionOfIteratorAggregate' => [ 'code' => ' [ 'code' => 'f(); (new C)->f("b"); (new C)->f("b", 3); (new C)->f("b", 3, 0.5); (new C)->f("b", 3, 0.5, 0.8);', ], 'allowLessSpecificDocblockTypeOnParent' => [ 'code' => 'getTargets();', 'assertions' => [ '$a' => 'string', ], ], 'parentIsKnown' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [], 'ignored_issues' => [], 'php_version' => '7.4', ], 'allowOverridingThrowable' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [], 'ignored_issues' => [], 'php_version' => '7.1', ], 'consistentConstructor' => [ 'code' => ' [ 'code' => ' [], 'ignored_issues' => [], 'php_version' => '8.0', ], 'suppressDocblockFinal' => [ 'code' => ' [ 'code' => ' [], 'ignored_issues' => [], 'php_version' => '7.4', ], 'extendStaticReturnTypeInFinal' => [ 'code' => ' [], 'ignored_issues' => [], 'php_version' => '8.0', ], 'notExtendedStaticReturntypeInFinal' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [], 'ignored_errors' => [], 'php_version' => '8.0', ], 'doesNotRequireInterfaceDestructorsToHaveReturnType' => [ 'code' => ' [ 'code' => 'x; } } ', ], 'descendantAddsByRefReturn' => [ 'code' => 'x; } } ', ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'oneParam' => [ 'code' => ' 'Argument 1 of C::foo has wrong name $c, expecting $i as defined by I::foo', ], 'moreArguments' => [ 'code' => ' 'Method B::fooFoo has more required parameters than parent method A::fooFoo', ], 'fewerArguments' => [ 'code' => ' 'Method B::fooFoo has fewer parameters than parent method A::fooFoo', ], 'differentArgumentTypes' => [ 'code' => ' 'Argument 2 of B::fooFoo has wrong type \'int\', expecting \'bool\' as defined ' . 'by A::fooFoo', ], 'differentArgumentNames' => [ 'code' => ' 'ParamNameMismatch', ], 'nonNullableSubclassParam' => [ 'code' => ' 'Argument 1 of B::foo has wrong type \'string\', expecting \'null|string\' as', ], 'misplacedRequiredParam' => [ 'code' => ' 'TooFewArguments', ], 'clasginByRef' => [ 'code' => ' 'MethodSignatureMismatch', ], 'disallowSubclassesForNonInheritedMethodParams' => [ 'code' => 'bar(); } }', 'error_message' => 'MoreSpecificImplementedParamType', ], 'preventVoidToNullConversionSignature' => [ 'code' => ' 'MethodSignatureMismatch', ], 'abstractExtendsNonAbstractWithMethod' => [ 'code' => ' 'MethodSignatureMismatch', ], 'traitReturnTypeMismatch' => [ 'code' => ' 'MethodSignatureMismatch', ], 'abstractTraitMethodWithDifferentReturnType' => [ 'code' => ' 'TraitMethodSignatureMismatch', ], 'traitMoreParams' => [ 'code' => ' 'MethodSignatureMismatch', ], 'abstractTraitMethodWithDifferentParamType' => [ 'code' => ' 'TraitMethodSignatureMismatch', ], 'mustOmitReturnType' => [ 'code' => ' 'MethodSignatureMustOmitReturnType', ], 'requireParam' => [ 'code' => ' 'MethodSignatureMismatch - src' . DIRECTORY_SEPARATOR . 'somefile.php:6:27 - Method C::foo has more required', ], 'inheritParamTypes' => [ 'code' => 'foo(new stdClass);', 'error_message' => 'InvalidArgument', ], 'interfaceHasFewerConstructorArgs' => [ 'code' => ' 'ConstructorSignatureMismatch', ], 'enforceParameterInheritanceWithInheritDoc' => [ 'code' => 'boo(new A());', 'error_message' => 'ArgumentTypeCoercion', ], 'enforceParameterInheritanceWithCapitalizedInheritDoc' => [ 'code' => 'boo(new A());', 'error_message' => 'ArgumentTypeCoercion', ], 'warnAboutMismatchingClassParamDoc' => [ 'code' => ' 'MismatchingDocblockParamType', ], 'warnAboutMismatchingInterfaceParamDoc' => [ 'code' => ' 'MismatchingDocblockParamType', ], 'interfaceInsertDocblockTypes' => [ 'code' => ' */ public function getFoos() : array; } class A implements I { public function getFoos() : array { return [new Bar()]; } }', 'error_message' => 'InvalidReturnStatement', ], 'classInsertDocblockTypesFromParent' => [ 'code' => ' */ public function getFoos() : array { return [new Foo()]; } } class A extends B { public function getFoos() : array { return [new Bar()]; } }', 'error_message' => 'InvalidReturnStatement', ], 'preventInterfaceOverload' => [ 'code' => ' $f * @psalm-suppress ParamNameMismatch */ public function f($f): void {} }', 'error_message' => 'MethodSignatureMismatch', 'ignored_issues' => ['MoreSpecificImplementedParamType'], ], 'preventOneOfUnionMoreSpecific' => [ 'code' => ' 'MoreSpecificImplementedParamType', ], 'preventImplementingSerializableWithWrongDocblockType' => [ 'code' => ' 'ImplementedParamTypeMismatch', ], 'returnsParentWithNoParent' => [ 'code' => ' 'InvalidParent', ], 'returnsParentWithNoParentAndInvalidParentSuppressed' => [ 'code' => ' 'InvalidReturnType', 'ignored_issues' => ['InvalidParent'], ], // not sure how to handle it 'SKIPPED-returnsParentWithNoParentAndInvalidParentSuppressedMismatchingReturn' => [ 'code' => ' 'InvalidReturnType', 'ignored_issues' => ['InvalidParent'], ], 'regularMethodMismatchFromParentUse' => [ 'code' => ' 'MethodSignatureMismatch', ], 'regularMethodMismatchFromChildUse' => [ 'code' => ' 'MethodSignatureMismatch', ], 'traitMethodAccessLevel' => [ 'code' => ' 'TraitMethodSignatureMismatch', ], 'abstractClassReturnMismatch' => [ 'code' => ' 'MethodSignatureMismatch', ], 'abstractClassParamMismatch' => [ 'code' => ' 'MethodSignatureMismatch', ], 'preventTraitMatchIn73' => [ 'code' => ' 'MethodSignatureMismatch', 'ignored_issues' => [], 'php_version' => '7.3', ], 'inconsistentConstructorExplicitParentConstructorArgCount' => [ 'code' => ' 'ConstructorSignatureMismatch', ], 'inconsistentConstructorExplicitParentConstructorType' => [ 'code' => ' 'ConstructorSignatureMismatch', ], 'inconsistentConstructorImplicitParentConstructor' => [ 'code' => ' 'ConstructorSignatureMismatch', ], 'inheritDocblockReturnFromInterface' => [ 'code' => ' 'InvalidReturnType', ], 'disableNamedArgumentsInDescendant' => [ 'code' => ' 'MethodSignatureMismatch', ], 'SKIPPED-noMixedTypehintInDescendant' => [ 'code' => ' 'MethodSignatureMismatch', 'ignored_issues' => [], 'php_version' => '8.0', ], 'noTypehintInNativeDescendant' => [ 'code' => ' 'MethodSignatureMustProvideReturnType', 'ignored_issues' => [], 'php_version' => '8.1', ], 'absentByRefReturnInDescendant' => [ 'code' => ' 'MethodSignatureMismatch', ], 'methodAnnotationReturnMismatch' => [ 'code' => ' 'MismatchingDocblockReturnType', ], 'methodAnnotationParamMismatch' => [ 'code' => ' 'MismatchingDocblockParamType', ], ]; } }