diff --git a/config.xsd b/config.xsd index 2f3c6f059..373bb2094 100644 --- a/config.xsd +++ b/config.xsd @@ -144,6 +144,7 @@ + diff --git a/docs/issues.md b/docs/issues.md index 1f85d1807..e36a4f2a9 100644 --- a/docs/issues.md +++ b/docs/issues.md @@ -180,6 +180,17 @@ class A {} class A {} ``` +### DuplicateMethod + +Emitted when a method is defined twice + +```php +class A { + public function foo() {} + public function foo() {} +} +``` + ### DuplicateParam Emitted when a function has a param defined twice diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index f5ff851cf..5c7c37503 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -497,7 +497,13 @@ class Config } if (isset($config_xml['autoloader'])) { - $config->autoloader = (string) $config_xml['autoloader']; + $autoloader_path = $config->base_dir . DIRECTORY_SEPARATOR . $config_xml['autoloader']; + + if (!file_exists($autoloader_path)) { + throw new ConfigException('Cannot locate config schema'); + } + + $config->autoloader = realpath($autoloader_path); } if (isset($config_xml['cacheDirectory'])) { @@ -1331,7 +1337,7 @@ class Config if ($this->autoloader) { // do this in a separate method so scope does not leak - $this->requireAutoloader($this->base_dir . DIRECTORY_SEPARATOR . $this->autoloader); + $this->requireAutoloader($this->autoloader); $this->collectPredefinedConstants(); $this->collectPredefinedFunctions(); diff --git a/src/Psalm/Internal/Visitor/ReflectorVisitor.php b/src/Psalm/Internal/Visitor/ReflectorVisitor.php index 754d99a63..68b0df840 100644 --- a/src/Psalm/Internal/Visitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/Visitor/ReflectorVisitor.php @@ -21,6 +21,7 @@ use Psalm\Exception\IncorrectDocblockException; use Psalm\Exception\TypeParseTreeException; use Psalm\FileSource; use Psalm\Issue\DuplicateClass; +use Psalm\Issue\DuplicateMethod; use Psalm\Issue\DuplicateParam; use Psalm\Issue\InvalidDocblock; use Psalm\Issue\MisplacedRequiredParam; @@ -835,7 +836,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse * @param PhpParser\Node\FunctionLike $stmt * @param bool $fake_method in the case of @method annotations we do something a little strange * - * @return FunctionLikeStorage + * @return FunctionLikeStorage|false */ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_method = false) { @@ -889,7 +890,25 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse if (isset($class_storage->methods[strtolower($stmt->name->name)])) { if (!$this->codebase->register_stub_files) { - throw new \InvalidArgumentException('Cannot re-register ' . $function_id); + $duplicate_method_storage = $class_storage->methods[strtolower($stmt->name->name)]; + + if (IssueBuffer::accepts( + new DuplicateMethod( + 'Method ' . $function_id . ' has already been defined' + . ($duplicate_method_storage->location + ? ' in ' . $duplicate_method_storage->location->file_path + : ''), + new CodeLocation($this->file_scanner, $stmt, null, true) + ) + )) { + // fall through + } + + $this->file_storage->has_visitor_issues = true; + + $duplicate_method_storage->has_visitor_issues = true; + + return false; } $storage = $class_storage->methods[strtolower($stmt->name->name)]; diff --git a/src/Psalm/Issue/DuplicateMethod.php b/src/Psalm/Issue/DuplicateMethod.php new file mode 100644 index 000000000..8ce64ef50 --- /dev/null +++ b/src/Psalm/Issue/DuplicateMethod.php @@ -0,0 +1,6 @@ +reportIssueInFile($issue_type, $e->getFilePath())) { + if (!$e instanceof DuplicateClass + && !$e instanceof DuplicateMethod + && !$config->reportIssueInFile($issue_type, $e->getFilePath()) + ) { return false; } diff --git a/tests/ClassStringTest.php b/tests/ClassStringTest.php index 805ff3bd1..9b781cb14 100644 --- a/tests/ClassStringTest.php +++ b/tests/ClassStringTest.php @@ -288,6 +288,17 @@ class ClassStringTest extends TestCase use T; }' ], + 'refineStringToClassString' => [ + 'file_provider->registerFile($file_path, $contents); } + $this->expectException('\Psalm\Exception\CodeException'); + $this->expectExceptionMessageRegexp('/\b' . preg_quote($error_message, '/') . '\b/'); + $codebase->reloadFiles($this->project_analyzer, array_keys($end_files)); foreach ($end_files as $file_path => $_) { $codebase->addFilesToAnalyze([$file_path => $file_path]); } - $codebase->scanFiles(); - - $this->expectException('\Psalm\Exception\CodeException'); - $this->expectExceptionMessageRegexp('/\b' . preg_quote($error_message, '/') . '\b/'); - $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); } @@ -556,6 +554,46 @@ class ErrorAfterUpdateTest extends \Psalm\Tests\TestCase ], 'error_message' => 'PropertyNotSetInConstructor' ], + 'duplicateClass' => [ + 'file_stages' => [ + [ + getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'DuplicateClass' + ], + 'duplicateMethod' => [ + 'file_stages' => [ + [ + getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'DuplicateMethod' + ], ]; } } diff --git a/tests/performance/diff_parse_large_file.php b/tests/performance/diff_parse_large_file.php index 83ee6d6fc..5539670fa 100644 --- a/tests/performance/diff_parse_large_file.php +++ b/tests/performance/diff_parse_large_file.php @@ -2,7 +2,7 @@ ini_set('display_startup_errors', '1'); ini_set('html_errors', '1'); -ini_set('memory_limit', '-1'); +ini_set('memory_limit', '4G'); error_reporting(E_ALL); gc_disable(); @@ -44,3 +44,7 @@ Psalm\Internal\Provider\StatementsProvider::parseStatements($b); $diff_2 = microtime(true) - $time; echo 'Full parsing: ' . number_format($diff_2, 4) . "\n"; + +echo strlen($a); + +Psalm\Internal\Diff\FileDiffer::getDiff($a, '');