From 7354fe8ce08bd018998344cf5127cdbb56f5fedb Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Wed, 5 Jun 2024 20:04:23 +0300 Subject: [PATCH 1/5] solution prototype: quickFix for functions and methods; draft for properties --- src/linter/block_linter.go | 80 +++++++++++++++++++++++++++++++++++++- src/linter/quickfix.go | 17 ++++++++ src/linter/report.go | 10 +++++ src/linter/root_checker.go | 51 ++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go index bd7bc368..074b5b96 100644 --- a/src/linter/block_linter.go +++ b/src/linter/block_linter.go @@ -3,6 +3,7 @@ package linter import ( "bytes" "fmt" + "github.com/VKCOM/noverify/src/phpdoc" "strings" "github.com/VKCOM/noverify/src/constfold" @@ -32,6 +33,8 @@ func (b *blockLinter) enterNode(n ir.Node) { case *ir.ClassStmt: b.checkClass(n) + case *ir.ClassMethodStmt: + println("") case *ir.FunctionCallExpr: b.checkFunctionCall(n) @@ -207,15 +210,90 @@ func (b *blockLinter) checkUnaryPlus(n *ir.UnaryPlusExpr) { b.report(n, LevelWarning, "strangeCast", "Unary plus with non-constant expression, possible type cast, use an explicit cast to int or float instead of using the unary plus") } +func (b *blockLinter) checkMethodTypeHint(method *ir.ClassMethodStmt) { + for _, comment := range method.Doc.Parsed { + var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) + if !ok { + continue + } + + if typeContainer.Name() != "param" { + continue + } + + var typeParam = typeContainer.Type.Source + + for _, param := range method.Params { + var typedParam, ok = param.(*ir.Parameter) + if ok { + var variable = typedParam.Variable + + // maybe we don`t need it and order the same as param + if variable.Name != typeContainer.Var[1:] { + continue + } + + var paramType, ok = typedParam.VariableType.(*ir.Name) + if paramType != nil && ok { + //TODO: quickFix -> remove @param from typeHint + break + } + + if !types.IsTrivial(typeContainer.Type.Source) && !types.IsClass(typeContainer.Type.Source) { + continue + } + + //TODO: quickFix -> remove @param from typeHint + + //quickFix -> add type to param + var varDollar = typeContainer.Var + var variableWithType = typeParam + " " + varDollar + b.walker.report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) + b.walker.r.addQuickFix("implicitParamType", b.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) + } + } + } +} + func (b *blockLinter) checkClass(class *ir.ClassStmt) { const classMethod = 0 const classOtherMember = 1 var members = make([]int, 0, len(class.Stmts)) for _, stmt := range class.Stmts { - switch stmt.(type) { + switch value := stmt.(type) { case *ir.ClassMethodStmt: members = append(members, classMethod) + b.checkMethodTypeHint(value) + case *ir.PropertyListStmt: + for _, comment := range value.Doc.Parsed { + var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) + if !ok { + continue + } + + if typeContainer.Name() != "var" { + continue + } + // var typeHintType = typeContainer.Type.Source + + for _, NodeProperty := range value.Properties { + var typedProperty, ok = NodeProperty.(*ir.PropertyStmt) + if !ok { + continue + } + + var variable = typedProperty.Variable + + var propertyType, okCast = value.Type.(*ir.Name) + // if ok we have type and should equal it + if okCast { + + } + println(propertyType, variable) + } + } + members = append(members, classOtherMember) default: members = append(members, classOtherMember) } diff --git a/src/linter/quickfix.go b/src/linter/quickfix.go index ef83aa8a..153992fb 100644 --- a/src/linter/quickfix.go +++ b/src/linter/quickfix.go @@ -3,6 +3,7 @@ package linter import ( "bytes" "fmt" + "github.com/VKCOM/noverify/src/phpdoc" "github.com/VKCOM/noverify/src/ir" "github.com/VKCOM/noverify/src/quickfix" @@ -47,6 +48,22 @@ func (g *QuickFixGenerator) NullForNotNullableProperty(prop *ir.PropertyStmt) qu } } +func (g *QuickFixGenerator) FunctionParamTypeReplacementFromTypeHint(prop *ir.SimpleVar, variableWithType string) quickfix.TextEdit { + return quickfix.TextEdit{ + StartPos: prop.Position.StartPos, + EndPos: prop.Position.EndPos, + Replacement: variableWithType, + } +} + +func (g *QuickFixGenerator) RemoveParamTypeHint(prop *phpdoc.TypeVarCommentPart) quickfix.TextEdit { + return quickfix.TextEdit{ + StartPos: int(prop.Type.Expr.Begin), + EndPos: int(prop.Type.Expr.End), + Replacement: "", + } +} + func (g *QuickFixGenerator) GetType(node ir.Node, isFunctionName, nodeText string, isNegative bool) quickfix.TextEdit { pos := ir.GetPosition(node) diff --git a/src/linter/report.go b/src/linter/report.go index d1914da3..edeb1318 100644 --- a/src/linter/report.go +++ b/src/linter/report.go @@ -16,6 +16,16 @@ const ( func addBuiltinCheckers(reg *CheckersRegistry) { allChecks := []CheckerInfo{ + { + Name: "implicitParamType", + Default: true, + Quickfix: true, + Comment: `Report implicitly specifying the type of a parameter.`, + Before: `/* @param $str string */ +function test($str){}`, + After: `function test(string $str){}`, + }, + { Name: "stripTags", Default: true, diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go index 149b767d..836d76ee 100644 --- a/src/linter/root_checker.go +++ b/src/linter/root_checker.go @@ -55,6 +55,56 @@ func newRootChecker(walker *rootWalker, quickfix *QuickFixGenerator) *rootChecke return c } +func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { + for _, comment := range fun.Doc.Parsed { + var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) + if !ok { + continue + } + + if typeContainer.Name() != "param" { + continue + } + + var typeParam = typeContainer.Type.Source + + for _, param := range fun.Params { + var typedParam, ok = param.(*ir.Parameter) + if ok { + var variable = typedParam.Variable + + // maybe we don`t need it and order the same as param + if variable.Name != typeContainer.Var[1:] { + continue + } + + var paramType, ok = typedParam.VariableType.(*ir.Name) + if paramType != nil && ok { + //TODO: quickFix -> remove @param from typeHint + break + } + + converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), r.normalizer.KPHP(), typeContainer.Type) + if cap(converted.Types) > 1 { + continue + } + + if !types.IsTrivial(converted.Types[0].Elem) && !types.IsClass(converted.Types[0].Elem) { + continue + } + + //TODO: quickFix -> remove @param from typeHint + + //quickFix -> add type to param + var varDollar = typeContainer.Var + var variableWithType = typeParam + " " + varDollar + r.walker.Report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) + r.walker.addQuickFix("implicitParamType", r.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) + } + } + } +} + func (r *rootChecker) CheckFunction(fun *ir.FunctionStmt) bool { r.CheckKeywordCase(fun, "function") @@ -87,6 +137,7 @@ func (r *rootChecker) CheckFunction(fun *ir.FunctionStmt) bool { r.walker.handleFuncStmts(funcParams.params, nil, fun.Stmts, sc) + r.CheckFunctionTypeHint(fun) return false } From 7bb3c80e5d2f31b0df1cd59ec72c6d1cb13f9e80 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Thu, 6 Jun 2024 17:23:36 +0300 Subject: [PATCH 2/5] implementation for property and methods --- src/linter/block_linter.go | 77 ++++++++++++++++++++++---------------- src/linter/root_checker.go | 6 +-- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go index 074b5b96..53ff8bbc 100644 --- a/src/linter/block_linter.go +++ b/src/linter/block_linter.go @@ -235,7 +235,7 @@ func (b *blockLinter) checkMethodTypeHint(method *ir.ClassMethodStmt) { var paramType, ok = typedParam.VariableType.(*ir.Name) if paramType != nil && ok { - //TODO: quickFix -> remove @param from typeHint + // TODO: quickFix -> remove @param from typeHint break } @@ -243,9 +243,7 @@ func (b *blockLinter) checkMethodTypeHint(method *ir.ClassMethodStmt) { continue } - //TODO: quickFix -> remove @param from typeHint - - //quickFix -> add type to param + // TODO: quickFix -> remove @param from typeHint var varDollar = typeContainer.Var var variableWithType = typeParam + " " + varDollar b.walker.report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) @@ -255,6 +253,47 @@ func (b *blockLinter) checkMethodTypeHint(method *ir.ClassMethodStmt) { } } +func (b *blockLinter) checkPropertyTypeHint(property *ir.PropertyListStmt) { + for _, comment := range property.Doc.Parsed { + var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) + if !ok { + continue + } + + if typeContainer.Name() != "var" { + continue + } + + for _, NodeProperty := range property.Properties { + var typedProperty, ok = NodeProperty.(*ir.PropertyStmt) + if !ok { + continue + } + + var variable = typedProperty.Variable + var typeHintType = typeContainer.Type.Source + var propertyType, okCast = property.Type.(*ir.Name) + + if okCast { + if propertyType.Value == typeHintType { + // TODO: should delete doctype param + break + } + } + + if !types.IsTrivial(typeHintType) && !types.IsClass(typeHintType) { + continue + } + + // TODO: quickFix -> remove @param from typeHint + var varDollar = "$" + variable.Name + var variableWithType = typeHintType + " " + varDollar + b.walker.report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) + b.walker.r.addQuickFix("implicitParamType", b.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) + } + } +} + func (b *blockLinter) checkClass(class *ir.ClassStmt) { const classMethod = 0 const classOtherMember = 1 @@ -263,36 +302,10 @@ func (b *blockLinter) checkClass(class *ir.ClassStmt) { for _, stmt := range class.Stmts { switch value := stmt.(type) { case *ir.ClassMethodStmt: - members = append(members, classMethod) b.checkMethodTypeHint(value) + members = append(members, classMethod) case *ir.PropertyListStmt: - for _, comment := range value.Doc.Parsed { - var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) - if !ok { - continue - } - - if typeContainer.Name() != "var" { - continue - } - // var typeHintType = typeContainer.Type.Source - - for _, NodeProperty := range value.Properties { - var typedProperty, ok = NodeProperty.(*ir.PropertyStmt) - if !ok { - continue - } - - var variable = typedProperty.Variable - - var propertyType, okCast = value.Type.(*ir.Name) - // if ok we have type and should equal it - if okCast { - - } - println(propertyType, variable) - } - } + b.checkPropertyTypeHint(value) members = append(members, classOtherMember) default: members = append(members, classOtherMember) diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go index 836d76ee..588025ae 100644 --- a/src/linter/root_checker.go +++ b/src/linter/root_checker.go @@ -80,7 +80,7 @@ func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { var paramType, ok = typedParam.VariableType.(*ir.Name) if paramType != nil && ok { - //TODO: quickFix -> remove @param from typeHint + // TODO: quickFix -> remove @param from typeHint break } @@ -93,9 +93,7 @@ func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { continue } - //TODO: quickFix -> remove @param from typeHint - - //quickFix -> add type to param + // TODO: quickFix -> remove @param from typeHint var varDollar = typeContainer.Var var variableWithType = typeParam + " " + varDollar r.walker.Report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) From 382f79ca7f553dfe61ba13bcf844f75f1d52fd52 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Thu, 6 Jun 2024 19:19:51 +0300 Subject: [PATCH 3/5] test fix --- src/linter/block_linter.go | 7 +- src/linter/quickfix.go | 2 +- src/linter/root_checker.go | 10 +-- src/tests/checkers/basic_test.go | 2 + src/tests/checkers/closure_test.go | 1 + src/tests/checkers/oop_test.go | 7 +- src/tests/checkers/paramClobber_test.go | 5 +- src/tests/checkers/phpdoc_test.go | 9 ++ src/tests/checkers/printf_test.go | 2 + src/tests/checkers/shape_test.go | 14 ++- src/tests/checkers/typehint_test.go | 6 ++ .../golden/testdata/embeddedrules/golden.txt | 21 +++++ .../golden/testdata/flysystem/golden.txt | 87 +++++++++++++++++++ .../golden/testdata/inflector/golden.txt | 15 ++++ src/tests/golden/testdata/math/golden.txt | 12 +++ src/tests/golden/testdata/mustache/golden.txt | 3 + src/tests/golden/testdata/phpdoc/golden.txt | 27 ++++++ .../testdata/twitter-api-php/golden.txt | 27 ++++++ 18 files changed, 241 insertions(+), 16 deletions(-) diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go index 53ff8bbc..e69b0965 100644 --- a/src/linter/block_linter.go +++ b/src/linter/block_linter.go @@ -3,7 +3,6 @@ package linter import ( "bytes" "fmt" - "github.com/VKCOM/noverify/src/phpdoc" "strings" "github.com/VKCOM/noverify/src/constfold" @@ -12,6 +11,7 @@ import ( "github.com/VKCOM/noverify/src/ir/phpcore" "github.com/VKCOM/noverify/src/linter/autogen" "github.com/VKCOM/noverify/src/meta" + "github.com/VKCOM/noverify/src/phpdoc" "github.com/VKCOM/noverify/src/quickfix" "github.com/VKCOM/noverify/src/solver" "github.com/VKCOM/noverify/src/types" @@ -228,11 +228,6 @@ func (b *blockLinter) checkMethodTypeHint(method *ir.ClassMethodStmt) { if ok { var variable = typedParam.Variable - // maybe we don`t need it and order the same as param - if variable.Name != typeContainer.Var[1:] { - continue - } - var paramType, ok = typedParam.VariableType.(*ir.Name) if paramType != nil && ok { // TODO: quickFix -> remove @param from typeHint diff --git a/src/linter/quickfix.go b/src/linter/quickfix.go index 153992fb..a3902c6d 100644 --- a/src/linter/quickfix.go +++ b/src/linter/quickfix.go @@ -3,9 +3,9 @@ package linter import ( "bytes" "fmt" - "github.com/VKCOM/noverify/src/phpdoc" "github.com/VKCOM/noverify/src/ir" + "github.com/VKCOM/noverify/src/phpdoc" "github.com/VKCOM/noverify/src/quickfix" "github.com/VKCOM/noverify/src/workspace" ) diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go index 588025ae..bc085588 100644 --- a/src/linter/root_checker.go +++ b/src/linter/root_checker.go @@ -73,11 +73,6 @@ func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { if ok { var variable = typedParam.Variable - // maybe we don`t need it and order the same as param - if variable.Name != typeContainer.Var[1:] { - continue - } - var paramType, ok = typedParam.VariableType.(*ir.Name) if paramType != nil && ok { // TODO: quickFix -> remove @param from typeHint @@ -89,6 +84,10 @@ func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { continue } + if converted.Types == nil { + continue + } + if !types.IsTrivial(converted.Types[0].Elem) && !types.IsClass(converted.Types[0].Elem) { continue } @@ -98,6 +97,7 @@ func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { var variableWithType = typeParam + " " + varDollar r.walker.Report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) r.walker.addQuickFix("implicitParamType", r.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) + break } } } diff --git a/src/tests/checkers/basic_test.go b/src/tests/checkers/basic_test.go index 3594f26c..58cfe8e1 100644 --- a/src/tests/checkers/basic_test.go +++ b/src/tests/checkers/basic_test.go @@ -555,6 +555,8 @@ function f1() {} `Void type can only be used as a standalone type for the return type`, `Void type can only be used as a standalone type for the return type`, `Void type can only be used as a standalone type for the return type`, + `Type for $x can be wrote explicitly from typeHint`, + `Type for $y can be wrote explicitly from typeHint`, } test.RunAndMatch() } diff --git a/src/tests/checkers/closure_test.go b/src/tests/checkers/closure_test.go index 647d597d..b4ba0ec6 100644 --- a/src/tests/checkers/closure_test.go +++ b/src/tests/checkers/closure_test.go @@ -66,6 +66,7 @@ function f(callable $s, callable $s1, callable $s2) {} ) test.Expect = []string{ "Lost return type for callable(...), if the function returns nothing, specify void explicitly", + "Type for $s can be wrote explicitly from typeHint", } test.RunAndMatch() } diff --git a/src/tests/checkers/oop_test.go b/src/tests/checkers/oop_test.go index 7e6a4988..f22bb2e9 100644 --- a/src/tests/checkers/oop_test.go +++ b/src/tests/checkers/oop_test.go @@ -765,7 +765,9 @@ func TestStaticResolutionInsideSameClass(t *testing.T) { } func TestStaticResolutionInsideOtherStaticResolution(t *testing.T) { - linttest.SimpleNegativeTest(t, `testProperty; echo $result; }`) + + test.Expect = []string{"Type for $testProperty can be wrote explicitly from typeHint"} + test.RunAndMatch() } func TestInheritanceLoop(t *testing.T) { diff --git a/src/tests/checkers/paramClobber_test.go b/src/tests/checkers/paramClobber_test.go index d7ee55f3..eeec5702 100644 --- a/src/tests/checkers/paramClobber_test.go +++ b/src/tests/checkers/paramClobber_test.go @@ -18,7 +18,8 @@ function f($x, $y) { } func TestParamClobberReferenced(t *testing.T) { - linttest.SimpleNegativeTest(t, `good6['y']->value; } func TestShapeReturn(t *testing.T) { - linttest.SimpleNegativeTest(t, `next->next; `) + test.Expect = []string{ + `Type for $next can be wrote explicitly from typeHint`, + } + test.RunAndMatch() } func TestTuple(t *testing.T) { - linttest.SimpleNegativeTest(t, `good2[0]->value; echo $t->good3[1][0]->value; `) + test.Expect = []string{ + `Type for $good1 can be wrote explicitly from typeHint`, + } + test.RunAndMatch() } diff --git a/src/tests/checkers/typehint_test.go b/src/tests/checkers/typehint_test.go index db14990a..c3103c5a 100644 --- a/src/tests/checkers/typehint_test.go +++ b/src/tests/checkers/typehint_test.go @@ -96,6 +96,12 @@ function f4() { }; } `) + test.Expect = []string{ + `Type for $a can be wrote explicitly from typeHint`, + `Type for $a can be wrote explicitly from typeHint`, + `Type for $a can be wrote explicitly from typeHint`, + `Type for $a can be wrote explicitly from typeHint`, + } test.RunAndMatch() } diff --git a/src/tests/golden/testdata/embeddedrules/golden.txt b/src/tests/golden/testdata/embeddedrules/golden.txt index e0418748..5942f0ad 100644 --- a/src/tests/golden/testdata/embeddedrules/golden.txt +++ b/src/tests/golden/testdata/embeddedrules/golden.txt @@ -67,6 +67,9 @@ WARNING bitwiseOps: Used | bitwise operator over bool operands, perhaps || is in MAYBE callSimplify: Could simplify to array_key_exists('abc', $array) at testdata/embeddedrules/callSimplify.php:7 $_ = in_array('abc', array_keys($array)); // bad ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $array can be wrote explicitly from typeHint at testdata/embeddedrules/callSimplify.php:6 +function in_array_over_array_keys(array $array) { + ^^^^^^ MAYBE callSimplify: Could simplify to $str[$index] at testdata/embeddedrules/callSimplify.php:14 $_ = substr($str, $index, 1); ^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,6 +85,12 @@ MAYBE callSimplify: Could simplify to $array[] = $val at testdata/embeddedrule MAYBE callSimplify: Could simplify to $array[] = 10 at testdata/embeddedrules/callSimplify.php:28 array_push($array, 10); ^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $array can be wrote explicitly from typeHint at testdata/embeddedrules/callSimplify.php:26 +function some_array_push(array $array, int $val) { + ^^^^^^ +WARNING implicitParamType: Type for $val can be wrote explicitly from typeHint at testdata/embeddedrules/callSimplify.php:26 +function some_array_push(array $array, int $val) { + ^^^^^^ WARNING indexingSyntax: a{i} indexing is deprecated since PHP 7.4, use a[i] instead at testdata/embeddedrules/indexingSyntax.php:14 $_ = $a{0}; ^^^^^ @@ -157,6 +166,15 @@ WARNING offBy1: Probably intended to use sizeof-1 as an index at testdata/embedd WARNING offBy1: Probably intended to use count-1 as an index at testdata/embeddedrules/offBy1.php:14 if ($tabs[count($tabs)] == "") { ^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $xs can be wrote explicitly from typeHint at testdata/embeddedrules/offBy1.php:7 +function test_countindex_bad(array $xs, array $tabs) { + ^^^ +WARNING implicitParamType: Type for $tabs can be wrote explicitly from typeHint at testdata/embeddedrules/offBy1.php:7 +function test_countindex_bad(array $xs, array $tabs) { + ^^^ +WARNING implicitParamType: Type for $xs can be wrote explicitly from typeHint at testdata/embeddedrules/offBy1.php:24 +function test_countindex_good(array $xs) { + ^^^ WARNING precedence: == has higher precedence than & at testdata/embeddedrules/precedence.php:4 $_ = 0 == $mask & $x; ^^^^^^^^^^^^^^^ @@ -310,3 +328,6 @@ MAYBE ternarySimplify: Could rewrite as `$x_arr[10] ?? null` at testdata/embed MAYBE ternarySimplify: Could rewrite as `(bool)($flags & SOME_MASK)` at testdata/embeddedrules/ternarySimplify.php:46 sink(($flags & SOME_MASK) ? true : false); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $flags can be wrote explicitly from typeHint at testdata/embeddedrules/ternarySimplify.php:45 +function ternarySimplify_issue540($flags) { + ^^^^^^ diff --git a/src/tests/golden/testdata/flysystem/golden.txt b/src/tests/golden/testdata/flysystem/golden.txt index 9378e752..5f167be6 100644 --- a/src/tests/golden/testdata/flysystem/golden.txt +++ b/src/tests/golden/testdata/flysystem/golden.txt @@ -1,9 +1,45 @@ +WARNING implicitParamType: Type for $pathSeparator can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractAdapter.php:17 + protected $pathSeparator = '/'; + ^^^^^^^^^^^^^^ MAYBE regexpSimplify: May re-write '/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/' as '/^\d{2,4}-\d{2}-\d{2}/' at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:536 return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix'; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ MAYBE callSimplify: Could simplify to $permissions[0] at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:548 return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file'; ^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $connection can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:17 + protected $connection; + ^^^^^^^^^^^ +WARNING implicitParamType: Type for $host can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:22 + protected $host; + ^^^^^ +WARNING implicitParamType: Type for $port can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:27 + protected $port = 21; + ^^^^^ +WARNING implicitParamType: Type for $ssl can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:32 + protected $ssl = false; + ^^^^ +WARNING implicitParamType: Type for $timeout can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:37 + protected $timeout = 90; + ^^^^^^^^ +WARNING implicitParamType: Type for $passive can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:42 + protected $passive = true; + ^^^^^^^^ +WARNING implicitParamType: Type for $separator can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:47 + protected $separator = '/'; + ^^^^^^^^^^ +WARNING implicitParamType: Type for $permPublic can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:57 + protected $permPublic = 0744; + ^^^^^^^^^^^ +WARNING implicitParamType: Type for $permPrivate can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:62 + protected $permPrivate = 0700; + ^^^^^^^^^^^^ +WARNING implicitParamType: Type for $systemType can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:72 + protected $systemType; + ^^^^^^^^^^^ +WARNING implicitParamType: Type for $enableTimestampsOnUnixListings can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:84 + protected $enableTimestampsOnUnixListings = false; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/flysystem/src/Adapter/Ftp.php:134 $this->connection = @ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -22,6 +58,18 @@ MAYBE regexpSimplify: May re-write '/^total [0-9]*$/' as '/^total \d*$/' at te WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/flysystem/src/Adapter/Ftp.php:570 $response = @ftp_raw($this->connection, trim($command)); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $transferMode can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Ftp.php:22 + protected $transferMode = FTP_BINARY; + ^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $recurseManually can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Ftp.php:32 + protected $recurseManually = false; + ^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $utf8 can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Ftp.php:37 + protected $utf8 = false; + ^^^^^ +WARNING implicitParamType: Type for $isPureFtpd can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Ftp.php:64 + protected $isPureFtpd; + ^^^^^^^^^^^ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/flysystem/src/Adapter/Ftpd.php:15 if (@ftp_chdir($this->getConnection(), $path) === true) { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,6 +97,15 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/ MAYBE invalidDocblockType: Void type can only be used as a standalone type for the return type at testdata/flysystem/src/Adapter/Local.php:444 * @return array|void ^^^^^^^^^^ +WARNING implicitParamType: Type for $pathSeparator can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Local.php:47 + protected $pathSeparator = DIRECTORY_SEPARATOR; + ^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $writeFlags can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Local.php:57 + protected $writeFlags; + ^^^^^^^^^^^ +WARNING implicitParamType: Type for $linkHandling can be wrote explicitly from typeHint at testdata/flysystem/src/Adapter/Local.php:62 + private $linkHandling; + ^^^^^^^^^^^^^ WARNING invalidDocblockRef: @see tag refers to unknown symbol League\Flysystem\ReadInterface::readStream at testdata/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php:17 * @see League\Flysystem\ReadInterface::readStream() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -61,6 +118,18 @@ MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\Adapter\Polyfill\Str MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\Adapter\Polyfill\StreamedWritingTrait::update public method at testdata/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php:59 abstract public function update($pash, $contents, Config $config); ^^^^^^ +WARNING implicitParamType: Type for $path can be wrote explicitly from typeHint at testdata/flysystem/src/FileExistsException.php:12 + protected $path; + ^^^^^ +WARNING implicitParamType: Type for $path can be wrote explicitly from typeHint at testdata/flysystem/src/FileNotFoundException.php:12 + protected $path; + ^^^^^ +WARNING implicitParamType: Type for $previous can be wrote explicitly from typeHint at testdata/flysystem/src/FileNotFoundException.php:21 + public function __construct($path, $code = 0, BaseException $previous = null) + ^^^^^ +WARNING implicitParamType: Type for $previous can be wrote explicitly from typeHint at testdata/flysystem/src/FileNotFoundException.php:21 + public function __construct($path, $code = 0, BaseException $previous = null) + ^^^^^ MAYBE typeHint: Specify the type for the parameter $config in PHPDoc, 'array' type hint too generic at testdata/flysystem/src/Filesystem.php:63 public function write($path, $contents, array $config = []) ^^^^^ @@ -85,6 +154,9 @@ MAYBE typeHint: Specify the type for the parameter $config in PHPDoc, 'array' WARNING unused: Variable $e is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/flysystem/src/Handler.php:129 } catch (BadMethodCallException $e) { ^^ +WARNING implicitParamType: Type for $path can be wrote explicitly from typeHint at testdata/flysystem/src/Handler.php:15 + protected $path; + ^^^^^ WARNING unused: Variable $e is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/flysystem/src/MountManager.php:275 } catch (PluginNotFoundException $e) { ^^ @@ -106,6 +178,9 @@ MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\SafeStorage::storeSa MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\SafeStorage::retrieveSafely public method at testdata/flysystem/src/SafeStorage.php:28 public function retrieveSafely($key) ^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $hash can be wrote explicitly from typeHint at testdata/flysystem/src/SafeStorage.php:10 + private $hash; + ^^^^^ MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\UnreadableFileException::forFileInfo public method at testdata/flysystem/src/UnreadableFileException.php:9 public static function forFileInfo(SplFileInfo $fileInfo) ^^^^^^^^^^^ @@ -121,9 +196,21 @@ MAYBE regexpSimplify: May re-write '#^[a-zA-Z]{1}:$#' as '#^[a-zA-Z]:$#' at te MAYBE typeHint: Specify the type for the parameter $entry in PHPDoc, 'array' type hint too generic at testdata/flysystem/src/Util/ContentListingFormatter.php:52 private function addPathInfo(array $entry) ^^^^^^^^^^^ +WARNING implicitParamType: Type for $directory can be wrote explicitly from typeHint at testdata/flysystem/src/Util/ContentListingFormatter.php:15 + private $directory; + ^^^^^^^^^^ +WARNING implicitParamType: Type for $recursive can be wrote explicitly from typeHint at testdata/flysystem/src/Util/ContentListingFormatter.php:20 + private $recursive; + ^^^^^^^^^^ +WARNING implicitParamType: Type for $caseSensitive can be wrote explicitly from typeHint at testdata/flysystem/src/Util/ContentListingFormatter.php:25 + private $caseSensitive; + ^^^^^^^^^^^^^^ WARNING unused: Variable $e is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/flysystem/src/Util/MimeType.php:208 } catch (ErrorException $e) { ^^ MAYBE ternarySimplify: Could rewrite as `static::$extensionToMimeTypeMap[$extension] ?? 'text/plain'` at testdata/flysystem/src/Util/MimeType.php:222 return isset(static::$extensionToMimeTypeMap[$extension]) ^^^^^^^^^^^ +WARNING implicitParamType: Type for $algo can be wrote explicitly from typeHint at testdata/flysystem/src/Util/StreamHasher.php:10 + private $algo; + ^^^^^ diff --git a/src/tests/golden/testdata/inflector/golden.txt b/src/tests/golden/testdata/inflector/golden.txt index 92cc75ea..5494b497 100644 --- a/src/tests/golden/testdata/inflector/golden.txt +++ b/src/tests/golden/testdata/inflector/golden.txt @@ -22,3 +22,18 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/inflector/lib/Doctrine/Common/Inflector/Inflector.php:181 @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in Doctrine Inflector 3.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $pattern can be wrote explicitly from typeHint at testdata/inflector/lib/Doctrine/Inflector/Rules/Pattern.php:12 + private $pattern; + ^^^^^^^^ +WARNING implicitParamType: Type for $regex can be wrote explicitly from typeHint at testdata/inflector/lib/Doctrine/Inflector/Rules/Pattern.php:15 + private $regex; + ^^^^^^ +WARNING implicitParamType: Type for $regex can be wrote explicitly from typeHint at testdata/inflector/lib/Doctrine/Inflector/Rules/Patterns.php:17 + private $regex; + ^^^^^^ +WARNING implicitParamType: Type for $replacement can be wrote explicitly from typeHint at testdata/inflector/lib/Doctrine/Inflector/Rules/Transformation.php:16 + private $replacement; + ^^^^^^^^^^^^ +WARNING implicitParamType: Type for $word can be wrote explicitly from typeHint at testdata/inflector/lib/Doctrine/Inflector/Rules/Word.php:10 + private $word; + ^^^^^ diff --git a/src/tests/golden/testdata/math/golden.txt b/src/tests/golden/testdata/math/golden.txt index ae3f8b9b..525c77a4 100644 --- a/src/tests/golden/testdata/math/golden.txt +++ b/src/tests/golden/testdata/math/golden.txt @@ -1,9 +1,18 @@ WARNING unused: Variable $a is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/math/src/BigDecimal.php:292 [$a, $b] = $this->scaleValues($this, $that); ^^ +WARNING implicitParamType: Type for $value can be wrote explicitly from typeHint at testdata/math/src/BigDecimal.php:28 + private $value; + ^^^^^^ +WARNING implicitParamType: Type for $scale can be wrote explicitly from typeHint at testdata/math/src/BigDecimal.php:37 + private $scale; + ^^^^^^ MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/math/src/BigInteger.php:435 new BigInteger($remainder) ^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $value can be wrote explicitly from typeHint at testdata/math/src/BigInteger.php:32 + private $value; + ^^^^^^ MAYBE ternarySimplify: Could rewrite as `$matches['fractional'] ?? ''` at testdata/math/src/BigNumber.php:90 $fractional = isset($matches['fractional']) ? $matches['fractional'] : ''; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -19,6 +28,9 @@ MAYBE trailingComma: Last element in a multi-line array should have a trailing MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/math/src/Internal/Calculator/NativeCalculator.php:187 (string) $r ^^^^^^^^^^^ +WARNING implicitParamType: Type for $maxDigits can be wrote explicitly from typeHint at testdata/math/src/Internal/Calculator/NativeCalculator.php:28 + private $maxDigits; + ^^^^^^^^^^ ERROR classMembersOrder: Constant UNNECESSARY must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:33 public const UNNECESSARY = 0; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tests/golden/testdata/mustache/golden.txt b/src/tests/golden/testdata/mustache/golden.txt index dfdf00ad..ecd0d711 100644 --- a/src/tests/golden/testdata/mustache/golden.txt +++ b/src/tests/golden/testdata/mustache/golden.txt @@ -172,3 +172,6 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/ WARNING unused: Variable $v is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/mustache/src/Mustache/Template.php:122 foreach ($value as $k => $v) { ^^ +WARNING implicitParamType: Type for $strictCallables can be wrote explicitly from typeHint at testdata/mustache/src/Mustache/Template.php:27 + protected $strictCallables = false; + ^^^^^^^^^^^^^^^^ diff --git a/src/tests/golden/testdata/phpdoc/golden.txt b/src/tests/golden/testdata/phpdoc/golden.txt index ee111c4e..1f395317 100644 --- a/src/tests/golden/testdata/phpdoc/golden.txt +++ b/src/tests/golden/testdata/phpdoc/golden.txt @@ -37,6 +37,9 @@ MAYBE invalidDocblockType: Repeated nullable doesn't make sense at testdata/ph WARNING invalidDocblock: Multiline PHPDoc comment should start with /**, not /* at testdata/phpdoc/phpdoc.php:31 /* ^^ +WARNING implicitParamType: Type for $a can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:34 +function g($a) { + ^^ WARNING invalidDocblockRef: @see tag refers to unknown symbol FooUnExisting at testdata/phpdoc/phpdoc.php:54 * @see FooUnExisting ^^^^^^^^^^^^^ @@ -85,6 +88,30 @@ MAYBE invalidDocblockType: Shape param #1: want key:type, found x at testdata/ WARNING invalidDocblock: Multiline PHPDoc comment should start with /**, not /* at testdata/phpdoc/phpdoc.php:70 /* ^^^^ +WARNING implicitParamType: Type for $a can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for $unexisting can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for $b can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for $e can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for $f can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for $h can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:57 +function f($a, $b, $c, $d, $e, $f, $g, $h, $a1, $b1, $c1, $d1, $e1, $f1, $g1, $h1) { + ^^ +WARNING implicitParamType: Type for $a can be wrote explicitly from typeHint at testdata/phpdoc/phpdoc.php:21 + public $a = 100; + ^^ WARNING invalidDocblock: Multiline PHPDoc comment should start with /**, not /* at testdata/phpdoc/phpdoc.php:31 /* ^^ diff --git a/src/tests/golden/testdata/twitter-api-php/golden.txt b/src/tests/golden/testdata/twitter-api-php/golden.txt index 2c6f30fc..fb010f7a 100644 --- a/src/tests/golden/testdata/twitter-api-php/golden.txt +++ b/src/tests/golden/testdata/twitter-api-php/golden.txt @@ -25,6 +25,33 @@ MAYBE trailingComma: Last element in a multi-line array should have a trailing MAYBE invalidDocblockType: Use int type instead of integer at testdata/twitter-api-php/TwitterAPIExchange.php:404 * @return integer ^^^^^^^ +WARNING implicitParamType: Type for $oauth_access_token can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:20 + private $oauth_access_token; + ^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $oauth_access_token_secret can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:25 + private $oauth_access_token_secret; + ^^^^^^^^^^^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $consumer_key can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:30 + private $consumer_key; + ^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $consumer_secret can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:35 + private $consumer_secret; + ^^^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $getfield can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:45 + private $getfield; + ^^^^^^^^^ +WARNING implicitParamType: Type for $oauth can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:50 + protected $oauth; + ^^^^^^ +WARNING implicitParamType: Type for $url can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:55 + public $url; + ^^^^ +WARNING implicitParamType: Type for $requestMethod can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:60 + public $requestMethod; + ^^^^^^^^^^^^^^ +WARNING implicitParamType: Type for $httpStatusCode can be wrote explicitly from typeHint at testdata/twitter-api-php/TwitterAPIExchange.php:67 + protected $httpStatusCode; + ^^^^^^^^^^^^^^^ MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/twitter-api-php/index.php:10 'consumer_secret' => "" ^^^^^^^^^^^^^^^^^^^^^^^ From ac2b32a92b068cd3aa90ff71b3e3ff354946bf90 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Thu, 6 Jun 2024 19:41:51 +0300 Subject: [PATCH 4/5] new tests for typeHints --- src/tests/checkers/typehint_test.go | 63 +++++++++++++++++++ .../testdata/quickfix/phpTypeHintApply.php | 32 ++++++++++ .../phpTypeHintApply.php.fix.expected | 32 ++++++++++ 3 files changed, 127 insertions(+) create mode 100644 src/tests/golden/testdata/quickfix/phpTypeHintApply.php create mode 100644 src/tests/golden/testdata/quickfix/phpTypeHintApply.php.fix.expected diff --git a/src/tests/checkers/typehint_test.go b/src/tests/checkers/typehint_test.go index c3103c5a..7e6d044b 100644 --- a/src/tests/checkers/typehint_test.go +++ b/src/tests/checkers/typehint_test.go @@ -139,3 +139,66 @@ function f2() { } linttest.RunFilterMatch(test, "typeHint") } + +func TestTypeHintToFunParam(t *testing.T) { + test := linttest.NewSuite(t) + test.AddFile(` Date: Thu, 8 Aug 2024 22:41:09 +0300 Subject: [PATCH 5/5] refactoring prototype --- src/linter/block_linter.go | 88 +---------------------------- src/linter/quickfix.go | 2 +- src/linter/root_checker.go | 88 +++++++++++++---------------- src/tests/checkers/closure_test.go | 1 - src/tests/checkers/typehint_test.go | 11 +--- src/types/map.go | 8 +++ src/types/predicates.go | 4 ++ 7 files changed, 54 insertions(+), 148 deletions(-) diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go index e69b0965..bd7bc368 100644 --- a/src/linter/block_linter.go +++ b/src/linter/block_linter.go @@ -11,7 +11,6 @@ import ( "github.com/VKCOM/noverify/src/ir/phpcore" "github.com/VKCOM/noverify/src/linter/autogen" "github.com/VKCOM/noverify/src/meta" - "github.com/VKCOM/noverify/src/phpdoc" "github.com/VKCOM/noverify/src/quickfix" "github.com/VKCOM/noverify/src/solver" "github.com/VKCOM/noverify/src/types" @@ -33,8 +32,6 @@ func (b *blockLinter) enterNode(n ir.Node) { case *ir.ClassStmt: b.checkClass(n) - case *ir.ClassMethodStmt: - println("") case *ir.FunctionCallExpr: b.checkFunctionCall(n) @@ -210,98 +207,15 @@ func (b *blockLinter) checkUnaryPlus(n *ir.UnaryPlusExpr) { b.report(n, LevelWarning, "strangeCast", "Unary plus with non-constant expression, possible type cast, use an explicit cast to int or float instead of using the unary plus") } -func (b *blockLinter) checkMethodTypeHint(method *ir.ClassMethodStmt) { - for _, comment := range method.Doc.Parsed { - var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) - if !ok { - continue - } - - if typeContainer.Name() != "param" { - continue - } - - var typeParam = typeContainer.Type.Source - - for _, param := range method.Params { - var typedParam, ok = param.(*ir.Parameter) - if ok { - var variable = typedParam.Variable - - var paramType, ok = typedParam.VariableType.(*ir.Name) - if paramType != nil && ok { - // TODO: quickFix -> remove @param from typeHint - break - } - - if !types.IsTrivial(typeContainer.Type.Source) && !types.IsClass(typeContainer.Type.Source) { - continue - } - - // TODO: quickFix -> remove @param from typeHint - var varDollar = typeContainer.Var - var variableWithType = typeParam + " " + varDollar - b.walker.report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) - b.walker.r.addQuickFix("implicitParamType", b.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) - } - } - } -} - -func (b *blockLinter) checkPropertyTypeHint(property *ir.PropertyListStmt) { - for _, comment := range property.Doc.Parsed { - var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) - if !ok { - continue - } - - if typeContainer.Name() != "var" { - continue - } - - for _, NodeProperty := range property.Properties { - var typedProperty, ok = NodeProperty.(*ir.PropertyStmt) - if !ok { - continue - } - - var variable = typedProperty.Variable - var typeHintType = typeContainer.Type.Source - var propertyType, okCast = property.Type.(*ir.Name) - - if okCast { - if propertyType.Value == typeHintType { - // TODO: should delete doctype param - break - } - } - - if !types.IsTrivial(typeHintType) && !types.IsClass(typeHintType) { - continue - } - - // TODO: quickFix -> remove @param from typeHint - var varDollar = "$" + variable.Name - var variableWithType = typeHintType + " " + varDollar - b.walker.report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) - b.walker.r.addQuickFix("implicitParamType", b.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) - } - } -} - func (b *blockLinter) checkClass(class *ir.ClassStmt) { const classMethod = 0 const classOtherMember = 1 var members = make([]int, 0, len(class.Stmts)) for _, stmt := range class.Stmts { - switch value := stmt.(type) { + switch stmt.(type) { case *ir.ClassMethodStmt: - b.checkMethodTypeHint(value) members = append(members, classMethod) - case *ir.PropertyListStmt: - b.checkPropertyTypeHint(value) - members = append(members, classOtherMember) default: members = append(members, classOtherMember) } diff --git a/src/linter/quickfix.go b/src/linter/quickfix.go index a3902c6d..bb45b0e4 100644 --- a/src/linter/quickfix.go +++ b/src/linter/quickfix.go @@ -48,7 +48,7 @@ func (g *QuickFixGenerator) NullForNotNullableProperty(prop *ir.PropertyStmt) qu } } -func (g *QuickFixGenerator) FunctionParamTypeReplacementFromTypeHint(prop *ir.SimpleVar, variableWithType string) quickfix.TextEdit { +func (g *QuickFixGenerator) FunctionParamTypeReplacementFromTypeHint(prop *ir.Parameter, variableWithType string) quickfix.TextEdit { return quickfix.TextEdit{ StartPos: prop.Position.StartPos, EndPos: prop.Position.EndPos, diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go index bc085588..c2b4c671 100644 --- a/src/linter/root_checker.go +++ b/src/linter/root_checker.go @@ -55,54 +55,6 @@ func newRootChecker(walker *rootWalker, quickfix *QuickFixGenerator) *rootChecke return c } -func (r *rootChecker) CheckFunctionTypeHint(fun *ir.FunctionStmt) { - for _, comment := range fun.Doc.Parsed { - var typeContainer, ok = comment.(*phpdoc.TypeVarCommentPart) - if !ok { - continue - } - - if typeContainer.Name() != "param" { - continue - } - - var typeParam = typeContainer.Type.Source - - for _, param := range fun.Params { - var typedParam, ok = param.(*ir.Parameter) - if ok { - var variable = typedParam.Variable - - var paramType, ok = typedParam.VariableType.(*ir.Name) - if paramType != nil && ok { - // TODO: quickFix -> remove @param from typeHint - break - } - - converted := phpdoctypes.ToRealType(r.normalizer.ClassFQNProvider(), r.normalizer.KPHP(), typeContainer.Type) - if cap(converted.Types) > 1 { - continue - } - - if converted.Types == nil { - continue - } - - if !types.IsTrivial(converted.Types[0].Elem) && !types.IsClass(converted.Types[0].Elem) { - continue - } - - // TODO: quickFix -> remove @param from typeHint - var varDollar = typeContainer.Var - var variableWithType = typeParam + " " + varDollar - r.walker.Report(variable, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) - r.walker.addQuickFix("implicitParamType", r.quickfix.FunctionParamTypeReplacementFromTypeHint(variable, variableWithType)) - break - } - } - } -} - func (r *rootChecker) CheckFunction(fun *ir.FunctionStmt) bool { r.CheckKeywordCase(fun, "function") @@ -135,7 +87,6 @@ func (r *rootChecker) CheckFunction(fun *ir.FunctionStmt) bool { r.walker.handleFuncStmts(funcParams.params, nil, fun.Stmts, sc) - r.CheckFunctionTypeHint(fun) return false } @@ -632,9 +583,48 @@ func (r *rootChecker) CheckTypeHintNode(n ir.Node, place string) { }) } +func (r *rootChecker) CheckFuncParamTypeHint(param *ir.Parameter, phpDocParamTypes phpdoctypes.ParamsMap) { + if param.AttrGroups != nil || param.Variadic || param.Variable.Name == "closure" || param.Variable.Name == "referent" || param.Variable.Name == "callback" || types.IsObject(param.Variable.Name) { + return // callback referent newThis value + } + + var phpDocType types.Map + + if phpDocParamType, ok := phpDocParamTypes[param.Variable.Name]; ok { + phpDocType = phpDocParamType.Typ + + var allTypes = phpDocType.GetTypes() + if len(allTypes) > 1 || allTypes == nil { + return + } + + switch paramType := param.VariableType.(type) { + case *ir.Name: + if paramType != nil { + // TODO: quickFix -> remove @param from typeHint + return + } + case *ir.Identifier: + return + } + + for _, typ := range phpDocType.GetTypes() { + if !types.IsTrivial(typ) || types.IsClass(typ) || types.IsArray(typ) || types.IsShape(typ) || typ == "mixed" { + continue + } + + var varDollar = "$" + param.Variable.Name + var variableWithType = typ + " " + varDollar + r.walker.Report(param, LevelWarning, "implicitParamType", "Type for %s can be wrote explicitly from typeHint", varDollar) + r.walker.addQuickFix("implicitParamType", r.quickfix.FunctionParamTypeReplacementFromTypeHint(param, variableWithType)) + } + } +} + func (r *rootChecker) CheckFuncParams(funcName *ir.Identifier, params []ir.Node, funcParams parseFuncParamsResult, phpDocParamTypes phpdoctypes.ParamsMap) { for _, param := range params { r.checkFuncParam(param.(*ir.Parameter)) + r.CheckFuncParamTypeHint(param.(*ir.Parameter), phpDocParamTypes) } r.checkParamsTypeHint(funcName, funcParams, phpDocParamTypes) diff --git a/src/tests/checkers/closure_test.go b/src/tests/checkers/closure_test.go index b4ba0ec6..647d597d 100644 --- a/src/tests/checkers/closure_test.go +++ b/src/tests/checkers/closure_test.go @@ -66,7 +66,6 @@ function f(callable $s, callable $s1, callable $s2) {} ) test.Expect = []string{ "Lost return type for callable(...), if the function returns nothing, specify void explicitly", - "Type for $s can be wrote explicitly from typeHint", } test.RunAndMatch() } diff --git a/src/tests/checkers/typehint_test.go b/src/tests/checkers/typehint_test.go index 7e6d044b..b8258d2e 100644 --- a/src/tests/checkers/typehint_test.go +++ b/src/tests/checkers/typehint_test.go @@ -96,12 +96,6 @@ function f4() { }; } `) - test.Expect = []string{ - `Type for $a can be wrote explicitly from typeHint`, - `Type for $a can be wrote explicitly from typeHint`, - `Type for $a can be wrote explicitly from typeHint`, - `Type for $a can be wrote explicitly from typeHint`, - } test.RunAndMatch() } @@ -179,7 +173,7 @@ private $foo2 ; * @param $str string * @param $str2 string */ -function test($str, string $str){ +function test($str, string $str2){ } } @@ -192,11 +186,8 @@ function test2($str){ test.Expect = []string{ `Specify the access modifier for \SimpleClass::test method explicitly`, `Non-canonical order of variable and type `, - `@param for non-existing argument $str2`, `Non-canonical order of variable and type`, - `Type for $foo2 can be wrote explicitly from typeHint`, `Type for $str can be wrote explicitly from typeHint`, - `Type for $str2 can be wrote explicitly from typeHint`, `Non-canonical order of variable and type`, `Type for $str can be wrote explicitly from typeHint`, } diff --git a/src/types/map.go b/src/types/map.go index f942d81b..77345a99 100644 --- a/src/types/map.go +++ b/src/types/map.go @@ -37,6 +37,14 @@ type Map struct { m map[string]struct{} } +func (m Map) GetTypes() []string { + var types []string + for typ := range m.m { + types = append(types, typ) + } + return types +} + // IsPrecise reports whether the type set represented by the map is precise // enough to perform typecheck-like analysis. // diff --git a/src/types/predicates.go b/src/types/predicates.go index d0b4034f..d4e8de4c 100644 --- a/src/types/predicates.go +++ b/src/types/predicates.go @@ -12,6 +12,10 @@ func IsShape(s string) bool { return strings.HasPrefix(s, `\shape$`) } +func IsObject(s string) bool { + return strings.HasPrefix(s, `object`) +} + func IsClosure(s string) bool { return strings.HasPrefix(s, `\Closure`) }