file_provider = new Provider\FakeFileProvider(); } /** * @param Config $config * * @return \Psalm\Internal\Analyzer\ProjectAnalyzer */ private function getProjectAnalyzerWithConfig(Config $config) { return new \Psalm\Internal\Analyzer\ProjectAnalyzer( $config, new \Psalm\Internal\Provider\Providers( $this->file_provider, new Provider\FakeParserCacheProvider() ), new \Psalm\Report\ReportOptions() ); } /** * @return void */ public function testStringAnalyzerPlugin() { $this->expectExceptionMessage('InvalidClass'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @return void */ public function testStringAnalyzerPluginWithClassConstant() { $this->expectExceptionMessage('InvalidClass'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, ' "Psalm\Internal\Analyzer\ProjectAnalyzer", ]; }' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testStringAnalyzerPluginWithClassConstantConcat() { $this->expectExceptionMessage('UndefinedMethod'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, ' \Psalm\Internal\Analyzer\ProjectAnalyzer::class . "::foo", ]; }' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testEchoAnalyzerPluginWithJustHtml() { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, '

This is a header

' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testEchoAnalyzerPluginWithUnescapedConcatenatedString() { $this->expectExceptionMessage('TypeCoercion'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, '' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testEchoAnalyzerPluginWithUnescapedString() { $this->expectExceptionMessage('TypeCoercion'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, '' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testEchoAnalyzerPluginWithEscapedString() { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, ' Some text ' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testFloatCheckerPlugin() { $this->expectExceptionMessage('NoFloatAssignment'); $this->expectException(\Psalm\Exception\CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @return void */ public function testFloatCheckerPluginIssueSuppressionByConfig() { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @return void */ public function testFloatCheckerPluginIssueSuppressionByDocblock() { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** @return void */ public function testInheritedHookHandlersAreCalled() { require_once dirname(__DIR__) . '/fixtures/stubs/extending_plugin_entrypoint.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $this->assertContains( 'ExtendingPlugin', $this->project_analyzer->getCodebase()->config->after_function_checks ); } /** @return void */ public function testAfterCodebasePopulatedHookIsLoaded() { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $hook = new class implements AfterCodebasePopulatedInterface { /** @return void */ public static function afterCodebasePopulated(Codebase $codebase) { } }; $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; (new PluginRegistrationSocket($config, $codebase))->registerHooksFromClass(get_class($hook)); $this->assertContains( get_class($hook), $this->project_analyzer->getCodebase()->config->after_codebase_populated ); } /** @return void */ public function testPropertyProviderHooks() { require_once __DIR__ . '/Plugin/PropertyPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'magic_property;' ); $this->analyzeFile($file_path, new Context()); } /** @return void */ public function testMethodProviderHooksValidArg() { require_once __DIR__ . '/Plugin/MethodPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'magicMethod("hello"); echo $foo::magicMethod("hello"); $foo2 = $foo->magicMethod2("test"); $foo2->id(); i($foo2); echo $foo2->id();' ); $this->analyzeFile($file_path, new Context()); } /** @return void */ public function testFunctionProviderHooks() { require_once __DIR__ . '/Plugin/FunctionPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** @return void */ public function testSqlStringProviderHooks() { require_once __DIR__ . '/Plugin/SqlStringProviderPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, $context); $this->assertTrue(isset($context->vars_in_scope['$a'])); foreach ($context->vars_in_scope['$a']->getAtomicTypes() as $type) { $this->assertInstanceOf(\Psalm\Test\Config\Plugin\Hook\StringProvider\TSqlSelectString::class, $type); } } /** * @return void */ public function testPropertyProviderHooksInvalidAssignment() { $this->expectExceptionMessage('InvalidPropertyAssignmentValue'); $this->expectException(\Psalm\Exception\CodeException::class); require_once __DIR__ . '/Plugin/PropertyPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'magic_property = 5;' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testMethodProviderHooksInvalidArg() { $this->expectExceptionMessage('InvalidScalarArgument'); $this->expectException(\Psalm\Exception\CodeException::class); require_once __DIR__ . '/Plugin/MethodPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'magicMethod(5);' ); $this->analyzeFile($file_path, new Context()); } /** * @return void */ public function testFunctionProviderHooksInvalidArg() { $this->expectExceptionMessage('InvalidScalarArgument'); $this->expectException(\Psalm\Exception\CodeException::class); require_once __DIR__ . '/Plugin/FunctionPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @return void */ public function testAfterAnalysisHooks() { require_once __DIR__ . '/Plugin/AfterAnalysisPlugin.php'; $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $this->assertNotNull($this->project_analyzer->stdout_report_options); $this->project_analyzer->stdout_report_options->format = \Psalm\Report::TYPE_JSON; $this->project_analyzer->check('tests/fixtures/DummyProject', true); \Psalm\IssueBuffer::finish($this->project_analyzer, true, microtime(true)); } /** * @return void */ public function testPluginFilenameCanBeAbsolute() { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, sprintf( ' ', __DIR__ . '/../..' ) ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); } public function testPluginInvalidAbsoluteFilenameThrowsException() : void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('does-not-exist/plugins/StringChecker.php'); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, sprintf( ' ', __DIR__ . '/..' ) ) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); } public function testAfterEveryFunctionPluginIsCalledInAllCases(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, ' ' ) ); $mock = $this->getMockBuilder(\stdClass::class)->setMethods(['check'])->getMock(); $mock->expects($this->exactly(3)) ->method('check') ->withConsecutive( [$this->equalTo('array_map')], [$this->equalTo('fopen')], [$this->equalTo('a')] ); $plugin = new class($mock) implements AfterEveryFunctionCallAnalysisInterface { /** @var MockObject */ private static $m; public function __construct(MockObject $m) { self::$m = $m; } public static function afterEveryFunctionCallAnalysis( FuncCall $expr, string $function_id, Context $context, StatementsSource $statements_source, Codebase $codebase ): void { /** @psalm-suppress UndefinedInterfaceMethod */ self::$m->check($function_id); } }; $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $this->project_analyzer->getCodebase()->config->after_every_function_checks[] = get_class($plugin); $file_path = getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } }