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

dynamic rules: new @link tag #1232

Merged
merged 7 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
70 changes: 36 additions & 34 deletions src/linter/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,42 @@ import (
// cacheVersions is a magic number that helps to distinguish incompatible caches.
//
// 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
//
// 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 - added @link tag -> new field in Rule
const cacheVersion = 54

var (
Expand Down
2 changes: 1 addition & 1 deletion src/linter/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ main();
// 1. Check cache contents length.
//
// If cache encoding changes, there is a very high chance that
// encoded data lengh will change as well.
// encoded data length will change as well.
wantLen := 5953
haveLen := buf.Len()
if haveLen != wantLen {
Expand Down
5 changes: 5 additions & 0 deletions src/linter/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,11 @@ func (d *rootWalker) runRule(n ir.Node, sc *meta.Scope, rule *rules.Rule) bool {
}

message := d.renderRuleMessage(rule.Message, n, m, true)

if rule.Link != "" {
message += " | More about this rule: " + rule.Link
}

d.Report(location, rule.Level, rule.Name, "%s", message)

if d.config.ApplyQuickFixes && rule.Fix != "" {
Expand Down
16 changes: 15 additions & 1 deletion src/rules/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ func (p *parser) parseRuleInfo(st ir.Node, labelStmt ir.Node, proto *Rule) (Rule
}
rule.Name = part.Params[0]

case "link":
if len(part.Params) != 1 {
return rule, p.errorf(st, "@link expects exactly 1 param, got %d", len(part.Params))
}
var link = part.Params[0]
if !isURL(link) {
return rule, p.errorf(st, "@link argument is not link")
}
rule.Link = link

case "location":
if len(part.Params) != 1 {
return rule, p.errorf(st, "@location expects exactly 1 params, got %d", len(part.Params))
Expand Down Expand Up @@ -318,7 +328,6 @@ func (p *parser) parseRuleInfo(st ir.Node, labelStmt ir.Node, proto *Rule) (Rule
}
filter.Regexp = regex
filterSet[name] = filter

default:
return rule, p.errorf(st, "unknown attribute @%s on line %d", part.Name(), part.Line())
}
Expand All @@ -339,6 +348,11 @@ func (p *parser) parseRuleInfo(st ir.Node, labelStmt ir.Node, proto *Rule) (Rule
return rule, nil
}

func isURL(str string) bool {
re := regexp.MustCompile(`^((https?|ftp)://)?[^\s/$.?#].\S*$`)
return re.MatchString(str)
}

func (p *parser) parseRules(stmts []ir.Node, proto *Rule) error {
for len(stmts) > 0 {
stmt := stmts[0]
Expand Down
3 changes: 3 additions & 0 deletions src/rules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type Rule struct {
// Name tells whether this rule causes critical report.
Name string

// Link to the documentation about rule
Link string

// Matcher is an object that is used to check whether a given AST node
// should trigger a warning that is associated with rule.
Matcher *phpgrep.Matcher
Expand Down
27 changes: 27 additions & 0 deletions src/tests/rules/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,33 @@ function f() {
test.RunRulesTest()
}

func TestLinkTag(t *testing.T) {
rfile := `<?php
/**
* @name emptyIf
* @warning suspicious empty body of the if statement
* @scope local
* @link goodrule.com
*/
if ($_);
`

test := linttest.NewSuite(t)
test.RuleFile = rfile
test.AddFile(`<?php
if (123); // No warning

function f() {
if (123); // Warning
}
`)

test.Expect = []string{
` | More about this rule: goodrule.com`,
}
test.RunRulesTest()
}

func TestRootRules(t *testing.T) {
rfile := `<?php
/**
Expand Down
Loading