diff --git a/docs/checkers_doc.md b/docs/checkers_doc.md
index 1c28d347..b7f9f0e3 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() {
-### `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();
-```
-
-
-
### `errorSilence` checker
#### Description
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 66cd5c13..ace77db1 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 d5580e6f..eca069d5 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 bf66e43b..c16c9198 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 and list
-// 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 and list
+// 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/cache_test.go b/src/linter/cache_test.go
index 8281dc48..773966a4 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/linter/phpdoc_util.go b/src/linter/phpdoc_util.go
index 5d794927..51beb21a 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 c4c9c62f..0af6f29d 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 0fb275f2..4b40ceae 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 f7b12e0d..c3c98522 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 38f88c00..ea5cf34c 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 {
diff --git a/src/tests/checkers/deprecated_test.go b/src/tests/checkers/deprecated_test.go
index 336e78c1..7755ecb3 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(`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(`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(`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 7ca6977a..1e0d3510 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 0cd1a414..81f9ceab 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);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^