Repository: VKCOM/noverify
Pull request #1248 (2025-02-24 20:33:30)

linter: expanded deprecated for calling const, property and instances of classes
Now we have deprecated and deprecatedUntagged rule that could be apply for methods and functions.
I expanded it for calling const, property and instances of classes

Hidanio: Level notice for deprecated and deprecatedUntagged 

Patch:
diff --git a/docs/checkers_doc.md b/docs/checkers_doc.md
index 1c28d34..b7f9f0e 100644
--- a/docs/checkers_doc.md
+++ b/docs/checkers_doc.md
@@ -113,7 +113,6 @@
    - [`arrayAccess` checker](#arrayaccess-checker)
    - [`classMembersOrder` checker](#classmembersorder-checker)
    - [`complexity` checker](#complexity-checker)
-   - [`deprecatedUntagged` checker](#deprecateduntagged-checker)
    - [`errorSilence` checker](#errorsilence-checker)
    - [`getTypeMisUse` checker (autofixable)](#gettypemisuse-checker)
    - [`langDeprecated` checker](#langdeprecated-checker)
@@ -2303,34 +2302,6 @@ function checkRights() {
 <p><br></p>
 
 
-### `deprecatedUntagged` checker
-
-#### Description
-
-Report usages of deprecated symbols if the `@deprecated` tag has no description (see `deprecated` check).
-
-#### Non-compliant code:
-```php
-/**
- * @deprecated
- */
-function f() {}
-
-f();
-```
-
-#### Compliant code:
-```php
-/**
- * @deprecated
- */
-function f() {}
-
-g();
-```
-<p><br></p>
-
-
 ### `errorSilence` checker
 
 #### Description
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 66cd5c1..ace77db 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -253,13 +253,6 @@ func (b *blockLinter) checkClass(class *ir.ClassStmt) {
 		case *ir.ClassMethodStmt:
 			members = append(members, classMethod)
 			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)
 		}
@@ -565,6 +558,15 @@ func (b *blockLinter) checkNew(e *ir.NewExpr) {
 		if class.IsInterface() {
 			b.report(e.Class, LevelError, "invalidNew", "Cannot instantiate interface %s", class.Name)
 		}
+
+		if class.IsDeprecated() {
+			if class.WithDeprecationNote() {
+				b.report(e, LevelNotice, "deprecated", "Try to create instance of the class %s that was marked as deprecated (%s)", class.Name, class.DeprecationInfo)
+			} else {
+				b.report(e, LevelNotice, "deprecated", "Try to create instance %s class that was marked as deprecated", class.Name)
+			}
+		}
+
 		b.walker.r.checker.CheckNameCase(e.Class, className, class.Name)
 	}
 
@@ -1035,7 +1037,7 @@ func (b *blockLinter) checkDeprecatedFunctionCall(e *ir.FunctionCallExpr, call *
 		return
 	}
 
-	b.report(e.Function, LevelNotice, "deprecatedUntagged", "Call to deprecated function %s", utils.NameNodeToString(e.Function))
+	b.report(e.Function, LevelNotice, "deprecated", "Call to deprecated function %s", utils.NameNodeToString(e.Function))
 }
 
 func (b *blockLinter) checkFunctionAvailability(e *ir.FunctionCallExpr, call *funcCallInfo) {
@@ -1387,7 +1389,7 @@ func (b *blockLinter) checkMethodCall(e *ir.MethodCallExpr) {
 			b.report(e.Method, LevelNotice, "deprecated", "Call to deprecated method {%s}->%s() (%s)",
 				call.methodCallerType, call.methodName, deprecation)
 		} else {
-			b.report(e.Method, LevelNotice, "deprecatedUntagged", "Call to deprecated method {%s}->%s()",
+			b.report(e.Method, LevelNotice, "deprecated", "Call to deprecated method {%s}->%s()",
 				call.methodCallerType, call.methodName)
 		}
 	}
@@ -1465,7 +1467,7 @@ func (b *blockLinter) checkStaticCall(e *ir.StaticCallExpr) {
 			b.report(e.Call, LevelNotice, "deprecated", "Call to deprecated static method %s::%s() (%s)",
 				call.className, call.methodName, deprecation)
 		} else {
-			b.report(e.Call, LevelNotice, "deprecatedUntagged", "Call to deprecated static method %s::%s()",
+			b.report(e.Call, LevelNotice, "deprecated", "Call to deprecated static method %s::%s()",
 				call.className, call.methodName)
 		}
 	}
@@ -1524,6 +1526,14 @@ func (b *blockLinter) checkPropertyFetch(e *ir.PropertyFetchExpr) {
 		b.report(e.Property, LevelError, "accessLevel", "Cannot access %s property %s->%s", fetch.info.AccessLevel, fetch.className, fetch.propertyNode.Value)
 	}
 
+	if fetch.info.IsDeprecated() {
+		if fetch.info.WithDeprecationNote() {
+			b.report(e, LevelNotice, "deprecated", "Try to call property %s that was marked as deprecated (%s) in the class %s", fetch.propertyNode.Value, fetch.info.DeprecationInfo, fetch.className)
+		} else {
+			b.report(e, LevelNotice, "deprecated", "Try to call property %s that was marked as deprecated in the class %s", fetch.propertyNode.Value, fetch.className)
+		}
+	}
+
 	left, ok := b.walker.ctx.sc.GetVarType(e.Variable)
 	if ok {
 		b.checkSafetyCall(e, left, "", "PropertyFetch")
@@ -1577,6 +1587,14 @@ func (b *blockLinter) checkClassConstFetch(e *ir.ClassConstFetchExpr) {
 	if fetch.isFound && !canAccess(b.classParseState(), fetch.implClassName, fetch.info.AccessLevel) {
 		b.report(e.ConstantName, LevelError, "accessLevel", "Cannot access %s constant %s::%s", fetch.info.AccessLevel, fetch.implClassName, fetch.constName)
 	}
+
+	if fetch.info.IsDeprecated() {
+		if fetch.info.WithDeprecationNote() {
+			b.report(e, LevelNotice, "deprecated", "Try to call constant %s that was marked as deprecated (%s) from the class %s", fetch.constName, fetch.info.DeprecationInfo, fetch.className)
+		} else {
+			b.report(e, LevelNotice, "deprecated", "Try to call constant %s that was marked as deprecated from the class %s", fetch.constName, fetch.className)
+		}
+	}
 }
 
 func (b *blockLinter) checkClassSpecialNameCase(n ir.Node, className string) {
diff --git a/src/linter/block_utils.go b/src/linter/block_utils.go
index d5580e6..eca069d 100644
--- a/src/linter/block_utils.go
+++ b/src/linter/block_utils.go
@@ -38,10 +38,12 @@ func findProperty(info *meta.Info, className, propName string) (res solver.FindP
 		// Construct a dummy property from the magic method.
 		p.ClassName = m.ClassName
 		p.TraitName = m.TraitName
+
 		p.Info = meta.PropertyInfo{
-			Pos:         m.Info.Pos,
-			Typ:         m.Info.Typ,
-			AccessLevel: m.Info.AccessLevel,
+			Pos:             m.Info.Pos,
+			Typ:             m.Info.Typ,
+			AccessLevel:     m.Info.AccessLevel,
+			DeprecationInfo: m.Info.DeprecationInfo,
 		}
 		return p, true, true
 	}
diff --git a/src/linter/cache.go b/src/linter/cache.go
index bf66e43..c16c919 100644
--- a/src/linter/cache.go
+++ b/src/linter/cache.go
@@ -17,43 +17,44 @@ import (
 //
 // Version log:
 //
-//			27 - added Static field to meta.FuncInfo
-//			28 - array type parsed as mixed[]
-//			29 - updated type inference for ClassConstFetch
-//			30 - resolve ClassConstFetch to a wrapped type string
-//			31 - fixed plus operator type inference for arrays
-//			32 - replaced Static:bool with Flags:uint8 in meta.FuncInfo
-//			33 - support parsing of array<k,v> and list<type>
-//			34 - support parsing of ?ClassName as "ClassName|null"
-//			35 - added Flags:uint8 to meta.ClassInfo
-//			36 - added FuncAbstract bit to FuncFlags
-//			     added FuncFinal bit to FuncFlags
-//			     added ClassFinal bit to ClassFlags
-//			     FuncInfo now stores original function name
-//			     ClassInfo now stores original class name
-//			37 - added ClassShape bit to ClassFlags
-//			     changed meta.scopeVar bool fields representation
-//			38 - replaced TypesMap.immutable:bool with flags:uint8.
-//			     added mapPrecise flag to mark precise type maps.
-//			39 - added new field Value in ConstantInfo
-//			40 - changed string const value storage (no quotes)
-//			41 - const-folding affected const definition values
-//			42 - bool-typed consts are now stored in meta info
-//			43 - define'd const values stored in cache
-//			44 - rename ConstantInfo => ConstInfo
-//			45 - added Mixins field to meta.ClassInfo
-//			46 - changed the way of inferring the return type of functions and methods
-//			47 - forced cache version invalidation due to the #921
-//			48 - renamed meta.TypesMap to types.Map; this affects gob encoding
-//			49 - for shape, names are now generated using the keys that make up this shape
-//			50 - added Flags field for meta.PropertyInfo
-//			51 - added anonymous classes
-//			52 - renamed all PhpDoc and Phpdoc with PHPDoc
-//			53 - added DeprecationInfo for functions and methods and support for some attributes
-//			54 - forced cache version invalidation due to the #1165
-//		 	55 - updated go version 1.16 -> 1.21
-//	     56 - added isVariadic to meta.FuncInfo
-const cacheVersion = 56
+//	27 - added Static field to meta.FuncInfo
+//	28 - array type parsed as mixed[]
+//	29 - updated type inference for ClassConstFetch
+//	30 - resolve ClassConstFetch to a wrapped type string
+//	31 - fixed plus operator type inference for arrays
+//	32 - replaced Static:bool with Flags:uint8 in meta.FuncInfo
+//	33 - support parsing of array<k,v> and list<type>
+//	34 - support parsing of ?ClassName as "ClassName|null"
+//	35 - added Flags:uint8 to meta.ClassInfo
+//	36 - added FuncAbstract bit to FuncFlags
+//	     added FuncFinal bit to FuncFlags
+//	     added ClassFinal bit to ClassFlags
+//	     FuncInfo now stores original function name
+//	     ClassInfo now stores original class name
+//	37 - added ClassShape bit to ClassFlags
+//	     changed meta.scopeVar bool fields representation
+//	38 - replaced TypesMap.immutable:bool with flags:uint8.
+//	     added mapPrecise flag to mark precise type maps.
+//	39 - added new field Value in ConstantInfo
+//	40 - changed string const value storage (no quotes)
+//	41 - const-folding affected const definition values
+//	42 - bool-typed consts are now stored in meta info
+//	43 - define'd const values stored in cache
+//	44 - rename ConstantInfo => ConstInfo
+//	45 - added Mixins field to meta.ClassInfo
+//	46 - changed the way of inferring the return type of functions and methods
+//	47 - forced cache version invalidation due to the #921
+//	48 - renamed meta.TypesMap to types.Map; this affects gob encoding
+//	49 - for shape, names are now generated using the keys that make up this shape
+//	50 - added Flags field for meta.PropertyInfo
+//	51 - added anonymous classes
+//	52 - renamed all PhpDoc and Phpdoc with PHPDoc
+//	53 - added DeprecationInfo for functions and methods and support for some attributes
+//	54 - forced cache version invalidation due to the #1165
+//	55 - updated go version 1.16 -> 1.21
+//	56 - added isVariadic to meta.FuncInfo
+//	57 - added DeprecationInfo for property and const
+const cacheVersion = 57
 
 var (
 	errWrongVersion = errors.New("Wrong cache version")
diff --git a/src/linter/phpdoc_util.go b/src/linter/phpdoc_util.go
index 5d79492..51beb21 100644
--- a/src/linter/phpdoc_util.go
+++ b/src/linter/phpdoc_util.go
@@ -84,11 +84,16 @@ func (e *PHPDocErrors) pushLint(place PHPDocLocation, format string, args ...int
 type classPHPDocParseResult struct {
 	properties  meta.PropertiesMap
 	methods     meta.FunctionsMap
+	deprecation meta.DeprecationInfo
 	errs        PHPDocErrors
 	mixins      []string
 	packageName string
 	internal    bool
-	deprecated  bool
+}
+
+type variablePHPDocParseResult struct {
+	typesMap    types.Map
+	deprecation meta.DeprecationInfo
 }
 
 func parseClassPHPDocMethod(classNode ir.Node, ctx *rootContext, result *classPHPDocParseResult, part *phpdoc.RawCommentPart) {
diff --git a/src/linter/report.go b/src/linter/report.go
index c4c9c62..0af6f29 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -743,25 +743,6 @@ f();`,
  */
 function f() {}
 
-g();`,
-		},
-
-		{
-			Name:     "deprecatedUntagged",
-			Default:  false,
-			Quickfix: false,
-			Comment:  "Report usages of deprecated symbols if the `@deprecated` tag has no description (see `deprecated` check).",
-			Before: `/**
- * @deprecated
- */
-function f() {}
-
-f();`,
-			After: `/**
- * @deprecated
- */
-function f() {}
-
 g();`,
 		},
 
diff --git a/src/linter/root.go b/src/linter/root.go
index 0fb275f..4b40cea 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -201,9 +201,12 @@ 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)
+		// Handle attributes if any.
+		deprecation, ok := attributes.Deprecated(n.AttrGroups, d.ctx.st)
+		if ok {
+			doc.deprecation.Append(deprecation)
 		}
+		cl.DeprecationInfo = doc.deprecation
 
 		d.meta.Classes.Set(d.ctx.st.CurrentClass, cl)
 
@@ -845,7 +848,9 @@ func (d *rootWalker) parseClassPHPDoc(class ir.Node, doc phpdoc.Comment) classPH
 		case "package":
 			parseClassPHPDocPackage(class, d.ctx.st, &result, part.(*phpdoc.PackageCommentPart))
 		case "deprecated":
-			result.deprecated = true
+			part := part.(*phpdoc.RawCommentPart)
+			result.deprecation.Deprecated = true
+			result.deprecation.Reason = part.ParamsText
 		case "internal":
 			result.internal = true
 		}
@@ -902,19 +907,30 @@ func (d *rootWalker) handleClassDoc(doc classPHPDocParseResult, cl *meta.ClassIn
 	}
 }
 
-func (d *rootWalker) parsePHPDocVar(doc phpdoc.Comment) (typesMap types.Map) {
+func (d *rootWalker) parseVarPHPDoc(doc phpdoc.Comment) variablePHPDocParseResult {
+	var result variablePHPDocParseResult
+
+	if doc.Raw == "" {
+		return result
+	}
+
 	for _, part := range doc.Parsed {
-		part, ok := part.(*phpdoc.TypeVarCommentPart)
-		if ok && part.Name() == "var" {
+		switch part.Name() {
+		case "var":
+			part := part.(*phpdoc.TypeVarCommentPart)
 			converted := phpdoctypes.ToRealType(d.ctx.typeNormalizer.ClassFQNProvider(), d.config.KPHP, part.Type)
 			moveShapesToContext(&d.ctx, converted.Shapes)
 			d.handleClosuresFromDoc(converted.Closures)
 
-			typesMap = types.NewMapWithNormalization(d.ctx.typeNormalizer, converted.Types)
+			result.typesMap = types.NewMapWithNormalization(d.ctx.typeNormalizer, converted.Types)
+		case "deprecated":
+			part := part.(*phpdoc.RawCommentPart)
+			result.deprecation.Deprecated = true
+			result.deprecation.Reason = part.ParamsText
 		}
 	}
 
-	return typesMap
+	return result
 }
 
 func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
@@ -936,9 +952,14 @@ func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
 		}
 	}
 
-	phpDocType := d.parsePHPDocVar(pl.Doc)
+	varPhpDocRes := d.parseVarPHPDoc(pl.Doc)
 	typeHintType, _ := d.parseTypeHintNode(pl.Type)
 
+	deprecation, ok := attributes.Deprecated(pl.AttrGroups, d.ctx.st)
+	if ok {
+		varPhpDocRes.deprecation.Append(deprecation)
+	}
+
 	for _, pNode := range pl.Properties {
 		prop := pNode.(*ir.PropertyStmt)
 
@@ -947,7 +968,7 @@ func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
 		// We need to clone the types, because otherwise, if several
 		// properties are written in one definition, and null was
 		// assigned to the first, then all properties become nullable.
-		propTypes := phpDocType.Clone().Append(typeHintType)
+		propTypes := varPhpDocRes.typesMap.Clone().Append(typeHintType)
 
 		if prop.Expr != nil {
 			propTypes = propTypes.Append(solver.ExprTypeLocal(d.scope(), d.ctx.st, prop.Expr))
@@ -959,15 +980,32 @@ func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
 
 		// TODO: handle duplicate property
 		cl.Properties[nm] = meta.PropertyInfo{
-			Pos:         d.getElementPos(prop),
-			Typ:         propTypes.Immutable(),
-			AccessLevel: accessLevel,
+			Pos:             d.getElementPos(prop),
+			Typ:             propTypes.Immutable(),
+			AccessLevel:     accessLevel,
+			DeprecationInfo: varPhpDocRes.deprecation,
 		}
 	}
 
 	return true
 }
 
+func (d *rootWalker) parseConstPHPDoc(doc phpdoc.Comment) (deprecationInfo meta.DeprecationInfo) {
+	if doc.Raw == "" {
+		return deprecationInfo
+	}
+
+	for _, part := range doc.Parsed {
+		if part.Name() == "deprecated" {
+			part := part.(*phpdoc.RawCommentPart)
+			deprecationInfo.Deprecated = true
+			deprecationInfo.Reason = part.ParamsText
+		}
+	}
+
+	return deprecationInfo
+}
+
 func (d *rootWalker) enterClassConstList(list *ir.ClassConstListStmt) bool {
 	cl := d.getClass()
 	accessLevel := meta.Public
@@ -984,6 +1022,13 @@ func (d *rootWalker) enterClassConstList(list *ir.ClassConstListStmt) bool {
 		}
 	}
 
+	deprecationInfo := d.parseConstPHPDoc(list.Doc)
+
+	deprecation, ok := attributes.Deprecated(list.AttrGroups, d.ctx.st)
+	if ok {
+		deprecationInfo.Append(deprecation)
+	}
+
 	for _, cNode := range list.Consts {
 		c := cNode.(*ir.ConstantStmt)
 
@@ -995,10 +1040,11 @@ func (d *rootWalker) enterClassConstList(list *ir.ClassConstListStmt) bool {
 
 		// TODO: handle duplicate constant
 		cl.Constants[nm] = meta.ConstInfo{
-			Pos:         d.getElementPos(c),
-			Typ:         typ.Immutable(),
-			AccessLevel: accessLevel,
-			Value:       value,
+			Pos:             d.getElementPos(c),
+			Typ:             typ.Immutable(),
+			AccessLevel:     accessLevel,
+			Value:           value,
+			DeprecationInfo: deprecationInfo,
 		}
 	}
 
diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go
index f7b12e0..c3c9852 100644
--- a/src/linter/root_checker.go
+++ b/src/linter/root_checker.go
@@ -110,13 +110,13 @@ func (r *rootChecker) CheckPropertyList(pl *ir.PropertyListStmt) bool {
 		r.walker.Report(pl, LevelNotice, "implicitModifiers", "Specify the access modifier for %s explicitly", target)
 	}
 
-	docblockType := r.walker.parsePHPDocVar(pl.Doc)
+	varPhpDocRes := r.walker.parseVarPHPDoc(pl.Doc)
 
 	r.CheckCommentMisspellings(pl, pl.Doc.Raw)
-	r.CheckPHPDocVar(pl, pl.Doc, docblockType)
+	r.CheckPHPDocVar(pl, pl.Doc, varPhpDocRes.typesMap)
 
 	typeHintType, ok := r.walker.parseTypeHintNode(pl.Type)
-	if ok && !types.TypeHintHasMoreAccurateType(typeHintType, docblockType) {
+	if ok && !types.TypeHintHasMoreAccurateType(typeHintType, varPhpDocRes.typesMap) {
 		r.walker.Report(pl, LevelNotice, "typeHint", "Specify the type for the property in PHPDoc, 'array' type hint too generic")
 	}
 
@@ -128,7 +128,7 @@ func (r *rootChecker) CheckPropertyList(pl *ir.PropertyListStmt) bool {
 		// We need to clone the types, because otherwise, if several
 		// properties are written in one definition, and null was
 		// assigned to the first, then all properties become nullable.
-		propTypes := docblockType.Clone().Append(typeHintType)
+		propTypes := varPhpDocRes.typesMap.Clone().Append(typeHintType)
 
 		r.CheckAssignNullToNotNullableProperty(prop, propTypes)
 	}
diff --git a/src/meta/metainfo.go b/src/meta/metainfo.go
index 38f88c0..ea5cf34 100644
--- a/src/meta/metainfo.go
+++ b/src/meta/metainfo.go
@@ -178,17 +178,22 @@ type PropertyInfo struct {
 	Typ         types.Map
 	AccessLevel AccessLevel
 	Flags       PropertyFlags
+	DeprecationInfo
 }
 
 func (info *PropertyInfo) IsFromAnnotation() bool { return info.Flags&PropFromAnnotation != 0 }
+func (info *PropertyInfo) IsDeprecated() bool     { return info.Deprecated }
 
 type ConstInfo struct {
 	Pos         ElementPosition
 	Typ         types.Map
 	AccessLevel AccessLevel
 	Value       ConstValue
+	DeprecationInfo
 }
 
+func (info *ConstInfo) IsDeprecated() bool { return info.Deprecated }
+
 type ClassFlags uint8
 
 const (
@@ -212,12 +217,14 @@ type ClassInfo struct {
 	Mixins           []string
 
 	PackageInfo
+	DeprecationInfo
 }
 
-func (info *ClassInfo) IsAbstract() bool  { return info.Flags&ClassAbstract != 0 }
-func (info *ClassInfo) IsFinal() bool     { return info.Flags&ClassFinal != 0 }
-func (info *ClassInfo) IsShape() bool     { return info.Flags&ClassShape != 0 }
-func (info *ClassInfo) IsInterface() bool { return info.Flags&ClassInterface != 0 }
+func (info *ClassInfo) IsAbstract() bool   { return info.Flags&ClassAbstract != 0 }
+func (info *ClassInfo) IsFinal() bool      { return info.Flags&ClassFinal != 0 }
+func (info *ClassInfo) IsShape() bool      { return info.Flags&ClassShape != 0 }
+func (info *ClassInfo) IsInterface() bool  { return info.Flags&ClassInterface != 0 }
+func (info *ClassInfo) IsDeprecated() bool { return info.Deprecated }
 
 // TODO: rename it; it's not only class-related.
 type ClassParseState struct {


Test patch:
diff --git a/src/linter/cache_test.go b/src/linter/cache_test.go
index 8281dc4..773966a 100644
--- a/src/linter/cache_test.go
+++ b/src/linter/cache_test.go
@@ -149,7 +149,7 @@ main();
 		//
 		// If cache encoding changes, there is a very high chance that
 		// encoded data lengh will change as well.
-		wantLen := 5952
+		wantLen := 6069
 		haveLen := buf.Len()
 		if haveLen != wantLen {
 			t.Errorf("cache len mismatch:\nhave: %d\nwant: %d", haveLen, wantLen)
@@ -158,7 +158,7 @@ main();
 		// 2. Check cache "strings" hash.
 		//
 		// It catches new fields in cached types, field renames and encoding of additional named attributes.
-		wantStrings := "690e77c94ecdd7878de0bf6f6881d786cf1fafa4588f7905f54d700646c4952aad359008ae2dcddb1c7f29163ecee62355d525672090ac30257bc414f690006f"
+		wantStrings := "339e0a8348fcf8f5060b8ce3534f97be9af03fe462ca06eba27ab83488efc5e5d5813f4fb227535997f0c4a99253f57042876fdead6f058a923219c99593f3b6"
 		haveStrings := collectCacheStrings(buf.String())
 		if haveStrings != wantStrings {
 			t.Errorf("cache strings mismatch:\nhave: %q\nwant: %q", haveStrings, wantStrings)
diff --git a/src/tests/checkers/deprecated_test.go b/src/tests/checkers/deprecated_test.go
index 336e78c..7755ecb 100644
--- a/src/tests/checkers/deprecated_test.go
+++ b/src/tests/checkers/deprecated_test.go
@@ -83,15 +83,149 @@ declare(strict_types = "1");
 class OldClass
 {
 /**
- * @deprecated property
+ * @deprecated prp
  */
-public $prp;
+public $deprecated;
+
+public $notDeprecated;
+}
+
+$v = new OldClass();
+echo $v->deprecated;
+`)
+	test.Expect = []string{
+		"Try to create instance of the class \\OldClass that was marked as deprecated",
+		"Try to call property deprecated that was marked as deprecated",
+	}
+	test.RunAndMatch()
+}
+
+func TestDeprecatedClassPropertyConstants(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+
+/**
+ * @deprecated class
+ */
+class SomeClass {
+
+  /**
+   * @deprecated Use NEW_ONE instead
+   *
+   * @see SomeClass::NEW_ONE Replacement
+   */
+  public const OLD_ONE = 1;
+
+  public const NEW_ONE = 1;
+
+  /** @deprecated */
+  public const OLD_TWO = 2;
+  public const NEW_TWO = 2;
+
+  /** @deprecated */
+  public $a = 5;
+
+  /**
+   * @deprecated Use xyz() instead
+   */
+  public static function abc() {
+
+  }
+
+  /** Some phpdoc to hide notice */
+  public static function xyz() {
+
+  }
+}
+
+$b = new SomeClass();
+
+echo $b->a;
+
+echo SomeClass::OLD_ONE;
+
+echo SomeClass::OLD_TWO;
+echo SomeClass::abc();
+
+`)
+	test.Expect = []string{
+		"Try to create instance of the class \\SomeClass that was marked as deprecated",
+		"Try to call property a that was marked as deprecated in the class \\SomeClass",
+		"Try to call constant OLD_ONE that was marked as deprecated",
+		"Try to call constant OLD_TWO that was marked as deprecated",
+		"Call to deprecated static method \\SomeClass::abc()",
+	}
+	test.RunAndMatch()
+}
+
+func TestDeprecatedCallWithChainPropertyAndExplicitType(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+
+class Baz {
+    /**
+     * @deprecated Use method getValue() instead of this property.
+     */
+    public string $value = 'Hello, World!';
+
+    public function getValue(): string {
+        return 'Hello from new method!';
+    }
+}
+
+class Bar {
+    public Baz $baz;
+}
+
+class Foo {
+    public Bar $bar;
+}
+
+$a = new Foo();
+$a->bar = new Bar();
+$a->bar->baz = new Baz();
+
+echo $a->bar->baz->value;
+
+`)
+	test.Expect = []string{
+		"Missing PHPDoc for \\Baz::getValue public",
+		"Try to call property value that was marked as deprecated",
+	}
+	test.RunAndMatch()
+}
+
+// TODO: This test must fail after realisation CFG and DFG, because we can type inference correctly
+func TestDeprecatedCallWithChainPropertyAndWithoutExplicitType(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+class Baz {
+    /**
+     * @deprecated Use method getValue() instead of this property
+     */
+    public $value = 'Hello, World!';
+
+    public function getValue() {
+        return 'Hello from new method!';
+    }
+}
+
+class Bar {
+    public $baz;
+}
+
+class Foo {
+    public $bar;
 }
 
+$a = new Foo();
+$a->bar = new Bar();
+$a->bar->baz = new Baz();
+
+echo $a->bar->baz->value;
 `)
 	test.Expect = []string{
-		"Has deprecated class OldClass",
-		"Has deprecated field in class OldClass",
+		"Missing PHPDoc for \\Baz::getValue public method",
 	}
 	test.RunAndMatch()
 }
diff --git a/src/tests/golden/testdata/flysystem/golden.txt b/src/tests/golden/testdata/flysystem/golden.txt
index 7ca6977..1e0d351 100644
--- a/src/tests/golden/testdata/flysystem/golden.txt
+++ b/src/tests/golden/testdata/flysystem/golden.txt
@@ -400,18 +400,15 @@ 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
-^
 WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'path' at testdata/flysystem/src/Directory.php:17
         return $this->filesystem->deleteDir($this->path);
                                             ^^^^^^^^^^^
 WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'path' at testdata/flysystem/src/Directory.php:29
         return $this->filesystem->listContents($this->path, $recursive);
                                                ^^^^^^^^^^^
-MAYBE   deprecated: Has deprecated class File at testdata/flysystem/src/File.php:8
-class File extends Handler
-^
+MAYBE   deprecated: Try to create instance \League\Flysystem\File class that was marked as deprecated at testdata/flysystem/src/File.php:140
+            return new File($this->filesystem, $newpath);
+                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 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)
                                                   ^^^^^^^^^^^^^
@@ -439,15 +436,18 @@ MAYBE   typeHint: Specify the type for the parameter $config in PHPDoc, 'array'
 MAYBE   typeHint: Specify the type for the parameter $config in PHPDoc, 'array' type hint too generic at testdata/flysystem/src/Filesystem.php:257
     public function createDir($dirname, array $config = [])
                     ^^^^^^^^^
+MAYBE   deprecated: Try to create instance \League\Flysystem\File class that was marked as deprecated at testdata/flysystem/src/Filesystem.php:368
+            $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path);
+                                                                     ^^^^^^^^^^^^^^^^^^^^^^
+MAYBE   deprecated: Try to create instance \League\Flysystem\Directory class that was marked as deprecated at testdata/flysystem/src/Filesystem.php:368
+            $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path);
+                                                                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/flysystem/src/Filesystem.php:362
     public function get($path, Handler $handler = null)
                                ^^^^^^^
 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 notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'path' at testdata/flysystem/src/Handler.php:61
         $metadata = $this->filesystem->getMetadata($this->path);
                                                    ^^^^^^^^^^^
@@ -484,7 +484,7 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
 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) {
                                          ^^
-MAYBE   deprecatedUntagged: Call to deprecated method {\League\Flysystem\FilesystemInterface}->get() at testdata/flysystem/src/MountManager.php:646
+MAYBE   deprecated: Call to deprecated method {\League\Flysystem\FilesystemInterface}->get() at testdata/flysystem/src/MountManager.php:646
         return $this->getFilesystem($prefix)->get($path);
                                               ^^^
 WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/flysystem/src/MountManager.php:642
diff --git a/src/tests/golden/testdata/inflector/golden.txt b/src/tests/golden/testdata/inflector/golden.txt
index 0cd1a41..81f9cea 100644
--- a/src/tests/golden/testdata/inflector/golden.txt
+++ b/src/tests/golden/testdata/inflector/golden.txt
@@ -1,6 +1,3 @@
-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);
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^