Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linter: expanded deprecated for calling const, property and instances of classes #1248

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions src/linter/block_linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,13 +251,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)
}
Expand Down Expand Up @@ -563,6 +556,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, "deprecatedUntagged", "Try to create instance %s class that was marked as deprecated", class.Name)
}
}

b.walker.r.checker.CheckNameCase(e.Class, className, class.Name)
}

Expand Down Expand Up @@ -1298,7 +1300,7 @@ func (b *blockLinter) checkMethodCall(e *ir.MethodCallExpr) {
}
}

if call.info.Deprecated {
if call.info.IsDeprecated() {
deprecation := call.info.DeprecationInfo

if deprecation.WithDeprecationNote() {
Expand Down Expand Up @@ -1405,6 +1407,14 @@ func (b *blockLinter) checkPropertyFetch(e *ir.PropertyFetchExpr) {
if fetch.isFound && !fetch.isMagic && !canAccess(b.classParseState(), fetch.className, fetch.info.AccessLevel) {
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, "deprecatedUntagged", "Try to call property %s that was marked as deprecated in the class %s", fetch.propertyNode.Value, fetch.className)
}
}
}

func (b *blockLinter) checkStaticPropertyFetch(e *ir.StaticPropertyFetchExpr) {
Expand Down Expand Up @@ -1446,6 +1456,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, "deprecatedUntagged", "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) {
Expand Down
8 changes: 5 additions & 3 deletions src/linter/block_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions src/linter/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ import (
// 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
// 56 - added isVariadic to meta.FuncInfo
// 57 - added DeprecationInfo for property and const
const cacheVersion = 57

var (
errWrongVersion = errors.New("Wrong cache version")
Expand Down
4 changes: 2 additions & 2 deletions src/linter/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/linter/phpdoc_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ 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
}

func parseClassPHPDocMethod(classNode ir.Node, ctx *rootContext, result *classPHPDocParseResult, part *phpdoc.RawCommentPart) {
Expand Down
2 changes: 1 addition & 1 deletion src/linter/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ g();`,

{
Name: "deprecatedUntagged",
Default: false,
Default: true,
Quickfix: false,
Comment: "Report usages of deprecated symbols if the `@deprecated` tag has no description (see `deprecated` check).",
Before: `/**
Expand Down
73 changes: 58 additions & 15 deletions src/linter/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -902,19 +907,26 @@ func (d *rootWalker) handleClassDoc(doc classPHPDocParseResult, cl *meta.ClassIn
}
}

func (d *rootWalker) parsePHPDocVar(doc phpdoc.Comment) (typesMap types.Map) {
for _, part := range doc.Parsed {
part, ok := part.(*phpdoc.TypeVarCommentPart)
func (d *rootWalker) parsePHPDocVar(doc phpdoc.Comment) (typesMap types.Map, deprecationInfo meta.DeprecationInfo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeprecationInfo should not be part of parsePHPDocVar, even the function name says that on parses var tag.

Copy link
Contributor Author

@Hidanio Hidanio Mar 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case the contex of Var - variable (property). See parseConstPHPDoc, parseClassPHPDoc: in all cases we are parse everything

for _, partDoc := range doc.Parsed {
part, ok := partDoc.(*phpdoc.TypeVarCommentPart)
if ok && part.Name() == "var" {
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)
}

rawPart, rawOk := partDoc.(*phpdoc.RawCommentPart)

if rawOk && rawPart.Name() == "deprecated" {
deprecationInfo.Deprecated = true
deprecationInfo.Reason = rawPart.ParamsText
}
}

return typesMap
return typesMap, deprecationInfo
}

func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
Expand All @@ -936,9 +948,14 @@ func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
}
}

phpDocType := d.parsePHPDocVar(pl.Doc)
phpDocType, deprecationInfo := d.parsePHPDocVar(pl.Doc)
typeHintType, _ := d.parseTypeHintNode(pl.Type)

deprecation, ok := attributes.Deprecated(pl.AttrGroups, d.ctx.st)
if ok {
deprecationInfo.Append(deprecation)
}

for _, pNode := range pl.Properties {
prop := pNode.(*ir.PropertyStmt)

Expand All @@ -959,15 +976,33 @@ 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: deprecationInfo,
}
}

return true
}

func (d *rootWalker) parseConstPHPDoc(constList *ir.ClassConstListStmt, 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
Expand All @@ -984,6 +1019,13 @@ func (d *rootWalker) enterClassConstList(list *ir.ClassConstListStmt) bool {
}
}

deprecationInfo := d.parseConstPHPDoc(list, 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)

Expand All @@ -995,10 +1037,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,
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/linter/root_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ 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)
docblockType, _ := r.walker.parsePHPDocVar(pl.Doc)

r.CheckCommentMisspellings(pl, pl.Doc.Raw)
r.CheckPHPDocVar(pl, pl.Doc, docblockType)
Expand Down
15 changes: 11 additions & 4 deletions src/meta/metainfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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 {
Expand Down
Loading