nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: VKCOM/noverify
Pull request #721 (2020-08-28 10:58:50)

linter: assume int|string array key types in foreach stmt
To avoid types map allocation in every foreach statement,
we'll use a global immutable `int|string` types map for this.

This particular types information is useful for warning
messages, so the user gets {int|string}->method instead of
{mixed}->method and it will be easier to see that the
linter does infer the types and it's not a false positive.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/linter/block.go b/src/linter/block.go
index 27d8159..bd470fb 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -52,6 +52,11 @@ const (
 	varStatic
 )
 
+// arrayKeyType is an universal PHP array key type.
+// In PHP, all array keys are converted to either int or string,
+// so we can always assume that `int|string` is good enough.
+var arrayKeyType = meta.NewTypesMap("int|string").Immutable()
+
 // BlockWalker is used to process function/method contents.
 type BlockWalker struct {
 	ctx *blockContext
@@ -1098,7 +1103,7 @@ func (b *BlockWalker) handleForeach(s *ir.ForeachStmt) bool {
 				b.handleVariableNode(s.Variable, meta.NewTypesMap(meta.WrapElemOf(typ)), "foreach_value")
 			})
 
-			b.handleVariableNode(s.Key, meta.TypesMap{}, "foreach_key")
+			b.handleVariableNode(s.Key, arrayKeyType, "foreach_key")
 			if list, ok := s.Variable.(*ir.ListExpr); ok {
 				for _, item := range list.Items {
 					b.handleVariableNode(item.Val, meta.TypesMap{}, "foreach_value")


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 7f05eff..6dd95d1 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -46,6 +46,28 @@ func init() {
 	})
 }
 
