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,
'= $unsafe . "safeString" ?>'
);
$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,
'= $unsafe ?>'
);
$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
= escapeHtml($unsafe) ?>'
);
$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());
}
}