diff --git a/composer.json b/composer.json index 2b7200a89..0b18e94b5 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,6 @@ "symfony/console": "^3.3||^4.0", "amphp/amp": "^2.1", "amphp/byte-stream": "^1.5", - "phpmyadmin/sql-parser": "^5.0", "sebastian/diff": "^3.0" }, "bin": ["psalm", "psalter", "psalm-language-server", "psalm-plugin"], @@ -47,7 +46,8 @@ "phpunit/phpunit": "^7.5 || ^8.0", "squizlabs/php_codesniffer": "3.4.0", "bamarni/composer-bin-plugin": "^1.2", - "psalm/plugin-phpunit": "^0.6" + "psalm/plugin-phpunit": "^0.6", + "phpmyadmin/sql-parser": "^5.0" }, "suggest": { "ext-igbinary": "^2.0.5" diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 24f35a7c3..a2cc6a46a 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -320,6 +320,13 @@ class Config */ public $after_statement_checks = []; + /** + * Static methods to be called after method checks have completed + * + * @var class-string[] + */ + public $string_interpreters = []; + /** * Static methods to be called after classlike exists checks have completed * diff --git a/src/Psalm/Plugin/Hook/StringInterpreterInterface.php b/src/Psalm/Plugin/Hook/StringInterpreterInterface.php new file mode 100644 index 000000000..2c41e6187 --- /dev/null +++ b/src/Psalm/Plugin/Hook/StringInterpreterInterface.php @@ -0,0 +1,13 @@ +config->after_analysis[$handler] = $handler; } + + if (is_subclass_of($handler, Hook\StringInterpreterInterface::class)) { + $this->config->string_interpreters[$handler] = $handler; + } } } diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 9680b8f61..9f73e2035 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -29,7 +29,6 @@ use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TObjectWithProperties; use Psalm\Type\Atomic\TResource; use Psalm\Type\Atomic\TSingleLetter; -use Psalm\Type\Atomic\TSqlSelectString; use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TTrue; use Psalm\Type\Atomic\TVoid; @@ -933,16 +932,10 @@ abstract class Type if ($value !== null) { $config = \Psalm\Config::getInstance(); - if ($config->parse_sql && stripos($value, 'select ') !== false) { - try { - $parser = new \PhpMyAdmin\SqlParser\Parser($value); - - if (!$parser->errors) { - $type = new TSqlSelectString($value); - } - } catch (\Throwable $e) { - if (strlen($value) < $config->max_string_length) { - $type = new TLiteralString($value); + if ($config->string_interpreters) { + foreach ($config->string_interpreters as $string_interpreter) { + if ($type = $string_interpreter::getTypeFromValue($value)) { + break; } } } diff --git a/tests/Config/Plugin/Hook/SqlStringProvider.php b/tests/Config/Plugin/Hook/SqlStringProvider.php new file mode 100644 index 000000000..50da7ffc0 --- /dev/null +++ b/tests/Config/Plugin/Hook/SqlStringProvider.php @@ -0,0 +1,25 @@ +errors) { + return new StringProvider\TSqlSelectString($value); + } + } catch (\Throwable $e) { + // fall through + } + } + + return null; + } +} diff --git a/src/Psalm/Type/Atomic/TSqlSelectString.php b/tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php similarity index 76% rename from src/Psalm/Type/Atomic/TSqlSelectString.php rename to tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php index 65ad7cadd..3523cd072 100644 --- a/src/Psalm/Type/Atomic/TSqlSelectString.php +++ b/tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php @@ -1,7 +1,8 @@ value . ')'; } - /** * @return bool */ diff --git a/tests/Config/Plugin/SqlStringProviderPlugin.php b/tests/Config/Plugin/SqlStringProviderPlugin.php new file mode 100644 index 000000000..d7bca1c61 --- /dev/null +++ b/tests/Config/Plugin/SqlStringProviderPlugin.php @@ -0,0 +1,18 @@ +registerHooksFromClass(Hook\SqlStringProvider::class); + } +} diff --git a/tests/Config/PluginTest.php b/tests/Config/PluginTest.php index 4003ab527..1a3117011 100644 --- a/tests/Config/PluginTest.php +++ b/tests/Config/PluginTest.php @@ -618,6 +618,46 @@ class PluginTest extends \Psalm\Tests\TestCase $this->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']->getTypes() as $type) { + $this->assertInstanceOf(\Psalm\Test\Config\Plugin\Hook\StringProvider\TSqlSelectString::class, $type); + } + } + /** * * @return void diff --git a/tests/ValueTest.php b/tests/ValueTest.php index 6ea6845c6..dfad1aca6 100644 --- a/tests/ValueTest.php +++ b/tests/ValueTest.php @@ -486,17 +486,6 @@ class ValueTest extends TestCase 'assertions' => [], 'error_levels' => ['MissingParamType', 'MixedAssignment'], ], - 'sqlTypes' => [ - ' [ - '$a===' => 'sql-select-string(select * from foo)', - '$b===' => 'string(select * from)', - '$c===' => 'sql-select-string(select * from foo where i = :i)', - ], - ], 'numericToStringComparison' => [ '