1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Add special type for SQL select strings for plugins to consume

This commit is contained in:
Brown 2019-03-07 14:56:18 -05:00
parent 0b74c6a6e7
commit 5beb26659e
12 changed files with 92 additions and 14 deletions

View File

@ -23,7 +23,8 @@
"webmozart/path-util": "^2.3", "webmozart/path-util": "^2.3",
"symfony/console": "^3.0||^4.0", "symfony/console": "^3.0||^4.0",
"amphp/amp": "^2.1", "amphp/amp": "^2.1",
"amphp/byte-stream": "^1.5" "amphp/byte-stream": "^1.5",
"phpmyadmin/sql-parser": "^4.0"
}, },
"bin": ["psalm", "psalter", "psalm-language-server", "psalm-plugin"], "bin": ["psalm", "psalter", "psalm-language-server", "psalm-plugin"],
"autoload": { "autoload": {

View File

@ -876,7 +876,8 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$stmt->name->name, $stmt->name->name,
$stmt->args, $stmt->args,
$context, $context,
new CodeLocation($statements_analyzer->getSource(), $stmt->name) new CodeLocation($statements_analyzer->getSource(), $stmt->name),
$lhs_type_part instanceof TGenericObject ? $lhs_type_part->type_params : null
); );
} }

View File

