1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00
psalm/tests/Config/ConfigTest.php

1381 lines
42 KiB
PHP
Raw Normal View History

2016-12-29 16:24:10 +01:00
<?php
2019-05-10 00:58:30 +02:00
namespace Psalm\Tests\Config;
2016-12-29 16:24:10 +01:00
use Psalm\Config;
use Psalm\Context;
2019-03-23 19:27:54 +01:00
use Psalm\Internal\Analyzer\FileAnalyzer;
use Psalm\Tests\Internal\Provider;
2019-05-10 00:58:30 +02:00
use Psalm\Tests\TestConfig;
use function defined;
use function define;
use function getcwd;
use function realpath;
use function dirname;
use function unlink;
use function error_get_last;
use function is_array;
use function symlink;
use function preg_match;
use function implode;
use function array_map;
use const DIRECTORY_SEPARATOR;
2016-12-29 16:24:10 +01:00
2019-05-10 00:58:30 +02:00
class ConfigTest extends \Psalm\Tests\TestCase
2016-12-29 16:24:10 +01:00
{
/** @var TestConfig */
protected static $config;
2018-11-06 03:57:36 +01:00
/** @var \Psalm\Internal\Analyzer\ProjectAnalyzer */
2018-11-11 18:01:14 +01:00
protected $project_analyzer;
/**
* @return void
*/
2019-05-17 00:36:36 +02:00
public static function setUpBeforeClass() : void
{
self::$config = new TestConfig();
if (!defined('PSALM_VERSION')) {
define('PSALM_VERSION', '2.0.0');
}
if (!defined('PHP_PARSER_VERSION')) {
define('PHP_PARSER_VERSION', '4.0.0');
}
}
2017-01-13 20:07:23 +01:00
/**
* @return void
*/
2019-05-17 00:36:36 +02:00
public function setUp() : void
2016-12-29 16:24:10 +01:00
{
2018-11-06 03:57:36 +01:00
FileAnalyzer::clearCache();
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$this->file_provider = new Provider\FakeFileProvider();
2016-12-29 16:24:10 +01:00
}
/**
* @param Config $config
*
2018-11-06 03:57:36 +01:00
* @return \Psalm\Internal\Analyzer\ProjectAnalyzer
*/
2018-11-06 03:57:36 +01:00
private function getProjectAnalyzerWithConfig(Config $config)
{
2019-02-07 21:27:43 +01:00
$p = new \Psalm\Internal\Analyzer\ProjectAnalyzer(
$config,
2018-11-06 03:57:36 +01:00
new \Psalm\Internal\Provider\Providers(
$this->file_provider,
new Provider\FakeParserCacheProvider()
)
);
2019-02-07 21:27:43 +01:00
$p->setPhpVersion('7.3');
return $p;
}
2017-01-13 20:07:23 +01:00
/**
* @return void
*/
2016-12-29 16:24:10 +01:00
public function testBarebonesConfig()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
(string)getcwd(),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
</psalm>'
)
);
2016-12-29 16:24:10 +01:00
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
2018-11-06 03:57:36 +01:00
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
2016-12-29 16:24:10 +01:00
}
2017-01-13 20:07:23 +01:00
/**
* @return void
*/
public function testIgnoreProjectDirectory()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<ignoreFiles>
2018-11-06 03:57:36 +01:00
<directory name="src/Psalm/Internal/Analyzer" />
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
2018-11-06 03:57:36 +01:00
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
}
2016-12-30 02:07:42 +01:00
/**
* @return void
*/
public function testIgnoreMissingProjectDirectory()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<ignoreFiles allowMissingFiles="true">
<directory name="does/not/exist" />
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
$config = $this->project_analyzer->getConfig();
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
$this->assertFalse($config->isInProjectDirs(realpath('does/not/exist/FileAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
}
/**
* @return void
*/
public function testIgnoreSymlinkedProjectDirectory()
{
2019-05-10 00:58:30 +02:00
@unlink(dirname(__DIR__, 1) . '/fixtures/symlinktest/ignored/b');
$no_symlinking_error = 'symlink(): Cannot create symlink, error code(1314)';
$last_error = error_get_last();
$check_symlink_error =
2019-03-23 19:27:54 +01:00
!is_array($last_error) ||
!isset($last_error['message']) ||
$no_symlinking_error !== $last_error['message'];
2019-05-10 00:58:30 +02:00
@symlink(dirname(__DIR__, 1) . '/fixtures/symlinktest/a', dirname(__DIR__, 1) . '/fixtures/symlinktest/ignored/b');
if ($check_symlink_error) {
$last_error = error_get_last();
if (is_array($last_error) && $no_symlinking_error === $last_error['message']) {
$this->markTestSkipped($no_symlinking_error);
return;
}
}
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="tests" />
<ignoreFiles>
<directory name="tests/fixtures/symlinktest/ignored" />
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
$config = $this->project_analyzer->getConfig();
$this->assertTrue($config->isInProjectDirs(realpath('tests/AnnotationTest.php')));
$this->assertFalse($config->isInProjectDirs(realpath('tests/fixtures/symlinktest/a/ignoreme.php')));
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
$regex = '/^unlink\([^\)]+\): (?:Permission denied|No such file or directory)$/';
$last_error = error_get_last();
$check_unlink_error =
!is_array($last_error) ||
!preg_match($regex, $last_error['message']);
@unlink(__DIR__ . '/fixtures/symlinktest/ignored/b');
if ($check_unlink_error) {
$last_error = error_get_last();
if (is_array($last_error) && !preg_match($regex, $last_error['message'])) {
throw new \ErrorException(
$last_error['message'],
0,
$last_error['type'],
$last_error['file'],
$last_error['line']
);
}
}
}
/**
* @return void
*/
public function testIgnoreWildcardProjectDirectory()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<ignoreFiles>
2018-11-06 03:57:36 +01:00
<directory name="src/**/Internal/Analyzer" />
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
2018-11-06 03:57:36 +01:00
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
}
/**
* @return void
*/
public function testIgnoreWildcardFiles()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<ignoreFiles>
2018-11-06 03:57:36 +01:00
<file name="src/Psalm/Internal/Analyzer/*Analyzer.php" />
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
2018-11-06 03:57:36 +01:00
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php')));
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
}
2018-07-22 04:34:22 +02:00
/**
* @return void
*/
public function testIgnoreWildcardFilesInWildcardFolder()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
2018-07-22 04:34:22 +02:00
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
2018-07-22 04:34:22 +02:00
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<directory name="examples" />
<ignoreFiles>
2018-11-06 03:57:36 +01:00
<file name="src/Psalm/**/**/*Analyzer.php" />
<file name="src/Psalm/**/**/**/*Analyzer.php" />
2018-07-22 04:34:22 +02:00
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
2018-07-22 04:34:22 +02:00
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/Visitor/ReflectorVisitor.php')));
2018-11-06 03:57:36 +01:00
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php')));
$this->assertTrue($config->isInProjectDirs(realpath('examples/plugins/StringChecker.php')));
2018-07-22 04:34:22 +02:00
}
/**
* @return void
*/
public function testIgnoreWildcardFilesInAllPossibleWildcardFolders()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
2018-07-22 04:34:22 +02:00
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
2018-07-22 04:34:22 +02:00
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<directory name="examples" />
<ignoreFiles>
2018-11-06 03:57:36 +01:00
<file name="**/**/**/**/*Analyzer.php" />
<file name="**/**/**/**/**/*Analyzer.php" />
2018-07-22 04:34:22 +02:00
</ignoreFiles>
</projectFiles>
</psalm>'
)
);
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
2018-07-22 04:34:22 +02:00
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php')));
$this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/Visitor/ReflectorVisitor.php')));
2018-11-06 03:57:36 +01:00
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php')));
$this->assertFalse($config->isInProjectDirs(realpath('examples/StringAnalyzer.php')));
2018-07-22 04:34:22 +02:00
}
2017-01-13 20:07:23 +01:00
/**
* @return void
*/
2016-12-30 02:07:42 +01:00
public function testIssueHandler()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<directory name="tests" />
</projectFiles>
<issueHandlers>
<MissingReturnType errorLevel="suppress" />
</issueHandlers>
</psalm>'
)
);
2016-12-30 02:07:42 +01:00
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
$this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('tests/ConfigTest.php')));
$this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Type.php')));
2016-12-30 02:07:42 +01:00
}
2017-01-13 20:07:23 +01:00
/**
* @return void
*/
2016-12-30 02:07:42 +01:00
public function testIssueHandlerWithCustomErrorLevels()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<directory name="tests" />
</projectFiles>
<issueHandlers>
<MissingReturnType errorLevel="info">
<errorLevel type="suppress">
<directory name="tests" />
</errorLevel>
<errorLevel type="error">
2018-11-06 03:57:36 +01:00
<directory name="src/Psalm/Internal/Analyzer" />
</errorLevel>
</MissingReturnType>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="Psalm\Badger" />
<referencedClass name="Psalm\*Actor" />
<referencedClass name="*MagicFactory" />
</errorLevel>
</UndefinedClass>
<UndefinedMethod>
<errorLevel type="suppress">
<referencedMethod name="Psalm\Bodger::find1" />
<referencedMethod name="*::find2" />
</errorLevel>
</UndefinedMethod>
<UndefinedFunction>
<errorLevel type="suppress">
<referencedFunction name="fooBar" />
</errorLevel>
2019-05-03 23:12:20 +02:00
<errorLevel type="info">
<directory name="examples" />
</errorLevel>
</UndefinedFunction>
2019-05-03 23:12:20 +02:00
<PossiblyInvalidArgument>
<errorLevel type="suppress">
<directory name="tests" />
</errorLevel>
<errorLevel type="info">
<directory name="examples" />
</errorLevel>
</PossiblyInvalidArgument>
<UndefinedPropertyFetch>
<errorLevel type="suppress">
<referencedProperty name="Psalm\Bodger::$find3" />
</errorLevel>
</UndefinedPropertyFetch>
</issueHandlers>
</psalm>'
)
);
2016-12-30 02:07:42 +01:00
2018-11-11 18:01:14 +01:00
$config = $this->project_analyzer->getConfig();
$this->assertSame(
'info',
$config->getReportingLevelForFile(
'MissingReturnType',
realpath('src/Psalm/Type.php')
)
);
$this->assertSame(
'error',
$config->getReportingLevelForFile(
'MissingReturnType',
2018-11-06 03:57:36 +01:00
realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php')
)
);
2019-05-03 23:12:20 +02:00
$this->assertSame(
'error',
$config->getReportingLevelForFile(
'PossiblyInvalidArgument',
realpath('src/psalm.php')
)
);
$this->assertSame(
'info',
$config->getReportingLevelForFile(
'PossiblyInvalidArgument',
realpath('examples/TemplateChecker.php')
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForClass(
'UndefinedClass',
'Psalm\Badger'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForClass(
'UndefinedClass',
'Psalm\BadActor'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForClass(
'UndefinedClass',
'Psalm\GoodActor'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForClass(
'UndefinedClass',
'Psalm\MagicFactory'
)
);
$this->assertSame(
2019-05-03 23:12:20 +02:00
null,
$config->getReportingLevelForClass(
'UndefinedClass',
'Psalm\Bodger'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForMethod(
'UndefinedMethod',
'Psalm\Bodger::find1'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForMethod(
'UndefinedMethod',
'Psalm\Bodger::find2'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForMethod(
'UndefinedMethod',
'Psalm\Badger::find2'
)
);
$this->assertSame(
2019-05-03 23:12:20 +02:00
null,
$config->getReportingLevelForProperty(
'UndefinedMethod',
'Psalm\Bodger::$find3'
)
);
$this->assertSame(
2019-05-03 23:12:20 +02:00
null,
$config->getReportingLevelForProperty(
'UndefinedMethod',
'Psalm\Bodger::$find4'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForMethod(
'UndefinedFunction',
'fooBar'
)
);
$this->assertSame(
'suppress',
$config->getReportingLevelForMethod(
'UndefinedFunction',
'foobar'
)
);
2016-12-30 02:07:42 +01:00
}
2017-01-13 20:07:23 +01:00
/**
* @return void
*/
public function testAllPossibleIssues()
{
$all_possible_handlers = implode(
' ',
array_map(
2017-01-13 20:14:24 +01:00
/**
* @param string $issue_name
2017-05-27 02:16:18 +02:00
*
2017-01-13 20:14:24 +01:00
* @return string
*/
function ($issue_name) {
return '<' . $issue_name . ' errorLevel="suppress" />' . "\n";
},
2019-05-03 21:29:44 +02:00
\Psalm\Config\IssueHandler::getAllIssueTypes()
)
);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
<issueHandlers>
' . $all_possible_handlers . '
</issueHandlers>
</psalm>'
)
);
}
/**
2017-05-27 02:16:18 +02:00
*
2019-05-17 00:36:36 +02:00
* @return void
*/
public function testImpossibleIssue()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('This element is not expected');
$this->expectException(\Psalm\Exception\ConfigException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
<issueHandlers>
<ImpossibleIssue errorLevel="suppress" />
</issueHandlers>
</psalm>'
)
);
}
/**
2017-05-27 02:16:18 +02:00
*
2019-05-17 00:36:36 +02:00
* @return void
*/
public function testRequireVoidReturnTypeExists()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('MissingReturnType');
$this->expectException(\Psalm\Exception\CodeException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm
requireVoidReturnType="true">
<projectFiles>
<directory name="src" />
</projectFiles>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$this->addFile(
$file_path,
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
'<?php
function foo() {}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testDoNotRequireVoidReturnTypeExists()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm
requireVoidReturnType="false">
<projectFiles>
<directory name="src" />
</projectFiles>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$this->addFile(
$file_path,
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
'<?php
function foo() {}'
);
$this->analyzeFile($file_path, new Context());
}
2017-02-13 05:59:33 +01:00
/**
* @return void
*/
public function testMethodCallMemoize()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm memoizeMethodCallResults="true">
<projectFiles>
<directory name="src" />
</projectFiles>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
class A {
function getFoo() : ?Foo {
return rand(0, 1) ? new Foo : null;
}
}
class Foo {
2018-07-03 18:45:29 +02:00
function getBar() : ?Bar {
return rand(0, 1) ? new Bar : null;
}
}
class Bar {
public function bat() : void {}
};
$a = new A();
if ($a->getFoo()) {
2018-07-03 18:45:29 +02:00
if ($a->getFoo()->getBar()) {
$a->getFoo()->getBar()->bat();
}
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testThing()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
<mockClasses>
<class name="MyMockClass" />
</mockClasses>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
class MyMockClass {}
$a = new MyMockClass();
$a->foo($b = 5);
echo $b;'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testExitFunctions()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<exitFunctions>
<function name="leave" />
<function name="Foo\namespacedLeave" />
<function name="Foo\Bar::staticLeave" />
</exitFunctions>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
namespace {
function leave() : void {
exit();
}
function mightLeave() : string {
if (rand(0, 1)) {
leave();
} else {
return "here";
}
}
function mightLeaveWithNamespacedFunction() : string {
if (rand(0, 1)) {
\Foo\namespacedLeave();
} else {
return "here";
}
}
function mightLeaveWithStaticMethod() : string {
if (rand(0, 1)) {
Foo\Bar::staticLeave();
} else {
return "here";
}
}
}
namespace Foo {
function namespacedLeave() : void {
exit();
}
class Bar {
public static function staticLeave() : void {
exit();
}
}
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testAllowedEchoFunction()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm></psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
echo "hello";'
);
$this->analyzeFile($file_path, new Context());
}
/**
2019-03-23 19:27:54 +01:00
*
* @return void
*/
public function testForbiddenEchoFunctionViaFunctions()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('ForbiddenCode');
$this->expectException(\Psalm\Exception\CodeException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<forbiddenFunctions>
<function name="echo" />
</forbiddenFunctions>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
echo "hello";'
);
$this->analyzeFile($file_path, new Context());
}
/**
2019-03-23 19:27:54 +01:00
*
* @return void
*/
public function testForbiddenEchoFunctionViaFlag()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('ForbiddenEcho');
$this->expectException(\Psalm\Exception\CodeException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm forbidEcho="true"></psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
echo "hello";'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testAllowedVarExportFunction()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm></psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
$a = [1, 2, 3];
var_export($a);'
);
$this->analyzeFile($file_path, new Context());
}
/**
2019-03-23 19:27:54 +01:00
*
2019-05-17 00:36:36 +02:00
* @return void
*/
public function testForbiddenVarExportFunction()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('ForbiddenCode');
$this->expectException(\Psalm\Exception\CodeException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<forbiddenFunctions>
<function name="var_export" />
</forbiddenFunctions>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
$a = [1, 2, 3];
var_export($a);'
);
$this->analyzeFile($file_path, new Context());
}
/**
2019-03-23 19:27:54 +01:00
*
* @return void
*/
public function testValidThrowInvalidCatch()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('InvalidCatch');
$this->expectException(\Psalm\Exception\CodeException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<issueHandlers>
<InvalidThrow>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidThrow>
</issueHandlers>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
interface I {}
class E extends Exception implements I {}
function foo() : void {
throw new E();
}
function handleThrow(I $e) : void {
echo "about to throw";
throw $e;
}
try {
foo();
} catch (I $e) {
handleThrow($e);
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
2019-03-23 19:27:54 +01:00
*
* @return void
*/
public function testInvalidThrowValidCatch()
{
2019-05-17 00:36:36 +02:00
$this->expectExceptionMessage('InvalidThrow');
$this->expectException(\Psalm\Exception\CodeException::class);
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<issueHandlers>
<InvalidCatch>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidCatch>
</issueHandlers>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
interface I {}
class E extends Exception implements I {}
function foo() : void {
throw new E();
}
function handleThrow(I $e) : void {
echo "about to throw";
throw $e;
}
try {
foo();
} catch (I $e) {
handleThrow($e);
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testValidThrowValidCatch()
{
2018-11-11 18:01:14 +01:00
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<issueHandlers>
<InvalidCatch>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidCatch>
<InvalidThrow>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidThrow>
</issueHandlers>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
interface I {}
class E extends Exception implements I {}
function foo() : void {
throw new E();
}
function handleThrow(I $e) : void {
echo "about to throw";
throw $e;
}
try {
foo();
} catch (I $e) {
handleThrow($e);
}'
);
$this->analyzeFile($file_path, new Context());
}
2017-02-13 05:59:33 +01:00
/**
* @return void
*/
public function testTemplatedFiles()
{
foreach (['1.xml', '2.xml', '3.xml', '4.xml', '5.xml', '6.xml', '7.xml', '8.xml'] as $file_name) {
Config::loadFromXMLFile(
2019-05-10 00:58:30 +02:00
realpath(dirname(__DIR__, 2) . '/assets/config_levels/' . $file_name),
dirname(__DIR__, 2)
);
2017-02-13 05:59:33 +01:00
}
}
2019-03-06 00:08:41 +01:00
/**
* @return void
*/
public function testGlobals()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
2019-03-06 00:08:41 +01:00
'<?xml version="1.0"?>
<psalm>
<globals>
<var name="glob1" type="string" />
<var name="glob2" type="array{str:string}" />
<var name="glob3" type="ns\Clazz" />
<var name="glob4" type="string|null" />
2019-03-06 00:14:07 +01:00
<var name="_GET" type="array{str:string}" />
2019-03-06 00:08:41 +01:00
</globals>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
namespace {
ord($glob1);
ord($glob2["str"]);
$glob3->func();
2019-03-06 00:14:07 +01:00
ord($_GET["str"]);
2019-03-06 00:08:41 +01:00
assert($glob4 !== null);
ord($glob4);
function example1(): void {
global $glob1, $glob2, $glob3, $glob4;
ord($glob1);
ord($glob2["str"]);
$glob3->func();
ord($glob4);
2019-03-06 00:14:07 +01:00
ord($_GET["str"]);
2019-03-06 00:08:41 +01:00
}
$glob1 = 0;
error_reporting($glob1);
2019-03-06 00:14:07 +01:00
$_GET["str"] = 0;
error_reporting($_GET["str"]);
2019-03-06 00:08:41 +01:00
function example2(): void {
global $glob1, $glob2, $glob3;
error_reporting($glob1);
ord($glob2["str"]);
$glob3->func();
2019-03-06 00:14:07 +01:00
ord($_GET["str"]);
2019-03-06 00:08:41 +01:00
}
}
namespace ns {
ord($glob1);
ord($glob2["str"]);
$glob3->func();
2019-03-06 00:14:07 +01:00
ord($_GET["str"]);
2019-03-06 00:08:41 +01:00
class Clazz {
public function func(): void {}
}
function example3(): void {
global $glob1, $glob2, $glob3;
ord($glob1);
ord($glob2["str"]);
$glob3->func();
2019-03-06 00:14:07 +01:00
ord($_GET["str"]);
2019-03-06 00:08:41 +01:00
}
}
namespace ns2 {
/** @psalm-suppress InvalidGlobal */
global $glob1, $glob2, $glob3;
ord($glob1);
ord($glob2["str"]);
$glob3->func();
2019-03-11 14:54:41 +01:00
}
namespace {
ord($glob1 ?? "str");
ord($_GET["str"] ?? "str");
2019-03-14 01:15:29 +01:00
function example4(): void {
global $glob1;
ord($glob1 ?? "str");
ord($_GET["str"] ?? "str");
}
2019-03-06 00:08:41 +01:00
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testIgnoreExceptions()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm checkForThrowsDocblock="true" checkForThrowsInGlobalScope="true">
<ignoreExceptions>
<class name="Exc1" />
<class name="Exc2" onlyGlobalScope="true" />
<classAndDescendants name="Exc3" />
<classAndDescendants name="Exc4" onlyGlobalScope="true" />
</ignoreExceptions>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
class Exc1 extends Exception {}
class Exc2 extends Exception {}
class Exc3 extends Exception {}
class Exc4 extends Exception {}
throw new Exc1();
throw new Exc2();
throw new Exc3();
throw new Exc4();
function example() : void {
throw new Exc1();
throw new Exc3();
}'
);
$this->analyzeFile($file_path, new Context());
}
2019-05-17 00:36:36 +02:00
public function testNotIgnoredException() : void
{
2019-05-17 00:36:36 +02:00
$this->expectException(\Psalm\Exception\CodeException::class);
$this->expectExceptionMessage('MissingThrowsDocblock');
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
2019-05-10 00:58:30 +02:00
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm checkForThrowsDocblock="true" checkForThrowsInGlobalScope="true">
<ignoreExceptions>
<class name="Exc1" />
<class name="Exc2" onlyGlobalScope="true" />
<classAndDescendants name="Exc3" />
<classAndDescendants name="Exc4" onlyGlobalScope="true" />
</ignoreExceptions>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
class Exc2 extends Exception {}
function example() : void {
throw new Exc2();
}'
);
$this->analyzeFile($file_path, new Context());
}
2019-06-05 06:00:42 +02:00
/**
* @return void
*/
public function testGetPossiblePsr4Path()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
Config::loadFromXML(
dirname(__DIR__, 2),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
<directory name="tests" />
</projectFiles>
</psalm>'
)
);
$config = $this->project_analyzer->getConfig();
$classloader = new \Composer\Autoload\ClassLoader();
$classloader->addPsr4(
'Psalm\\',
[
dirname(__DIR__, 2)
. DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR
. 'src' . DIRECTORY_SEPARATOR . 'Psalm'
]
);
$classloader->addPsr4(
'Psalm\\Tests\\',
[
dirname(__DIR__, 2)
. DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR
. 'tests'
]
);
$config->setComposerClassLoader($classloader);
$this->assertSame(
dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Psalm' . DIRECTORY_SEPARATOR . 'Foo.php',
$config->getPotentialComposerFilePathForClassLike("Psalm\\Foo")
);
$this->assertSame(
dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'Foo.php',
$config->getPotentialComposerFilePathForClassLike("Psalm\\Tests\\Foo")
);
}
2016-12-29 16:24:10 +01:00
}