Repository: VKCOM/noverify
Pull request #1220 (2024-05-21 16:48:16)

linter: deprecated support classes fields


Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index e4be55c..3343caf 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -247,10 +247,17 @@ func (b *blockLinter) checkClass(class *ir.ClassStmt) {
 
 	var members = make([]int, 0, len(class.Stmts))
 	for _, stmt := range class.Stmts {
-		switch stmt := stmt.(type) {
+		switch value := stmt.(type) {
 		case *ir.ClassMethodStmt:
 			members = append(members, classMethod)
-			b.walker.CheckParamNullability(stmt.Params)
+			b.walker.CheckParamNullability(value.Params)
+		case *ir.PropertyListStmt:
+			for _, element := range value.Doc.Parsed {
+				if element.Name() == "deprecated" {
+					b.report(stmt, LevelNotice, "deprecated", "Has deprecated field in class %s", class.ClassName.Value)
+				}
+			}
+			members = append(members, classOtherMember)
 		default:
 			members = append(members, classOtherMember)
 		}
diff --git a/src/linter/phpdoc_util.go b/src/linter/phpdoc_util.go
index 5e3ad03..5d79492 100644
--- a/src/linter/phpdoc_util.go
+++ b/src/linter/phpdoc_util.go
@@ -88,6 +88,7 @@ type classPHPDocParseResult struct {
 	mixins      []string
 	packageName string
 	internal    bool
+	deprecated  bool
 }
 
 func parseClassPHPDocMethod(classNode ir.Node, ctx *rootContext, result *classPHPDocParseResult, part *phpdoc.RawCommentPart) {
diff --git a/src/linter/root.go b/src/linter/root.go
index 5849f15..1f3c4ae 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -201,6 +201,10 @@ func (d *rootWalker) EnterNode(n ir.Node) (res bool) {
 		d.reportPHPDocErrors(doc.errs)
 		d.handleClassDoc(doc, &cl)
 
+		if doc.deprecated {
+			d.Report(n, LevelNotice, "deprecated", "Has deprecated class %s", n.ClassName.Value)
+		}
+
 		d.meta.Classes.Set(d.ctx.st.CurrentClass, cl)
 
 	case *ir.TraitStmt:
@@ -814,23 +818,25 @@ func (d *rootWalker) parseClassPHPDoc(class ir.Node, doc phpdoc.Comment) classPH
 		return result
 	}
 
-	// TODO: allocate maps lazily.
-	// Class may not have any @property or @method annotations.
-	// In that case we can handle avoid map allocations.
-	result.properties = make(meta.PropertiesMap)
-	result.methods = meta.NewFunctionsMap()
-
 	for _, part := range doc.Parsed {
 		d.checker.checkPHPDocRef(class, part)
 		switch part.Name() {
 		case "property", "property-read", "property-write":
+			if result.properties == nil {
+				result.properties = make(meta.PropertiesMap)
+			}
 			parseClassPHPDocProperty(class, &d.ctx, &result, part.(*phpdoc.TypeVarCommentPart))
 		case "method":
+			if result.methods.H == nil {
+				result.methods = meta.NewFunctionsMap()
+			}
 			parseClassPHPDocMethod(class, &d.ctx, &result, part.(*phpdoc.RawCommentPart))
 		case "mixin":
 			parseClassPHPDocMixin(class, d.ctx.st, &result, part.(*phpdoc.RawCommentPart))
 		case "package":
 			parseClassPHPDocPackage(class, d.ctx.st, &result, part.(*phpdoc.PackageCommentPart))
+		case "deprecated":
+			result.deprecated = true
 		case "internal":
 			result.internal = true
 		}


Test patch:
diff --git a/src/tests/checkers/deprecated_test.go b/src/tests/checkers/deprecated_test.go
index a7ddfd6..336e78c 100644
--- a/src/tests/checkers/deprecated_test.go
+++ b/src/tests/checkers/deprecated_test.go
@@ -71,3 +71,27 @@ WithText::staticMethod();
 	}
 	test.RunAndMatch()
 }
+
+func TestDeprecatedClassAndProperty(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+declare(strict_types = "1");
+
+/**
+ * @deprecated class 
+ */
+class OldClass
+{
+/**
+ * @deprecated property
+ */
+public $prp;
+}
+
+`)
+	test.Expect = []string{
+		"Has deprecated class OldClass",
+		"Has deprecated field in class OldClass",
+	}
+	test.RunAndMatch()
+}
diff --git a/src/tests/golden/testdata/flysystem/golden.txt b/src/tests/golden/testdata/flysystem/golden.txt
index 1391a8a..bbd2f80 100644
--- a/src/tests/golden/testdata/flysystem/golden.txt
+++ b/src/tests/golden/testdata/flysystem/golden.txt
@@ -61,6 +61,12 @@ 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);
                              ^^^^^^
+MAYBE   deprecated: Has deprecated class Directory at testdata/flysystem/src/Directory.php:8
+class Directory extends Handler
+^
+MAYBE   deprecated: Has deprecated class File at testdata/flysystem/src/File.php:8
+class File extends Handler
+^
 WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/flysystem/src/FileExistsException.php:21
     public function __construct($path, $code = 0, BaseException $previous = null)
                                                   ^^^^^^^^^^^^^
@@ -94,6 +100,9 @@ WARNING notExplicitNullableParam: parameter with null default value should be ex
 WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/flysystem/src/FilesystemInterface.php:274
     public function get($path, Handler $handler = null);
                                ^^^^^^^
+MAYBE   deprecated: Has deprecated class Handler at testdata/flysystem/src/Handler.php:10
+abstract class Handler
+^
 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) {
                                         ^^
diff --git a/src/tests/golden/testdata/inflector/golden.txt b/src/tests/golden/testdata/inflector/golden.txt
index 92cc75e..c975a6b 100644
--- a/src/tests/golden/testdata/inflector/golden.txt
+++ b/src/tests/golden/testdata/inflector/golden.txt
@@ -1,3 +1,6 @@
+MAYBE   deprecated: Has deprecated class Inflector at testdata/inflector/lib/Doctrine/Common/Inflector/Inflector.php:33
+final class Inflector
+^
 WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/inflector/lib/Doctrine/Common/Inflector/Inflector.php:54
         @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);
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^