@ -119,7 +119,7 @@ class ExpressionAnalyzer
} elseif ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { } elseif ($stmt instanceof PhpParser\Node\Expr\ConstFetch) {
ConstFetchAnalyzer::analyze($statements_analyzer, $stmt, $context); ConstFetchAnalyzer::analyze($statements_analyzer, $stmt, $context);
} elseif ($stmt instanceof PhpParser\Node\Scalar\String_) { } elseif ($stmt instanceof PhpParser\Node\Scalar\String_) {
$stmt->inferredType = Type::getString(strlen($stmt->value) < 50 ? $stmt->value : null); $stmt->inferredType = Type::getString($stmt->value);
} elseif ($stmt instanceof PhpParser\Node\Scalar\EncapsedStringPart) { } elseif ($stmt instanceof PhpParser\Node\Scalar\EncapsedStringPart) {
// do nothing // do nothing
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst) { } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst) {

View File

@ -20,7 +20,8 @@ class MethodReturnTypeProvider
* string, * string,
* array<PhpParser\Node\Arg>, * array<PhpParser\Node\Arg>,
* Context, * Context,
* CodeLocation * CodeLocation,
* ?array<Type\Union>=
* ) : ?Type\Union> * ) : ?Type\Union>
* > * >
*/ */
@ -68,7 +69,8 @@ class MethodReturnTypeProvider
* string, * string,
* array<PhpParser\Node\Arg>, * array<PhpParser\Node\Arg>,
* Context, * Context,
* CodeLocation * CodeLocation,
* ?array<Type\Union>=
* ) : ?Type\Union $c * ) : ?Type\Union $c
* *
* @return void * @return void
@ -85,6 +87,7 @@ class MethodReturnTypeProvider
/** /**
* @param array<PhpParser\Node\Arg> $call_args * @param array<PhpParser\Node\Arg> $call_args
* @param ?array<Type\Union> $template_type_parameters
* @return ?Type\Union * @return ?Type\Union
*/ */
public function getReturnType( public function getReturnType(
@ -93,7 +96,8 @@ class MethodReturnTypeProvider
string $method_name, string $method_name,
array $call_args, array $call_args,
Context $context, Context $context,
CodeLocation $code_location CodeLocation $code_location,
array $template_type_parameters = null
) { ) {
foreach (self::$handlers[strtolower($fq_classlike_name)] as $class_handler) { foreach (self::$handlers[strtolower($fq_classlike_name)] as $class_handler) {
$result = $class_handler( $result = $class_handler(
@ -102,7 +106,8 @@ class MethodReturnTypeProvider
strtolower($method_name), strtolower($method_name),
$call_args, $call_args,
$context, $context,
$code_location $code_location,
$template_type_parameters
); );
if ($result) { if ($result) {

View File

@ -25,7 +25,8 @@ class DomNodeAppendChild implements \Psalm\Plugin\Hook\MethodReturnTypeProviderI
string $method_name_lowercase, string $method_name_lowercase,
array $call_args, array $call_args,
Context $context, Context $context,
CodeLocation $code_location CodeLocation $code_location,
array $template_type_parameters = null
) { ) {
if ($method_name_lowercase === 'appendchild' if ($method_name_lowercase === 'appendchild'
&& isset($call_args[0]->value->inferredType) && isset($call_args[0]->value->inferredType)

View File

@ -25,7 +25,8 @@ class SimpleXmlElementAsXml implements \Psalm\Plugin\Hook\MethodReturnTypeProvid
string $method_name_lowercase, string $method_name_lowercase,
array $call_args, array $call_args,
Context $context, Context $context,
CodeLocation $code_location CodeLocation $code_location,
array $template_type_parameters = null
) { ) {
if ($method_name_lowercase === 'asxml' if ($method_name_lowercase === 'asxml'
&& !count($call_args) && !count($call_args)

View File

@ -17,6 +17,7 @@ interface MethodReturnTypeProviderInterface
/** /**
* @param array<PhpParser\Node\Arg> $call_args * @param array<PhpParser\Node\Arg> $call_args
* @param ?array<Type\Union> $template_type_parameters
* @return ?Type\Union * @return ?Type\Union
*/ */
public static function getMethodReturnType( public static function getMethodReturnType(
@ -25,6 +26,7 @@ interface MethodReturnTypeProviderInterface
string $method_name_lowercase, string $method_name_lowercase,
array $call_args, array $call_args,
Context $context, Context $context,
CodeLocation $code_location CodeLocation $code_location,
array $template_type_parameters = null
); );
} }

View File

@ -29,6 +29,7 @@ use Psalm\Type\Atomic\TObject;
use Psalm\Type\Atomic\TObjectWithProperties; use Psalm\Type\Atomic\TObjectWithProperties;
use Psalm\Type\Atomic\TResource; use Psalm\Type\Atomic\TResource;
use Psalm\Type\Atomic\TSingleLetter; use Psalm\Type\Atomic\TSingleLetter;
use Psalm\Type\Atomic\TSqlSelectString;
use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTrue; use Psalm\Type\Atomic\TTrue;
use Psalm\Type\Atomic\TVoid; use Psalm\Type\Atomic\TVoid;
@ -923,9 +924,23 @@ abstract class Type
*/ */
public static function getString($value = null) public static function getString($value = null)
{ {
$type = null;
if ($value !== null) { if ($value !== null) {
$type = new TLiteralString($value); if (stripos($value, 'select ') === 0) {
} else { $parser = new \PhpMyAdmin\SqlParser\Parser($value);
if (!$parser->errors) {
$type = new TSqlSelectString($value);
}
}
if (!$type && strlen($value) < 50) {
$type = new TLiteralString($value);
}
}
if (!$type) {
$type = new TString(); $type = new TString();
} }

View File

@ -0,0 +1,29 @@
<?php
namespace Psalm\Type\Atomic;
class TSqlSelectString extends TLiteralString
{
/**
* @return string
*/
public function getKey()
{
return 'sql-select-string';
}
/**
* @return string
*/
public function getId()
{
return 'sql-select-string(' . $this->value . ')';
}
/**
* @return bool
*/
public function canBeFullyExpressedInPhp()
{
return false;
}
}

View File

@ -78,7 +78,8 @@ class FooMethodProvider implements
string $method_name, string $method_name,
array $call_args, array $call_args,
Context $context, Context $context,
CodeLocation $code_location CodeLocation $code_location,
array $templated_type_parameters = null
) { ) {
return Type::getString(); return Type::getString();
} }

View File

@ -61,8 +61,19 @@ trait ValidCodeAnalysisTestTrait
$actual_vars = []; $actual_vars = [];
foreach ($assertions as $var => $_) { foreach ($assertions as $var => $_) {
$exact = false;
if ($var && strpos($var, '===') === strlen($var) - 3) {
$var = substr($var, 0, -3);
$exact = true;
}
if (isset($context->vars_in_scope[$var])) { if (isset($context->vars_in_scope[$var])) {
$actual_vars[$var] = (string)$context->vars_in_scope[$var]; if ($exact) {
$actual_vars[$var . '==='] = $context->vars_in_scope[$var]->getId();
} else {
$actual_vars[$var] = (string)$context->vars_in_scope[$var];
}
} }
} }

View File

@ -468,6 +468,17 @@ class ValueTest extends TestCase
'assertions' => [], 'assertions' => [],
'error_levels' => ['MissingParamType', 'MixedAssignment'], 'error_levels' => ['MissingParamType', 'MixedAssignment'],
], ],
'sqlTypes' => [
'<?php
$a = "select * from foo";
$b = "select * from";
$c = "select * from foo where i = :i";',
'assertions' => [
'$a===' => 'sql-select-string(select * from foo)',
'$b===' => 'string(select * from)',
'$c===' => 'sql-select-string(select * from foo where i = :i)',
]
],
]; ];
} }