Repository: VKCOM/noverify
Pull request #1092 (2021-08-21 12:33:02)

linter: split `undefined` checker into the following
- `maybeUndefined`
- `undefinedConstant`
- `undefinedFunction`
- `undefinedMethod`
- `undefinedProperty`
- `undefinedType`
- `undefinedVariable`


Patch:
diff --git a/docs/checkers_doc.md b/docs/checkers_doc.md
index 1a95856..8545a91 100644
--- a/docs/checkers_doc.md
+++ b/docs/checkers_doc.md
@@ -4,7 +4,7 @@
 
 | Total checks | Checks enabled by default | Disabled checks by default | Autofixable checks |
 | ------------ | ------------------------- | -------------------------- | ------------------ |
-| 89           | 79                        | 10                         | 12                 |
+| 95           | 85                        | 10                         | 12                 |
 ## Table of contents
  - Enabled by default
    - [`accessLevel` checker](#accesslevel-checker)
@@ -48,6 +48,7 @@
    - [`keywordCase` checker](#keywordcase-checker)
    - [`linterError` checker](#lintererror-checker)
    - [`magicMethodDecl` checker](#magicmethoddecl-checker)
+   - [`maybeUndefined` checker](#maybeundefined-checker)
    - [`methodSignatureMismatch` checker](#methodsignaturemismatch-checker)
    - [`misspellComment` checker](#misspellcomment-checker)
    - [`misspellName` checker](#misspellname-checker)
@@ -81,7 +82,12 @@
    - [`syntax` checker](#syntax-checker)
    - [`ternarySimplify` checker (autofixable)](#ternarysimplify-checker)
    - [`unaryRepeat` checker (autofixable)](#unaryrepeat-checker)
-   - [`undefined` checker](#undefined-checker)
+   - [`undefinedConstant` checker](#undefinedconstant-checker)
+   - [`undefinedFunction` checker](#undefinedfunction-checker)
+   - [`undefinedMethod` checker](#undefinedmethod-checker)
+   - [`undefinedProperty` checker](#undefinedproperty-checker)
+   - [`undefinedType` checker](#undefinedtype-checker)
+   - [`undefinedVariable` checker](#undefinedvariable-checker)
    - [`unimplemented` checker](#unimplemented-checker)
    - [`unused` checker](#unused-checker)
    - [`varShadow` checker](#varshadow-checker)
@@ -940,6 +946,31 @@ class Foo {
 <p><br></p>
 
 
+### `maybeUndefined` checker
+
+#### Description
+
+Report usages of potentially undefined symbols.
+
+#### Non-compliant code:
+```php
+if ($cond) {
+  $v = 10;
+}
+return $v; // $v may be undefined.
+```
+
+#### Compliant code:
+```php
+$v = 0; // Default value.
+if ($cond) {
+  $v = 10;
+}
+return $v;
+```
+<p><br></p>
+
+
 ### `methodSignatureMismatch` checker
 
 #### Description
@@ -1591,29 +1622,127 @@ echo (bool) $a;
 <p><br></p>
 
 
-### `undefined` checker
+### `undefinedConstant` checker
 
 #### Description
 
-Report usages of potentially undefined symbols.
+Report usages of undefined constant.
 
 #### Non-compliant code:
 ```php
-if ($cond) {
-  $v = 10;
+echo PI;
+```
+
+#### Compliant code:
+```php
+echo M_PI;
+```
+<p><br></p>
+
+
+### `undefinedFunction` checker
+
+#### Description
+
+Report usages of undefined function.
+
+#### Non-compliant code:
+```php
+undefinedFunc();
+```
+
+#### Compliant code:
+```php
+definedFunc();
+```
+<p><br></p>
+
+
+### `undefinedMethod` checker
+
+#### Description
+
+Report usages of undefined method.
+
+#### Non-compliant code:
+```php
+class Foo {
+  public function method() {};
 }
-return $v; // $v may be undefined.
 
+(new Foo)->method2(); // method2 is undefined.
 ```
 
 #### Compliant code:
 ```php
-$v = 0; // Default value.
-if ($cond) {
-  $v = 10;
+class Foo {
+  public function method() {}
 }
-return $v;
 
+(new Foo)->method();
+```
+<p><br></p>
+
+
+### `undefinedProperty` checker
+
+#### Description
+
+Report usages of undefined property.
+
+#### Non-compliant code:
+```php
+class Foo {
+  public string $prop;
+}
+
+(new Foo)->prop2; // prop2 is undefined.
+```
+
+#### Compliant code:
+```php
+class Foo {
+  public string $prop;
+}
+
+(new Foo)->prop;
+```
+<p><br></p>
+
+
+### `undefinedType` checker
+
+#### Description
+
+Report usages of undefined type.
+
+#### Non-compliant code:
+```php
+class Foo extends UndefinedClass {}
+```
+
+#### Compliant code:
+```php
+class Foo extends DefinedClass {}
+```
+<p><br></p>
+
+
+### `undefinedVariable` checker
+
+#### Description
+
+Report usages of undefined variable.
+
+#### Non-compliant code:
+```php
+echo $undefinedVar;
+```
+
+#### Compliant code:
+```php
+$definedVar = 100;
+echo $definedVar;
 ```
 <p><br></p>
 
diff --git a/src/linter/block.go b/src/linter/block.go
index 5a0c8a9..b2df75b 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -1236,7 +1236,7 @@ func (b *blockWalker) enterClosure(fun *ir.ClosureExpr, haveThis bool, thisType
 		}
 
 		if !b.ctx.sc.HaveVar(v) && !byRef {
-			b.r.Report(v, LevelWarning, "undefined", "Undefined variable $%s", v.Name)
+			b.r.Report(v, LevelWarning, "undefinedVariable", "Undefined variable $%s", v.Name)
 		}
 
 		typ, ok := b.ctx.sc.GetVarNameType(v.Name)
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 740a020..fe0b78d 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -561,7 +561,7 @@ func (b *blockLinter) checkConstFetch(e *ir.ConstFetchExpr) {
 			b.report(e.Constant, LevelError, "constCase", "Constant '%s' should be used in lower case as '%s'", nm, expected)
 			b.addFixForBuiltInConstantCase(e.Constant, expected)
 		default:
-			b.report(e.Constant, LevelError, "undefined", "Undefined constant %s", nm)
+			b.report(e.Constant, LevelError, "undefinedConstant", "Undefined constant %s", nm)
 		}
 	}
 }
@@ -942,7 +942,7 @@ func (b *blockLinter) checkDeprecatedFunctionCall(e *ir.FunctionCallExpr, call *
 
 func (b *blockLinter) checkFunctionAvailability(e *ir.FunctionCallExpr, call *funcCallInfo) {
 	if !call.isFound && !b.walker.ctx.customFunctionExists(e.Function) {
-		b.report(e.Function, LevelError, "undefined", "Call to undefined function %s", utils.NameNodeToString(e.Function))
+		b.report(e.Function, LevelError, "undefinedFunction", "Call to undefined function %s", utils.NameNodeToString(e.Function))
 	}
 }
 
@@ -1175,7 +1175,7 @@ func (b *blockLinter) checkMethodCall(e *ir.MethodCallExpr) {
 		// The method is undefined, but we permit calling it if `method_exists`
 		// was called prior to that call.
 		if !b.walker.ctx.customMethodExists(e.Variable, call.methodName) && needShowUndefinedMethod {
-			b.report(e.Method, LevelError, "undefined", "Call to undefined method {%s}->%s()", call.methodCallerType, call.methodName)
+			b.report(e.Method, LevelError, "undefinedMethod", "Call to undefined method {%s}->%s()", call.methodCallerType, call.methodName)
 		}
 	} else if !call.isMagic && !parseState.IsTrait {
 		// Method is defined.
@@ -1213,7 +1213,7 @@ func (b *blockLinter) checkStaticCall(e *ir.StaticCallExpr) {
 	}
 
 	if !call.isFound && !call.isMagic && !b.classParseState().IsTrait {
-		b.report(e.Call, LevelError, "undefined", "Call to undefined method %s::%s()", call.className, call.methodName)
+		b.report(e.Call, LevelError, "undefinedMethod", "Call to undefined method %s::%s()", call.className, call.methodName)
 	} else if !call.isParentCall && !call.methodInfo.Info.IsStatic() && !call.isMagic && !b.classParseState().IsTrait {
 		// Method is defined.
 		// parent::f() is permitted.
@@ -1247,7 +1247,7 @@ func (b *blockLinter) checkPropertyFetch(e *ir.PropertyFetchExpr) {
 		!b.classParseState().IsTrait &&
 		!b.walker.isThisInsideClosure(e.Variable) &&
 		needShowUndefinedProperty {
-		b.report(e.Property, LevelError, "undefined", "Property {%s}->%s does not exist", fetch.propertyFetchType, fetch.propertyNode.Value)
+		b.report(e.Property, LevelError, "undefinedProperty", "Property {%s}->%s does not exist", fetch.propertyFetchType, fetch.propertyNode.Value)
 	}
 
 	if fetch.isFound && !fetch.isMagic && !canAccess(b.classParseState(), fetch.className, fetch.info.AccessLevel) {
@@ -1264,7 +1264,7 @@ func (b *blockLinter) checkStaticPropertyFetch(e *ir.StaticPropertyFetchExpr) {
 	b.checkClassSpecialNameCase(e, fetch.className)
 
 	if !fetch.isFound && !b.classParseState().IsTrait {
-		b.report(e.Property, LevelError, "undefined", "Property %s::$%s does not exist", fetch.className, fetch.propertyName)
+		b.report(e.Property, LevelError, "undefinedProperty", "Property %s::$%s does not exist", fetch.className, fetch.propertyName)
 	}
 
 	if fetch.isFound && !canAccess(b.classParseState(), fetch.info.ClassName, fetch.info.Info.AccessLevel) {
@@ -1288,7 +1288,7 @@ func (b *blockLinter) checkClassConstFetch(e *ir.ClassConstFetchExpr) {
 	}
 
 	if !fetch.isFound && !b.classParseState().IsTrait {
-		b.walker.r.Report(e.ConstantName, LevelError, "undefined", "Class constant %s::%s does not exist", fetch.className, fetch.constName)
+		b.walker.r.Report(e.ConstantName, LevelError, "undefinedConstant", "Class constant %s::%s does not exist", fetch.className, fetch.constName)
 	}
 
 	if fetch.isFound && !canAccess(b.classParseState(), fetch.implClassName, fetch.info.AccessLevel) {
diff --git a/src/linter/report.go b/src/linter/report.go
index 0a294ba..c456cb6 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -344,21 +344,90 @@ public function process($acts, $config) {
 		},
 
 		{
-			Name:     "undefined",
+			Name:     "undefinedProperty",
+			Default:  true,
+			Quickfix: false,
+			Comment:  `Report usages of undefined property.`,
+			Before: `class Foo {
+  public string $prop;
+}
+
+(new Foo)->prop2; // prop2 is undefined.`,
+			After: `class Foo {
+  public string $prop;
+}
+
+(new Foo)->prop;`,
+		},
+
+		{
+			Name:     "undefinedMethod",
+			Default:  true,
+			Quickfix: false,
+			Comment:  `Report usages of undefined method.`,
+			Before: `class Foo {
+  public function method() {};
+}
+
+(new Foo)->method2(); // method2 is undefined.`,
+			After: `class Foo {
+  public function method() {}
+}
+
+(new Foo)->method();`,
+		},
+
+		{
+			Name:     "undefinedConstant",
+			Default:  true,
+			Quickfix: false,
+			Comment:  `Report usages of undefined constant.`,
+			Before:   `echo PI;`,
+			After:    `echo M_PI;`,
+		},
+
+		{
+			Name:     "undefinedFunction",
+			Default:  true,
+			Quickfix: false,
+			Comment:  `Report usages of undefined function.`,
+			Before:   `undefinedFunc();`,
+			After:    `definedFunc();`,
+		},
+
+		{
+			Name:     "undefinedVariable",
+			Default:  true,
+			Quickfix: false,
+			Comment:  `Report usages of undefined variable.`,
+			Before:   `echo $undefinedVar;`,
+			After: `$definedVar = 100;
+echo $definedVar;`,
+		},
+
+		{
+			Name:     "maybeUndefined",
 			Default:  true,
 			Quickfix: false,
 			Comment:  `Report usages of potentially undefined symbols.`,
 			Before: `if ($cond) {
   $v = 10;
 }
-return $v; // $v may be undefined.
-`,
+return $v; // $v may be undefined.`,
 			After: `$v = 0; // Default value.
 if ($cond) {
   $v = 10;
 }
-return $v;
-`,
+return $v;`,
+		},
+
+		{
+			Name:     "undefinedType",
+			Default:  true,
+			Quickfix: false,
+			Comment:  `Report usages of undefined type.`,
+			Before:   `class Foo extends UndefinedClass {}`,
+			After:    `class Foo extends DefinedClass {}`,
 		},
 
 		{
diff --git a/src/linter/root.go b/src/linter/root.go
index 2d11c63..9ff8132 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -569,7 +569,7 @@ func (d *rootWalker) reportHash(loc *ir.Location, contextLine []byte, checkName,
 func (d *rootWalker) reportUndefinedVariable(v ir.Node, maybeHave bool) {
 	sv, ok := v.(*ir.SimpleVar)
 	if !ok {
-		d.Report(v, LevelWarning, "undefined", "Unknown variable variable %s used",
+		d.Report(v, LevelWarning, "undefinedVariable", "Unknown variable variable %s used",
 			utils.NameNodeToString(v))
 		return
 	}
@@ -579,9 +579,9 @@ func (d *rootWalker) reportUndefinedVariable(v ir.Node, maybeHave bool) {
 	}
 
 	if maybeHave {
-		d.Report(sv, LevelWarning, "undefined", "Variable $%s might have not been defined", sv.Name)
+		d.Report(sv, LevelWarning, "maybeUndefined", "Variable $%s might have not been defined", sv.Name)
 	} else {
-		d.Report(sv, LevelError, "undefined", "Undefined variable $%s", sv.Name)
+		d.Report(sv, LevelError, "undefinedVariable", "Undefined variable $%s", sv.Name)
 	}
 }
 
@@ -2309,7 +2309,7 @@ func (d *rootWalker) checkImplementedStep(classNode, name ir.Node, className str
 }
 
 func (d *rootWalker) reportUndefinedType(n ir.Node, name string) {
-	d.Report(n, LevelError, "undefined", "Type %s not found", name)
+	d.Report(n, LevelError, "undefinedType", "Type %s not found", name)
 }
 
 func (d *rootWalker) checkNameCase(n ir.Node, nameUsed, nameExpected string) {


Test patch:
diff --git a/src/tests/checkers/basic_test.go b/src/tests/checkers/basic_test.go
index c164491..4da7864 100644
--- a/src/tests/checkers/basic_test.go
+++ b/src/tests/checkers/basic_test.go
@@ -716,7 +716,7 @@ class Foo {
 		"Undefined variable $argc",
 	}
 
-	linttest.RunFilterMatch(test, "undefined")
+	linttest.RunFilterMatch(test, "undefinedVariable")
 }
 
 func TestAutogenSkip(t *testing.T) {
@@ -2115,7 +2115,7 @@ function f() {
 		"Undefined variable $x",
 		"Undefined variable $y",
 	}
-	linttest.RunFilterMatch(test, "undefined")
+	linttest.RunFilterMatch(test, "undefinedVariable")
 }
 
 func TestAssignByRef(t *testing.T) {
diff --git a/src/tests/checkers/oop_test.go b/src/tests/checkers/oop_test.go
index 06c7007..317a355 100644
--- a/src/tests/checkers/oop_test.go
+++ b/src/tests/checkers/oop_test.go
@@ -843,7 +843,7 @@ func TestClosureLateBinding(t *testing.T) {
 		"Undefined variable $a",
 		"Call to undefined method {undefined}->method()",
 	}
-	linttest.RunFilterMatch(test, "undefined")
+	linttest.RunFilterMatch(test, "undefinedVariable", "undefinedMethod")
 }
 
 func TestProtected(t *testing.T) {
@@ -1083,7 +1083,7 @@ func TestInstanceOf(t *testing.T) {
 		`Call to undefined method {void}->get2()`,
 		`Call to undefined method {\Element}->callUndefinedMethod()`,
 	}
-	linttest.RunFilterMatch(test, "undefined")
+	linttest.RunFilterMatch(test, "undefinedMethod")
 }
 
 func TestNullableTypes(t *testing.T) {
@@ -1383,7 +1383,7 @@ trait AbstractTraitAB {
 
 		`Class \T6\Bad must implement \T6\TraitAbstractA::a method`,
 	}
-	linttest.RunFilterMatch(test, `unimplemented`, `nameMismatch`, `undefined`)
+	linttest.RunFilterMatch(test, `unimplemented`, `nameMismatch`, `undefinedType`)
 }
 
 func TestInterfaceRules(t *testing.T) {
diff --git a/src/tests/golden/testdata/baseline-test/baseline.json b/src/tests/golden/testdata/baseline-test/baseline.json
index cae6d42..a8fb32f 100644
--- a/src/tests/golden/testdata/baseline-test/baseline.json
+++ b/src/tests/golden/testdata/baseline-test/baseline.json
@@ -1,25 +1,26 @@
 {
-	"LinterVersion": "0e47cc44623afb94af3d315708cf831696bbda9d",
-	"CreatedAt": 1626115955,
+	"LinterVersion": "e605ebd8f04e03e74712fb4d8cb3fdcf459f2e7c",
+	"CreatedAt": 1629561919,
 	"Version": 3,
 	"Stats": {
 		"CountTotal": 3,
 		"CountPerCheck": {
 			"discardExpr": 1,
-			"undefined": 2
+			"undefinedFunction": 1,
+			"undefinedProperty": 1
 		}
 	},
 	"Files": [
 		{
 			"File": "file1.php",
 			"Hashes": [
-				"11ngx7h7507kw,m3krbhb78pqm"
+				"33xto60mohiss,m3krbhb78pqm"
 			]
 		},
 		{
 			"File": "file2.php",
 			"Hashes": [
-				"209wyajyitniw"
+				"28uq3iyzkgyeb"
 			]
 		}
 	]
diff --git a/src/tests/golden/testdata/baseline-test/golden.txt b/src/tests/golden/testdata/baseline-test/golden.txt
index 8493f0f..4c8c3a3 100644
--- a/src/tests/golden/testdata/baseline-test/golden.txt
+++ b/src/tests/golden/testdata/baseline-test/golden.txt
@@ -1,6 +1,6 @@
 WARNING discardExpr: Expression evaluated but not used at testdata/baseline-test/src/file1.php:10
   "this line is not suppressed";
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-ERROR   undefined: Type \NonExisting not found at testdata/baseline-test/src/file2.php:5
+ERROR   undefinedType: Type \NonExisting not found at testdata/baseline-test/src/file2.php:5
 $bad = new NonExisting();
            ^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/mustache/golden.txt b/src/tests/golden/testdata/mustache/golden.txt
index 19a34ae..a2b8215 100644
--- a/src/tests/golden/testdata/mustache/golden.txt
+++ b/src/tests/golden/testdata/mustache/golden.txt
@@ -121,16 +121,16 @@ MAYBE   ternarySimplify: Could rewrite as `$options['cache_file_mode'] ?? null`
 MAYBE   misspellComment: "entitity" is a misspelling of "entity" at testdata/mustache/src/Mustache/Engine.php:254
     public function getEntityFlags()
                     ^^^^^^^^^^^^^^
-ERROR   undefined: Call to undefined method {\Mustache_Cache}->getLogger() at testdata/mustache/src/Mustache/Engine.php:459
+ERROR   undefinedMethod: Call to undefined method {\Mustache_Cache}->getLogger() at testdata/mustache/src/Mustache/Engine.php:459
         if ($this->getCache()->getLogger() === null) {
                                ^^^^^^^^^
-ERROR   undefined: Call to undefined method {\Mustache_Cache}->setLogger() at testdata/mustache/src/Mustache/Engine.php:460
+ERROR   undefinedMethod: Call to undefined method {\Mustache_Cache}->setLogger() at testdata/mustache/src/Mustache/Engine.php:460
             $this->getCache()->setLogger($logger);
                                ^^^^^^^^^
-ERROR   undefined: Call to undefined method {\Mustache_Cache}->getLogger() at testdata/mustache/src/Mustache/Engine.php:561
+ERROR   undefinedMethod: Call to undefined method {\Mustache_Cache}->getLogger() at testdata/mustache/src/Mustache/Engine.php:561
         if (isset($this->logger) && $cache->getLogger() === null) {
                                             ^^^^^^^^^
-ERROR   undefined: Call to undefined method {\Mustache_Cache}->setLogger() at testdata/mustache/src/Mustache/Engine.php:562
+ERROR   undefinedMethod: Call to undefined method {\Mustache_Cache}->setLogger() at testdata/mustache/src/Mustache/Engine.php:562
             $cache->setLogger($this->getLogger());
                     ^^^^^^^^^
 MAYBE   ternarySimplify: Could rewrite as `$this->delimiters ?: '{{ }}'` at testdata/mustache/src/Mustache/Engine.php:628
diff --git a/src/tests/golden/testdata/parsedown/golden.txt b/src/tests/golden/testdata/parsedown/golden.txt
index 817f78e..dd0d7f3 100644
--- a/src/tests/golden/testdata/parsedown/golden.txt
+++ b/src/tests/golden/testdata/parsedown/golden.txt
@@ -142,10 +142,10 @@ MAYBE   typeHint: Specify the type for the parameter $Elements in PHPDoc, 'array
 MAYBE   typeHint: Specify the type for the parameter $Element in PHPDoc, 'array' type hint too generic at testdata/parsedown/parsedown.php:1689
     protected function element(array $Element)
                        ^^^^^^^
-WARNING undefined: Variable $text might have not been defined at testdata/parsedown/parsedown.php:1755
+WARNING maybeUndefined: Variable $text might have not been defined at testdata/parsedown/parsedown.php:1755
                     $markup .= self::escape($text, true);
                                             ^^^^^
-WARNING undefined: Variable $text might have not been defined at testdata/parsedown/parsedown.php:1759
+WARNING maybeUndefined: Variable $text might have not been defined at testdata/parsedown/parsedown.php:1759
                     $markup .= $text;
                                ^^^^^
 MAYBE   typeHint: Specify the type for the parameter $Elements in PHPDoc, 'array' type hint too generic at testdata/parsedown/parsedown.php:1773
diff --git a/src/tests/golden/testdata/phprocksyd/golden.txt b/src/tests/golden/testdata/phprocksyd/golden.txt
index 45ad2da..f293b81 100644
--- a/src/tests/golden/testdata/phprocksyd/golden.txt
+++ b/src/tests/golden/testdata/phprocksyd/golden.txt
@@ -31,7 +31,7 @@ WARNING phpdocLint: Malformed @param $stream_id tag (maybe type is missing?) at
 WARNING phpdocLint: Malformed @param $req tag (maybe type is missing?) at testdata/phprocksyd/Phprocksyd.php:444
      * @param $req
               ^^^^
-ERROR   undefined: Call to undefined method {mixed}->run() at testdata/phprocksyd/Phprocksyd.php:479
+ERROR   undefinedMethod: Call to undefined method {mixed}->run() at testdata/phprocksyd/Phprocksyd.php:479
                 $instance->run($req['params']);
                            ^^^
 ERROR   constCase: Constant 'NULL' should be used in lower case as 'null' at testdata/phprocksyd/Simple.php:158
diff --git a/src/tests/golden/testdata/qrcode/golden.txt b/src/tests/golden/testdata/qrcode/golden.txt
index 619629d..d5be167 100644
--- a/src/tests/golden/testdata/qrcode/golden.txt
+++ b/src/tests/golden/testdata/qrcode/golden.txt
@@ -22,7 +22,7 @@ WARNING unused: Variable $mode is unused (use $_ to ignore this inspection or sp
 WARNING switchDefault: Add 'default' branch to avoid unexpected unhandled condition values at testdata/qrcode/qrcode.php:203
     switch ($mode) {
     ^
-WARNING undefined: Variable $code might have not been defined at testdata/qrcode/qrcode.php:221
+WARNING maybeUndefined: Variable $code might have not been defined at testdata/qrcode/qrcode.php:221
     while (count($code) % 8) {
                  ^^^^^
 WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testdata/qrcode/qrcode.php:297
diff --git a/src/tests/golden/testdata/underscore/golden.txt b/src/tests/golden/testdata/underscore/golden.txt
index 62fd2ab..22fda22 100644
--- a/src/tests/golden/testdata/underscore/golden.txt
+++ b/src/tests/golden/testdata/underscore/golden.txt
@@ -112,37 +112,37 @@ WARNING unused: Variable $__ is unused (use $_ to ignore this inspection or spec
 WARNING unused: Variable $args is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/underscore/underscore.php:615
     $args = self::_wrapArgs(func_get_args(), 1);
     ^^^^^
-ERROR   undefined: Property {\__|mixed|null}->isEqual does not exist at testdata/underscore/underscore.php:723
+ERROR   undefinedProperty: Property {\__|mixed|null}->isEqual does not exist at testdata/underscore/underscore.php:723
       if(is_object($a) && isset($a->isEqual)) return self::_wrap($a->isEqual($b));
                                     ^^^^^^^
-ERROR   undefined: Property {mixed|null}->isEqual does not exist at testdata/underscore/underscore.php:724
+ERROR   undefinedProperty: Property {mixed|null}->isEqual does not exist at testdata/underscore/underscore.php:724
       if(is_object($b) && isset($b->isEqual)) return self::_wrap($b->isEqual($a));
                                     ^^^^^^^
-ERROR   undefined: Call to undefined method {mixed|null}->isEqual() at testdata/underscore/underscore.php:724
+ERROR   undefinedMethod: Call to undefined method {mixed|null}->isEqual() at testdata/underscore/underscore.php:724
       if(is_object($b) && isset($b->isEqual)) return self::_wrap($b->isEqual($a));
                                                                      ^^^^^^^
 MAYBE   arrayAccess: Array access to non-array type \__|mixed|null at testdata/underscore/underscore.php:725
       if(is_array($a) && array_key_exists('isEqual', $a)) return self::_wrap($a['isEqual']($b));
                                                                              ^^
-ERROR   undefined: Property {mixed}->_uniqueId does not exist at testdata/underscore/underscore.php:822
+ERROR   undefinedProperty: Property {mixed}->_uniqueId does not exist at testdata/underscore/underscore.php:822
     $_instance->_uniqueId++;
                 ^^^^^^^^^
-ERROR   undefined: Property {mixed}->_uniqueId does not exist at testdata/underscore/underscore.php:824
+ERROR   undefinedProperty: Property {mixed}->_uniqueId does not exist at testdata/underscore/underscore.php:824
     return (is_null($prefix)) ? self::_wrap($_instance->_uniqueId) : self::_wrap($prefix . $_instance->_uniqueId);
                                                         ^^^^^^^^^
-ERROR   undefined: Property {mixed}->_uniqueId does not exist at testdata/underscore/underscore.php:824
+ERROR   undefinedProperty: Property {mixed}->_uniqueId does not exist at testdata/underscore/underscore.php:824
     return (is_null($prefix)) ? self::_wrap($_instance->_uniqueId) : self::_wrap($prefix . $_instance->_uniqueId);
                                                                                                        ^^^^^^^^^
-ERROR   undefined: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:843
+ERROR   undefinedProperty: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:843
     $mixins =& self::getInstance()->_mixins;
                                     ^^^^^^^
-ERROR   undefined: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:853
+ERROR   undefinedProperty: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:853
     $mixins =& self::getInstance()->_mixins;
                                     ^^^^^^^
-ERROR   undefined: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:859
+ERROR   undefinedProperty: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:859
     $mixins =& self::getInstance()->_mixins;
                                     ^^^^^^^
-ERROR   undefined: Property {mixed}->_template_settings does not exist at testdata/underscore/underscore.php:882
+ERROR   undefinedProperty: Property {mixed}->_template_settings does not exist at testdata/underscore/underscore.php:882
     $_template_settings =& self::getInstance()->_template_settings;
                                                 ^^^^^^^^^^^^^^^^^^
 MAYBE   arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:885
@@ -151,7 +151,7 @@ MAYBE   arraySyntax: Use the short form '[]' instead of the old 'array()' at tes
 MAYBE   trailingComma: Last element in a multi-line array should have a trailing comma at testdata/underscore/underscore.php:888
         'escape'      => self::TEMPLATE_DEFAULT_ESCAPE
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-ERROR   undefined: Property {mixed}->_template_settings does not exist at testdata/underscore/underscore.php:909
+ERROR   undefinedProperty: Property {mixed}->_template_settings does not exist at testdata/underscore/underscore.php:909
       $ts = $class_name::getInstance()->_template_settings;
                                         ^^^^^^^^^^^^^^^^^^
 MAYBE   deprecated: Call to deprecated function create_function (7.2 Use anonymous functions instead.) at testdata/underscore/underscore.php:940
diff --git a/src/tests/regression/issue16_test.go b/src/tests/regression/issue16_test.go
index fc86b23..e0c8799 100644
--- a/src/tests/regression/issue16_test.go
+++ b/src/tests/regression/issue16_test.go
@@ -44,5 +44,5 @@ func TestIssue16(t *testing.T) {
 		"Call to undefined method {mixed}->format()",
 		"Class constant \\TestExInterface::TEST2 does not exist",
 	}
-	linttest.RunFilterMatch(test, "undefined")
+	linttest.RunFilterMatch(test, "undefinedMethod", "undefinedConstant")
 }
diff --git a/src/tests/regression/issue288_test.go b/src/tests/regression/issue288_test.go
index 7e79d1c..2034949 100644
--- a/src/tests/regression/issue288_test.go
+++ b/src/tests/regression/issue288_test.go
@@ -40,7 +40,7 @@ $_ = $x instanceof Box ? 0 : 1;
 		`Undefined variable $b1`,
 		`Undefined variable $b2`,
 		`Undefined variable $b3`,
-		`undefined: Property {mixed}->item2 does not exist`,
+		`Property {mixed}->item2 does not exist`,
 	}
 	test.RunAndMatch()
 }