+func TestExprTypeForeachKey(t *testing.T) {
+	code := `<?php
+$xs = [[1], [2]];
+
+foreach ($xs as $k => $ys) {
+  exprtype($k, 'int|string');
+
+  foreach ($xs as $k2 => $_) {
+    exprtype($k2, 'int|string');
+    $k2 = 10;
+    exprtype($k2, 'precise int');
+  }
+
+  exprtype($k, 'int|string');
+
+  $v = $xs ? $k : [1];
+  exprtype($v, 'int|int[]|string');
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func TestExprTypeRecursiveType1(t *testing.T) {
 	code := `<?php
 class Feed {
Repository: VKCOM/noverify
Pull request #737 (2020-08-31 11:56:41)

ir: add AnonClassExpr node kind
Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/ir/get_node_kind.go b/src/ir/get_node_kind.go
index 84b85ad..6abb67a 100644
--- a/src/ir/get_node_kind.go
+++ b/src/ir/get_node_kind.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 )
 
+// TODO: generate this code automatically.
+
 type NodeKind int
 
 const (
@@ -22,6 +24,7 @@ const (
 	KindAssignReference
 	KindAssignShiftLeft
 	KindAssignShiftRight
+	KindAnonClassExpr
 	KindBitwiseAndExpr
 	KindBitwiseOrExpr
 	KindBitwiseXorExpr
@@ -193,6 +196,8 @@ func GetNodeKind(n Node) NodeKind {
 		return KindAssignShiftLeft
 	case *AssignShiftRight:
 		return KindAssignShiftRight
+	case *AnonClassExpr:
+		return KindAnonClassExpr
 	case *BitwiseAndExpr:
 		return KindBitwiseAndExpr
 	case *BitwiseOrExpr:


Test patch:
diff --git a/src/tests/golden/testdata/embeddedrules/lib.php b/src/tests/golden/testdata/embeddedrules/lib.php
index 69ca933..c37a321 100644
--- a/src/tests/golden/testdata/embeddedrules/lib.php
+++ b/src/tests/golden/testdata/embeddedrules/lib.php
@@ -6,3 +6,11 @@ function random() {
   global $secret;
   return $secret;
 }
+
+$_ = new class {
+  // This is a bad code, but we're skipping the anon class
+  // bodies right now.
+  public function f(string $s) {
+    return strpos('/', $s);
+  }
+};
\ No newline at end of file
Repository: VKCOM/noverify
Pull request #742 (2020-09-01 15:21:12)

lintdoc: render Quickfix flag in the output
Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/lintdoc/lintdoc.go b/src/lintdoc/lintdoc.go
index d614b91..48043b1 100644
--- a/src/lintdoc/lintdoc.go
+++ b/src/lintdoc/lintdoc.go
@@ -22,7 +22,7 @@ var templateShort = template.Must(template.New("short").Parse(`
 `))
 
 var templateFull = template.Must(template.New("short").Parse(`
-{{- .Name}} checker documentation
+{{- .Name}} checker documentation{{if .Quickfix}} (auto fix available){{end}}
 
 {{.Comment}}
 


Test patch:
diff --git a/src/lintdoc/lintdoc_test.go b/src/lintdoc/lintdoc_test.go
index 568929e..9539c25 100644
--- a/src/lintdoc/lintdoc_test.go
+++ b/src/lintdoc/lintdoc_test.go
@@ -62,4 +62,20 @@ Compliant code:
 class Foo {
   public function __construct($v) { $this->v = $v; }
 }`)
+
+	runTest(linter.CheckInfo{
+		Name:     "ternarySimplify",
+		Comment:  `Report ternary expressions that can be simplified.`,
+		Before:   `$x ? $x : $y`,
+		After:    `$x ?: $y`,
+		Quickfix: true,
+	}, `ternarySimplify checker documentation (auto fix available)
+
+Report ternary expressions that can be simplified.
+
+Non-compliant code:
+$x ? $x : $y
+
+Compliant code:
+$x ?: $y`)
 }
Repository: VKCOM/noverify
Pull request #744 (2020-09-01 16:14:05)

ir/irfmt: fix array item expr printing with unpack=true
See https://github.com/z7zmey/php-parser/pull/116

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>

quasilyte: We also had swapped have/want arguments diff/equal calls, so I fixed that as well.

Patch:
diff --git a/src/ir/irfmt/printer.go b/src/ir/irfmt/printer.go
index aa04e86..3fc1d98 100644
--- a/src/ir/irfmt/printer.go
+++ b/src/ir/irfmt/printer.go
@@ -835,6 +835,10 @@ func (p *PrettyPrinter) printExprArrayDimFetch(n *ir.ArrayDimFetchExpr) {
 }
 
 func (p *PrettyPrinter) printExprArrayItem(n *ir.ArrayItemExpr) {
+	if n.Unpack {
+		io.WriteString(p.w, "...")
+	}
+
 	if n.Key != nil {
 		p.Print(n.Key)
 		io.WriteString(p.w, " => ")


Test patch:
diff --git a/src/ir/irfmt/printer_test.go b/src/ir/irfmt/printer_test.go
index f0c352d..0c259a4 100644
--- a/src/ir/irfmt/printer_test.go
+++ b/src/ir/irfmt/printer_test.go
@@ -250,6 +250,9 @@ LBL;
 
 		`$_ = array('Hello' => $world, 2 => &$var, $var);
 
+`,
+		`[...$x, $y, ...$z];
+
 `,
 
 		`~$var;
@@ -885,7 +888,7 @@ func runPrinterTest(t *testing.T, code string) {
 	want := o.String()
 	have := code
 
-	if !cmp.Equal(want, have) {
-		t.Errorf("results mismatch (+ have) (- want): %s", cmp.Diff(want, have))
+	if !cmp.Equal(have, want) {
+		t.Errorf("results mismatch (+ have) (- want): %s", cmp.Diff(have, want))
 	}
 }
Repository: VKCOM/noverify
Pull request #789 (2020-09-17 14:28:42)

solver: added type inference for magic constants


Patch:
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index 5c23db4..c0a536c 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -323,6 +323,9 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	case *ir.ShiftLeftExpr, *ir.ShiftRightExpr:
 		return meta.PreciseIntType
 	case *ir.ClassConstFetchExpr:
+		if n.ConstantName.Value == "class" {
+			return meta.PreciseStringType
+		}
 		className, ok := GetClassName(cs, n.Class)
 		if !ok {
 			return meta.TypesMap{}
@@ -374,6 +377,9 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 		return ExprTypeLocalCustom(sc, cs, n.Expr, custom)
 	case *ir.ClosureExpr:
 		return meta.NewTypesMap(`\Closure`)
+
+	case *ir.MagicConstant:
+		return magicConstantType(n)
 	}
 
 	return meta.TypesMap{}
@@ -387,3 +393,10 @@ func ExprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	}
 	return res
 }
+
+func magicConstantType(n *ir.MagicConstant) meta.TypesMap {
+	if n.Value == "__LINE__" {
+		return meta.PreciseIntType
+	}
+	return meta.PreciseStringType
+}


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 8d285b7..c90dc7e 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2510,6 +2510,42 @@ function f() {
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestMagicConstants(t *testing.T) {
+	code := `<?php
+class Foo {}
+
+function f() {
+	$line = __LINE__;
+	exprtype($line, "precise int");
+
+	$file = __FILE__;
+	exprtype($file, "precise string");
+
+	$dir = __DIR__;
+	exprtype($dir, "precise string");
+
+	$function = __FUNCTION__;
+	exprtype($function, "precise string");
+
+	$class = __CLASS__;
+	exprtype($class, "precise string");
+
+	$trait = __TRAIT__;
+	exprtype($trait, "precise string");
+
+	$method = __METHOD__;
+	exprtype($method, "precise string");
+
+	$namespace = __NAMESPACE__;
+	exprtype($namespace, "precise string");
+
+	$className = Foo::class;
+	exprtype($className, "precise string");
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func runExprTypeTest(t *testing.T, params *exprTypeTestParams) {
 	meta.ResetInfo()
 	if params.stubs != "" {
Repository: VKCOM/noverify
Pull request #792 (2020-09-18 12:20:29)

linter: fixed type inference for callback arguments if they have type hint
Due to the introduction of a new
`parseFuncArgsForCallback` function
for processing callback arguments,
we lost the type hints processing,
since an early exit from `parseFuncArgs`
is done and does not reach the type
hints processing. Therefore, we have
to process type hints explicitly.


Patch:
diff --git a/src/linter/root.go b/src/linter/root.go
index 9829621..703130a 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -1370,10 +1370,17 @@ func (d *RootWalker) parseTypeNode(n ir.Node) (typ meta.TypesMap, ok bool) {
 func (d *RootWalker) callbackParamByIndex(param ir.Node, argType meta.TypesMap) meta.FuncParam {
 	p := param.(*ir.Parameter)
 	v := p.Variable
+
 	var typ meta.TypesMap
-	argType.Iterate(func(t string) {
-		typ = typ.AppendString(meta.WrapElemOf(t))
-	})
+	tp, ok := d.parseTypeNode(p.VariableType)
+	if ok {
+		typ = tp
+	} else {
+		argType.Iterate(func(t string) {
+			typ = typ.AppendString(meta.WrapElemOf(t))
+		})
+	}
+
 	arg := meta.FuncParam{
 		IsRef: p.ByRef,
 		Name:  v.Name,


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index c90dc7e..cbc8d27 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2303,6 +2303,40 @@ array_reduce($foo_array, function($carry, $item) {
   exprtype($item, "\Foo");
 });
 
+// mixed array, but function args with type hints
+$mixed_arr = [];
+usort($mixed_arr, function(Foo $a, Foo $b) {
+  exprtype($a, "\Foo");
+  exprtype($b, "\Foo");
+});
+
+$mixed_arr_2 = [];
+usort($mixed_arr_2, function(int $a, int $b) {
+  exprtype($a, "int");
+  exprtype($b, "int");
+});
+
+// mixed array, but not all function args have type hints
+$mixed_arr_3 = [];
+usort($mixed_arr_3, function(Foo $a, $b) {
+  exprtype($a, "\Foo");
+  exprtype($b, "mixed");
+});
+
+// non mixed array, but function args with type hints
+$non_mixed_arr = [1, 2, 3];
+usort($non_mixed_arr, function(int $a, int $b) {
+  exprtype($a, "int");
+  exprtype($b, "int");
+});
+
+// non mixed array, but not all function args have type hints
+$non_mixed_arr_2 = [new Foo, new Foo, new Foo];
+usort($non_mixed_arr_2, function(Foo $a, $b) {
+  exprtype($a, "\Foo");
+  exprtype($b, "\Foo");
+});
+
 some_function_without_model(function($b) {
   exprtype($b, "mixed");
 }, $d);
Repository: VKCOM/noverify
Pull request #801 (2020-10-01 19:27:17)

checkers: fix regexpsimplify handling of named capture alt forms
Named capture always printed like ?P<name>, but it can have 2
more forms: ?<name> and ?'name'.

The quoted format is extra tricky as we always suggest '-quoted
replacements; we give up if we find this form to avoid invalid
suggestions. It's possible to suggest "-quoted literals in
the future, but it may require more changes (and tests).

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/linter/regexpsimplify.go b/src/linter/regexpsimplify.go
index a220519..2fec615 100644
--- a/src/linter/regexpsimplify.go
+++ b/src/linter/regexpsimplify.go
@@ -19,6 +19,8 @@ type regexpSimplifier struct {
 	// escapedDelims counts how many times we've seen escaped
 	// pattern delimiter inside the current regexp.
 	escapedDelims int
+
+	hasSingleQuotes bool
 }
 
 func (c *regexpSimplifier) simplifyRegexp(pat string) string {
@@ -53,6 +55,10 @@ func (c *regexpSimplifier) simplify(pass int, pat string) string {
 
 	c.walk(c.re.Expr)
 
+	if c.hasSingleQuotes {
+		return "" // Conservative, but safe
+	}
+
 	// Since '/' delimiter is so ubiquitous, tolerate 1 '\/' without
 	// suggesting an alternative delimiter.
 	minDelims := 2
@@ -128,9 +134,24 @@ func (c *regexpSimplifier) walk(e syntax.Expr) {
 		c.walk(e.Args[0])
 		out.WriteString(")")
 	case syntax.OpNamedCapture:
-		out.WriteString("(?P<")
-		out.WriteString(e.Args[1].Value)
-		out.WriteString(">")
+		out.WriteString("(?")
+		switch e.Form {
+		case syntax.FormNamedCaptureAngle:
+			out.WriteString("<")
+			out.WriteString(e.Args[1].Value)
+			out.WriteString(">")
+		case syntax.FormNamedCaptureQuote:
+			// We can't suggest single quote replacement with these.
+			// Collect this fact.
+			c.hasSingleQuotes = true
+			out.WriteString("'")
+			out.WriteString(e.Args[1].Value)
+			out.WriteString("'")
+		default:
+			out.WriteString("P<")
+			out.WriteString(e.Args[1].Value)
+			out.WriteString(">")
+		}
 		c.walk(e.Args[0])
 		out.WriteString(")")
 


Test patch:
diff --git a/src/tests/checkers/regexpsimplify_test.go b/src/tests/checkers/regexpsimplify_test.go
index 1935c8c..c501a0d 100644
--- a/src/tests/checkers/regexpsimplify_test.go
+++ b/src/tests/checkers/regexpsimplify_test.go
@@ -6,6 +6,23 @@ import (
 	"github.com/VKCOM/noverify/src/linttest"
 )
 
+func TestRESimplifyNamedCaptureForms(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.LoadStubs = []string{`stubs/phpstorm-stubs/pcre/pcre.php`}
+	test.AddFile(`<?php
+function f($s) {
+  preg_match('~(?P<abc>[0-9])~', $s);
+  preg_match('~(?<abc>[0-9])~', $s);
+  preg_match("~(?'abc'[0-9])~", $s); // Ignore due to the FormNamedCaptureQuote
+}
+`)
+	test.Expect = []string{
+		`May re-write '~(?P<abc>[0-9])~' as '~(?P<abc>\d)~'`,
+		`May re-write '~(?<abc>[0-9])~' as '~(?<abc>\d)~'`,
+	}
+	test.RunAndMatch()
+}
+
 func TestRESimplifyMixed(t *testing.T) {
 	test := linttest.NewSuite(t)
 	test.LoadStubs = []string{`stubs/phpstorm-stubs/pcre/pcre.php`}
Repository: VKCOM/noverify
Pull request #802 (2020-10-01 19:38:44)

constfold: added support for magic constants in constant folding, as …
…well as the `dirname(__FILE__)` function


Patch:
diff --git a/src/constfold/eval.go b/src/constfold/eval.go
index 6f122d9..4732a74 100644
--- a/src/constfold/eval.go
+++ b/src/constfold/eval.go
@@ -1,6 +1,7 @@
 package constfold
 
 import (
+	"path/filepath"
 	"strconv"
 
 	"github.com/VKCOM/noverify/src/ir"
@@ -94,6 +95,42 @@ func Eval(st *meta.ClassParseState, e ir.Node) meta.ConstValue {
 
 	case *ir.String:
 		return meta.ConstValue{Value: e.Value, Type: meta.String}
+
+	case *ir.FunctionCallExpr:
+		// dirname(__FILE__)
+		if !meta.NameNodeEquals(e.Function, `dirname`) {
+			return meta.UnknownValue
+		}
+		if len(e.Args) == 0 {
+			return meta.UnknownValue
+		}
+		arg, ok := e.Arg(0).Expr.(*ir.MagicConstant)
+		if !ok || arg.Value != "__FILE__" {
+			return meta.UnknownValue
+		}
+		return meta.NewStringConstant(filepath.Dir(st.CurrentFile))
+
+	case *ir.MagicConstant:
+		switch e.Value {
+		case "__LINE__":
+			return meta.NewIntConst(int64(e.Position.StartLine))
+		case "__FILE__":
+			return meta.NewStringConstant(st.CurrentFile)
+		case "__DIR__":
+			return meta.NewStringConstant(filepath.Dir(st.CurrentFile))
+		case "__FUNCTION__":
+			return meta.NewStringConstant(st.CurrentFunction)
+		case "__METHOD__":
+			return meta.NewStringConstant(st.CurrentClass + "::" + st.CurrentFunction)
+		case "__CLASS__":
+			return meta.NewStringConstant(st.CurrentClass)
+		case "__NAMESPACE__":
+			return meta.NewStringConstant(st.Namespace)
+		case "__TRAIT__":
+			if st.IsTrait {
+				return meta.NewStringConstant(st.CurrentClass)
+			}
+		}
 	}
 
 	return meta.UnknownValue


Test patch:
diff --git a/src/ir/normalize/normalize_test.go b/src/ir/normalize/normalize_test.go
index e788f17..dbe4393 100644
--- a/src/ir/normalize/normalize_test.go
+++ b/src/ir/normalize/normalize_test.go
@@ -221,6 +221,38 @@ class Foo {
 	})
 }
 
+func TestMagicConstFold(t *testing.T) {
+	linttest.ParseTestFile(t, "files/file.php", `<?php
+namespace Boo;
+
+const NAMESPACE_NAME = __NAMESPACE__;
+const FILENAME = __FILE__;
+const DIR = __DIR__;
+const DIR_WITH_FUNC = dirname(__FILE__);
+
+const CUSTOM_DIR = __DIR__ . "/file2.php";
+const CUSTOM_DIR_2 = dirname(__FILE__) . "/file2.php";
+
+class Foo {
+  const CLASSNAME = __CLASS__;
+  const LINE = __LINE__;
+}
+`)
+	meta.SetIndexingComplete(true)
+	defer meta.SetIndexingComplete(false)
+
+	runNormalizeTests(t, []normalizationTest{
+		{`\Boo\NAMESPACE_NAME`, `'\Boo'`},
+		{`\Boo\FILENAME`, `'files/file.php'`},
+		{`\Boo\DIR`, `'files'`},
+		{`\Boo\DIR_WITH_FUNC`, `'files'`},
+		{`\Boo\CUSTOM_DIR`, `'files/file2.php'`},
+		{`\Boo\CUSTOM_DIR_2`, `'files/file2.php'`},
+		{`\Boo\Foo::CLASSNAME`, `'\Boo\Foo'`},
+		{`\Boo\Foo::LINE`, `14`},
+	})
+}
+
 func parseStmtList(s string) ([]ir.Node, error) {
 	source := "<?php " + s
 	p := php7.NewParser([]byte(source))
Repository: VKCOM/noverify
Pull request #809 (2020-10-08 13:58:58)

phpgrep: add "call" meta node
Matches FunctionCall, MethodCall, StaticCall.

Useful for the command-line github.com/quasilyte/phpgrep tool.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/phpgrep/compile.go b/src/phpgrep/compile.go
index a965a6f..a1f8414 100644
--- a/src/phpgrep/compile.go
+++ b/src/phpgrep/compile.go
@@ -93,6 +93,8 @@ func (c *compiler) EnterNode(n ir.Node) bool {
 		v.Expr = anyNum{metaNode{name: name}}
 	case "expr":
 		v.Expr = anyExpr{metaNode{name: name}}
+	case "call":
+		v.Expr = anyCall{metaNode{name: name}}
 	case "const":
 		v.Expr = anyConst{metaNode{name: name}}
 	case "func":
diff --git a/src/phpgrep/matcher.go b/src/phpgrep/matcher.go
index f1c5740..ceea6b7 100644
--- a/src/phpgrep/matcher.go
+++ b/src/phpgrep/matcher.go
@@ -753,6 +753,13 @@ func (m *matcher) eqVar(state *matcherState, x *ir.Var, y ir.Node) bool {
 		}
 	case anyExpr:
 		return nodeIsExpr(y) && m.matchNamed(state, vn.name, y)
+	case anyCall:
+		switch y.(type) {
+		case *ir.FunctionCallExpr, *ir.StaticCallExpr, *ir.MethodCallExpr:
+			return m.matchNamed(state, vn.name, y)
+		default:
+			return false
+		}
 	}
 
 	if y, ok := y.(*ir.Var); ok {
diff --git a/src/phpgrep/meta_nodes.go b/src/phpgrep/meta_nodes.go
index 2038617..bde8e70 100644
--- a/src/phpgrep/meta_nodes.go
+++ b/src/phpgrep/meta_nodes.go
@@ -21,5 +21,6 @@ type (
 	anyStr1  struct{ metaNode }
 	anyNum   struct{ metaNode }
 	anyExpr  struct{ metaNode }
+	anyCall  struct{ metaNode }
 	anyFunc  struct{ metaNode }
 )
diff --git a/src/phpgrep/pattern_language.md b/src/phpgrep/pattern_language.md
index 1caad03..23716ba 100644
--- a/src/phpgrep/pattern_language.md
+++ b/src/phpgrep/pattern_language.md
@@ -60,6 +60,7 @@ matcher_class = <see the table of supported classes below>
 | `var` | Variable |
 | `func` | Anonymous function/closure expression |
 | `expr` | Any expression |
+| `call` | Function or method (static or not) call expression |
 
 Some examples of complete matcher expressions:
 * `${'*'}` - matches any number of nodes


Test patch:
diff --git a/src/phpgrep/matcher_test.go b/src/phpgrep/matcher_test.go
index 7f21fab..c391518 100644
--- a/src/phpgrep/matcher_test.go
+++ b/src/phpgrep/matcher_test.go
@@ -379,6 +379,12 @@ func TestMatch(t *testing.T) {
 		{`${"func"}`, `function($x) {}`},
 		{`${"func"}`, `function() { return 1; }`},
 
+		{`${"call"}`, `f()`},
+		{`${"call"}`, `$o->method()`},
+		{`${"call"}`, `C::method()`},
+		{`${"call"}`, `$a[0]->method()`},
+		{`${"x:call"} + ${"x:call"}`, `f() + f()`},
+
 		{`1`, `1`},
 		{`(1)`, `(1)`},
 		{`((1))`, `((1))`},
@@ -535,6 +541,10 @@ func TestMatchNegative(t *testing.T) {
 		{`${"func"}`, `$x`},
 		{`${"func"}`, `f()`},
 
+		{`${"call"}`, `10`},
+		{`${"call"}`, `$x[0]`},
+		{`${"x:call"} + ${"x:call"}`, `f() + g()`},
+
 		{`(1)`, `1`},
 		{`((1))`, `(1)`},
 		{`f((1))`, `f(1)`},
Repository: VKCOM/noverify
Pull request #842 (2020-12-09 10:30:05)

phpgrep: fix panic on ir.CatchStmt matching
Do we miss any other cases? ...


Patch:
diff --git a/src/phpgrep/matcher.go b/src/phpgrep/matcher.go
index a4e3f0c..0a8b656 100644
--- a/src/phpgrep/matcher.go
+++ b/src/phpgrep/matcher.go
@@ -343,6 +343,11 @@ func (m *matcher) eqNode(state *matcherState, x, y ir.Node) bool {
 	case *ir.ThrowStmt:
 		y, ok := y.(*ir.ThrowStmt)
 		return ok && m.eqNode(state, x.Expr, y.Expr)
+	case *ir.CatchStmt:
+		y, ok := y.(*ir.CatchStmt)
+		return ok && m.eqSimpleVar(state, x.Variable, y.Variable) &&
+			m.eqNodeSlice(state, x.Types, y.Types) &&
+			m.eqNodeSlice(state, x.Stmts, y.Stmts)
 	case *ir.TryStmt:
 		y, ok := y.(*ir.TryStmt)
 		return ok && m.eqNodeSlice(state, x.Stmts, y.Stmts) &&


Test patch:
diff --git a/src/phpgrep/matcher_test.go b/src/phpgrep/matcher_test.go
index 50333a8..8904095 100644
--- a/src/phpgrep/matcher_test.go
+++ b/src/phpgrep/matcher_test.go
@@ -475,6 +475,9 @@ func TestMatch(t *testing.T) {
 		{`f((1))`, `f((1))`},
 		{`($foo)()`, `($foo)()`},
 		{`($foo)->x`, `($foo)->x`},
+
+		{`try { ${"*"}; } catch (Exception $_) { ${"*"}; }`, `try {} catch (Exception $e) {}`},
+		{`try { ${"*"}; } catch (Exception $_) { ${"*"}; }`, `try {} catch (Exception $ex) { var_dump($ex); }`},
 	})
 }
 
@@ -643,6 +646,9 @@ func TestMatchNegative(t *testing.T) {
 		{`($foo)->x`, `$foo->x`},
 		{`(($foo))()`, `($foo)()`},
 		{`(($foo))->x`, `($foo)->x`},
+
+		{`try { ${"*"}; } catch (Exception $_) { ${"*"}; }`, `try {} catch (MyException $e) {}`},
+		{`try { ${"*"}; } catch (Exception $_) { ${"*"}; }`, `try {} catch (Exception $_) {} catch (Exception $_) {}`},
 	})
 }
Repository: VKCOM/noverify
Pull request #858 (2020-12-14 15:55:20)

linter: added check for deprecated for static methods


Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 07cac51..b960cd0 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -801,6 +801,16 @@ func (b *blockLinter) checkStaticCall(e *ir.StaticCallExpr) {
 		b.report(e.Call, LevelWarning, "callStatic", "Calling instance method as static method")
 	}
 
+	if call.methodInfo.Info.Doc.Deprecated {
+		if call.methodInfo.Info.Doc.DeprecationNote != "" {
+			b.report(e.Call, LevelDoNotReject, "deprecated", "Call to deprecated static method %s::%s() (%s)",
+				call.className, call.methodName, call.methodInfo.Info.Doc.DeprecationNote)
+		} else {
+			b.report(e.Call, LevelDoNotReject, "deprecated", "Call to deprecated static method %s::%s()",
+				call.className, call.methodName)
+		}
+	}
+
 	if call.isFound && !canAccess(b.walker.r.ctx.st, call.methodInfo.ClassName, call.methodInfo.Info.AccessLevel) {
 		b.report(e.Call, LevelError, "accessLevel", "Cannot access %s method %s::%s()", call.methodInfo.Info.AccessLevel, call.methodInfo.ClassName, call.methodName)
 	}


Test patch:
diff --git a/src/tests/checkers/phpdoc_test.go b/src/tests/checkers/phpdoc_test.go
index 6803fc4..f9f9018 100644
--- a/src/tests/checkers/phpdoc_test.go
+++ b/src/tests/checkers/phpdoc_test.go
@@ -189,6 +189,33 @@ function f() {
 	test.RunAndMatch()
 }
 
+func TestDeprecatedStaticMethod(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+class Foo {
+  /**
+   * @deprecated use newMethod instead
+   */
+  public static function legacyMethod1() {}
+
+  /**
+   * @deprecated
+   */
+  public static function legacyMethod2() {}
+}
+
+Foo::legacyMethod1();
+function f() {
+  Foo::legacyMethod2();
+}
+`)
+	test.Expect = []string{
+		`Call to deprecated static method \Foo::legacyMethod1() (use newMethod instead)`,
+		`Call to deprecated static method \Foo::legacyMethod2()`,
+	}
+	test.RunAndMatch()
+}
+
 func TestDeprecatedFunction(t *testing.T) {
 	test := linttest.NewSuite(t)
 	test.AddFile(`<?php