1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +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",
"symfony/console": "^3.0||^4.0",
"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"],
"autoload": {

View File

@ -876,7 +876,8 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$stmt->name->name,
$stmt->args,
$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) {
ConstFetchAnalyzer::analyze($statements_analyzer, $stmt, $context);
} 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) {
// do nothing
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst) {

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ interface MethodReturnTypeProviderInterface
/**
* @param array<PhpParser\Node\Arg> $call_args
* @param ?array<Type\Union> $template_type_parameters
* @return ?Type\Union
*/
public static function getMethodReturnType(
@ -25,6 +26,7 @@ interface MethodReturnTypeProviderInterface
string $method_name_lowercase,
array $call_args,
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\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;
@ -923,9 +924,23 @@ abstract class Type
*/
public static function getString($value = null)
{
$type = null;
if ($value !== null) {
if (stripos($value, 'select ') === 0) {
$parser = new \PhpMyAdmin\SqlParser\Parser($value);
if (!$parser->errors) {
$type = new TSqlSelectString($value);
}
}
if (!$type && strlen($value) < 50) {
$type = new TLiteralString($value);
} else {
}
}
if (!$type) {
$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,
array $call_args,
Context $context,
CodeLocation $code_location
CodeLocation $code_location,
array $templated_type_parameters = null
) {
return Type::getString();
}

View File

@ -61,10 +61,21 @@ trait ValidCodeAnalysisTestTrait
$actual_vars = [];
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 ($exact) {
$actual_vars[$var . '==='] = $context->vars_in_scope[$var]->getId();
} else {
$actual_vars[$var] = (string)$context->vars_in_scope[$var];
}
}
}
$this->assertSame($assertions, $actual_vars);
}

View File

@ -468,6 +468,17 @@ class ValueTest extends TestCase
'assertions' => [],
'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)',
]
],
];
}