diff --git a/config.xsd b/config.xsd index 2668b8252..3083e5931 100644 --- a/config.xsd +++ b/config.xsd @@ -68,6 +68,7 @@ + diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index 4288cc77c..9fae1d4de 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -275,6 +275,17 @@ Set the php version psalm should assume when checking and/or fixing the project. This can be overridden on the command-line using the `--php-version=` flag which takes the highest precedence over both the `phpVersion` setting and the version derived from `composer.json`. +#### skipChecksOnUnresolvableIncludes +```xml + +``` + +When `true`, Psalm will skip checking classes, variables and functions after it comes across an `include` or `require` it cannot resolve. This allows code to reference functions and classes unknown to Psalm. + +For backwards compatibility, this defaults to `true`, but if you do not rely on dynamically generated includes to cause classes otherwise unknown to psalm to come into existence, it's recommended you set this to `false` in order to reliably detect errors that would be fatal to PHP at runtime. + ### Running Psalm #### autoloader diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 2e3a8f3d4..7ac577b48 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -291,6 +291,11 @@ class Config */ public $use_phpdoc_property_without_magic_or_parent = false; + /** + * @var bool + */ + public $skip_checks_on_unresolvable_includes = true; + /** * @var bool */ @@ -765,6 +770,7 @@ class Config 'ensureArrayStringOffsetsExist' => 'ensure_array_string_offsets_exist', 'ensureArrayIntOffsetsExist' => 'ensure_array_int_offsets_exist', 'reportMixedIssues' => 'show_mixed_issues', + 'skipChecksOnUnresolvableIncludes' => 'skip_checks_on_unresolvable_includes' ]; foreach ($booleanAttributes as $xmlName => $internalName) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php index 0a452364a..f08f69079 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php @@ -160,6 +160,11 @@ class IncludeAnalyzer $global_context ); } catch (\Psalm\Exception\UnpreparedAnalysisException $e) { + if ($config->skip_checks_on_unresolvable_includes) { + $context->check_classes = false; + $context->check_variables = false; + $context->check_functions = false; + } } foreach ($include_file_analyzer->getRequiredFilePaths() as $required_file_path) { @@ -200,6 +205,12 @@ class IncludeAnalyzer } } + if ($config->skip_checks_on_unresolvable_includes) { + $context->check_classes = false; + $context->check_variables = false; + $context->check_functions = false; + } + return null; } diff --git a/tests/IncludeTest.php b/tests/IncludeTest.php index 3c7a0d3c9..d63a99639 100644 --- a/tests/IncludeTest.php +++ b/tests/IncludeTest.php @@ -37,6 +37,7 @@ class IncludeTest extends TestCase } $config = $codebase->config; + $config->skip_checks_on_unresolvable_includes = true; foreach ($error_levels as $error_level) { $config->setCustomErrorLevel($error_level, \Psalm\Config::REPORT_SUPPRESS); @@ -83,6 +84,7 @@ class IncludeTest extends TestCase } $config = $codebase->config; + $config->skip_checks_on_unresolvable_includes = false; $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); @@ -569,6 +571,25 @@ class IncludeTest extends TestCase getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php', ], ], + 'undefinedMethodAfterInvalidRequire' => [ + 'files' => [ + getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [ + getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + ], + ], + ]; }