file_provider = new FakeFileProvider(); } private function getProjectAnalyzerWithConfig(Config $config): \Psalm\Internal\Analyzer\ProjectAnalyzer { $project_analyzer = new \Psalm\Internal\Analyzer\ProjectAnalyzer( $config, new \Psalm\Internal\Provider\Providers( $this->file_provider, new Provider\FakeParserCacheProvider() ) ); $project_analyzer->setPhpVersion('7.4', 'tests'); $config->setIncludeCollector(new IncludeCollector()); $config->visitComposerAutoloadFiles($project_analyzer, null); return $project_analyzer; } public function testNonexistentStubFile(): void { $this->expectException(\Psalm\Exception\ConfigException::class); $this->expectExceptionMessage('Cannot resolve stubfile path'); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( Config::loadFromXML( dirname(__DIR__), ' ' ) ); } public function testStubFileClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(5, "hello"); $c = \SystemClass::bar(5, "hello"); echo \SystemClass::HELLO;' ); $this->analyzeFile($file_path, new Context()); } /** * @psalm-pure */ private function getOperatingSystemStyledPath(string $file): string { return implode(DIRECTORY_SEPARATOR, explode('/', $file)); } public function testLoadStubFileWithRelativePath(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $path = $this->getOperatingSystemStyledPath('tests/fixtures/stubs/systemclass.phpstub'); $stub_files = $this->project_analyzer->getConfig()->getStubFiles(); $this->assertStringContainsString($path, \reset($stub_files)); } public function testLoadStubFileWithAbsolutePath(): void { $runDir = dirname(__DIR__); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( $runDir, ' ' ) ); $path = $this->getOperatingSystemStyledPath('tests/fixtures/stubs/systemclass.phpstub'); $stub_files = $this->project_analyzer->getConfig()->getStubFiles(); $this->assertStringContainsString($path, \reset($stub_files)); } public function testStubFileConstant(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testPhpStormMetaParsingFile(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'creAte("object"); $a2 = (new \Ns\MyClass)->creaTe("exception"); $b1 = \Create("object"); $b2 = \cReate("exception"); $e2 = \creAte(\LogicException::class); $c1 = (new \Ns\MyClass)->foo(5); $c2 = (new \Ns\MyClass)->bar(["hello"]); $d1 = \foO(5); $d2 = \baR(["hello"]); }' ); $context = new Context(); $this->analyzeFile($file_path, $context); } public function testNamespacedStubClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(5, "hello"); $c = Foo\SystemClass::bar(5, "hello"); echo Foo\BAR;' ); $this->analyzeFile($file_path, new Context()); } public function testStubRegularFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubVariadicFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubVariadicFunctionWrongArgType(): void { $this->expectExceptionMessage('InvalidScalarArgument'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testUserVariadicWithFalseVariadic(): void { $this->expectExceptionMessage('TooManyArguments'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testPolyfilledFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testConditionalConstantDefined(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testClassAlias(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo; echo $a->bar("hello"); function f(): A { return new A; } function getAliased(): B { return f(); } $d = new D(); D::bat(); $d::bat(); class E implements IAlias {}' ); $this->analyzeFile($file_path, new Context()); } public function testStubFunctionWithFunctionExists(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testNamespacedStubFunctionWithFunctionExists(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testNoStubFunction(): void { $this->expectExceptionMessage('UndefinedFunction - /src/somefile.php:2:22 - Function barBar does not exist'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testNamespacedStubFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testConditionalNamespacedStubFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testConditionallyExtendingInterface(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'getMessage(); } function bar(I6 $d) : void { $d->getMessage(); } function bat(I7 $d) : void { $d->getMessage(); } function baz(I8 $d) : void { $d->getMessage(); }' ); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithExistingClassDefinition(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** @return iterable */ public function versionDependentStubsProvider(): iterable { yield '7.0' => [ '7.0', 'something("zzz");' ]; yield '8.0' => [ '8.0', 'something();' ]; } /** @dataProvider versionDependentStubsProvider */ public function testVersionDependentStubs(string $php_version, string $code): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $this->project_analyzer->setPhpVersion($php_version, 'tests'); $file_path = getcwd() . '/src/somefile.php'; $this->addFile($file_path, $code); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithPartialClassDefinitionWithMoreMethods(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(A::class); (new PartiallyStubbedClass())->bar(5);' ); $this->analyzeFile($file_path, new Context()); } public function testExtendOnlyStubbedClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(A::class);' ); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithExtendedStubbedClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubFileWithPartialClassDefinitionWithCoercion(): void { $this->expectExceptionMessage('TypeCoercion'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo("dasda");' ); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithPartialClassDefinitionGeneralReturnType(): void { $this->expectExceptionMessage('InvalidReturnStatement'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubFileWithTemplatedClassDefinitionAndMagicMethodOverride(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testInheritedMethodUsedInStub() : void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $this->project_analyzer->getCodebase()->reportUnusedCode(); $vendor_file_path = getcwd() . '/vendor/vendor_class.php'; $this->addFile( $vendor_file_path, 'foo(); } }' ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context(), false); $this->project_analyzer->consolidateAnalyzedData(); } public function testStubOverridingMissingClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'expectException(\Psalm\Exception\InvalidClasslikeOverrideException::class); $this->analyzeFile($file_path, new Context()); } public function testStubOverridingMissingMethod(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'expectException(\Psalm\Exception\InvalidMethodOverrideException::class); $this->analyzeFile($file_path, new Context()); } public function testStubReplacingInterfaceDocblock(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ' ) ); $this->addFile( getcwd() . '/vendor/doctrine/import.php', 'addFile( $file_path, 'getReference(A::class, 1); }' ); $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('A|null'); $this->analyzeFile($file_path, new Context()); } }