1
0
mirror of https://github.com/mgechev/revive.git synced 2025-05-21 22:13:14 +02:00
revive/test/utils_test.go

364 lines
9.7 KiB
Go
Raw Normal View History

2018-02-04 14:51:19 -08:00
package test
2018-01-23 18:46:13 -08:00
import (
"encoding/json"
2018-01-23 18:46:13 -08:00
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
2018-01-23 18:46:13 -08:00
"strconv"
"strings"
"testing"
2018-01-24 15:44:03 -08:00
"github.com/mgechev/revive/lint"
2018-01-23 18:46:13 -08:00
)
// configureRule configures the given rule with the given configuration
// if the rule implements the ConfigurableRule interface
func configureRule(t *testing.T, rule lint.Rule, arguments lint.Arguments) {
t.Helper()
cr, ok := rule.(lint.ConfigurableRule)
if !ok {
return
}
err := cr.Configure(arguments)
if err != nil {
t.Fatalf("Cannot configure rule %s: %v", rule.Name(), err)
}
}
func testRule(t *testing.T, filename string, rule lint.Rule, config ...*lint.RuleConfig) {
refactor: fix linting issues (#1188) * refactor: fix thelper issues test/utils_test.go:19:6 thelper test helper function should start from t.Helper() test/utils_test.go:42:6 thelper test helper function should start from t.Helper() test/utils_test.go:63:6 thelper test helper function should start from t.Helper() test/utils_test.go:146:6 thelper test helper function should start from t.Helper() * refactor: fix govet issues rule/error_strings.go:50:21 govet printf: non-constant format string in call to fmt.Errorf * refactor: fix gosimple issue rule/bare_return.go:52:9 gosimple S1016: should convert w (type lintBareReturnRule) to bareReturnFinder instead of using struct literal * refactor: fix errorlint issues lint/filefilter.go:70:86 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:113:104 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:125:89 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:166:72 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:171:73 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:174:57 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:179:64 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors * refactor: fix revive issue about comment spacing cli/main.go:31:2 revive comment-spacings: no space between comment delimiter and comment text * refactor: fix revive issue about unused-receiver revivelib/core_test.go:77:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ revivelib/core_test.go:81:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/context_as_argument.go:76:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/var_naming.go:73:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/modifies_value_receiver.go:59:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/filename_format.go:43:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ * refactor: fix revive issues about unused-parameter revivelib/core_test.go:81:24 revive unused-parameter: parameter 'file' seems to be unused, consider removing or renaming it as _ revivelib/core_test.go:81:41 revive unused-parameter: parameter 'arguments' seems to be unused, consider removing or renaming it as _ * refactor: fix gocritic issues about commentedOutCode test/utils_test.go:119:5 gocritic commentedOutCode: may want to remove commented-out code rule/unreachable_code.go:65:3 gocritic commentedOutCode: may want to remove commented-out code
2024-12-12 08:42:41 +01:00
t.Helper()
baseDir := filepath.Join("..", "testdata", filepath.Dir(filename))
filename = filepath.Base(filename) + ".go"
fullFilePath := filepath.Join(baseDir, filename)
src, err := os.ReadFile(fullFilePath)
if err != nil {
t.Fatalf("Bad filename path in test for %s: %v", rule.Name(), err)
}
stat, err := os.Stat(fullFilePath)
if err != nil {
t.Fatalf("Cannot get file info for %s: %v", rule.Name(), err)
}
var ruleConfig lint.RuleConfig
2018-02-04 14:51:19 -08:00
c := map[string]lint.RuleConfig{}
if len(config) > 0 {
ruleConfig = *config[0]
c[rule.Name()] = ruleConfig
}
configureRule(t, rule, ruleConfig.Arguments)
if parseInstructions(t, fullFilePath, src) == nil {
assertSuccess(t, baseDir, stat, []lint.Rule{rule}, c)
return
}
assertFailures(t, baseDir, stat, src, []lint.Rule{rule}, c)
}
func assertSuccess(t *testing.T, baseDir string, fi os.FileInfo, rules []lint.Rule, config map[string]lint.RuleConfig) error {
refactor: fix linting issues (#1188) * refactor: fix thelper issues test/utils_test.go:19:6 thelper test helper function should start from t.Helper() test/utils_test.go:42:6 thelper test helper function should start from t.Helper() test/utils_test.go:63:6 thelper test helper function should start from t.Helper() test/utils_test.go:146:6 thelper test helper function should start from t.Helper() * refactor: fix govet issues rule/error_strings.go:50:21 govet printf: non-constant format string in call to fmt.Errorf * refactor: fix gosimple issue rule/bare_return.go:52:9 gosimple S1016: should convert w (type lintBareReturnRule) to bareReturnFinder instead of using struct literal * refactor: fix errorlint issues lint/filefilter.go:70:86 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:113:104 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:125:89 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:166:72 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:171:73 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:174:57 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:179:64 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors * refactor: fix revive issue about comment spacing cli/main.go:31:2 revive comment-spacings: no space between comment delimiter and comment text * refactor: fix revive issue about unused-receiver revivelib/core_test.go:77:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ revivelib/core_test.go:81:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/context_as_argument.go:76:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/var_naming.go:73:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/modifies_value_receiver.go:59:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/filename_format.go:43:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ * refactor: fix revive issues about unused-parameter revivelib/core_test.go:81:24 revive unused-parameter: parameter 'file' seems to be unused, consider removing or renaming it as _ revivelib/core_test.go:81:41 revive unused-parameter: parameter 'arguments' seems to be unused, consider removing or renaming it as _ * refactor: fix gocritic issues about commentedOutCode test/utils_test.go:119:5 gocritic commentedOutCode: may want to remove commented-out code rule/unreachable_code.go:65:3 gocritic commentedOutCode: may want to remove commented-out code
2024-12-12 08:42:41 +01:00
t.Helper()
2024-11-04 14:25:03 +02:00
l := lint.New(os.ReadFile, 0)
2018-02-04 14:51:19 -08:00
filePath := filepath.Join(baseDir, fi.Name())
ps, err := l.Lint([][]string{{filePath}}, rules, lint.Config{
2018-02-04 14:51:19 -08:00
Rules: config,
})
if err != nil {
return err
}
failures := ""
for p := range ps {
failures += p.Failure
}
if failures != "" {
t.Errorf("Expected the rule to pass but got the following failures: %s", failures)
}
return nil
}
func assertFailures(t *testing.T, baseDir string, fi os.FileInfo, src []byte, rules []lint.Rule, config map[string]lint.RuleConfig) error {
refactor: fix linting issues (#1188) * refactor: fix thelper issues test/utils_test.go:19:6 thelper test helper function should start from t.Helper() test/utils_test.go:42:6 thelper test helper function should start from t.Helper() test/utils_test.go:63:6 thelper test helper function should start from t.Helper() test/utils_test.go:146:6 thelper test helper function should start from t.Helper() * refactor: fix govet issues rule/error_strings.go:50:21 govet printf: non-constant format string in call to fmt.Errorf * refactor: fix gosimple issue rule/bare_return.go:52:9 gosimple S1016: should convert w (type lintBareReturnRule) to bareReturnFinder instead of using struct literal * refactor: fix errorlint issues lint/filefilter.go:70:86 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:113:104 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:125:89 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:166:72 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:171:73 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:174:57 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:179:64 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors * refactor: fix revive issue about comment spacing cli/main.go:31:2 revive comment-spacings: no space between comment delimiter and comment text * refactor: fix revive issue about unused-receiver revivelib/core_test.go:77:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ revivelib/core_test.go:81:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/context_as_argument.go:76:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/var_naming.go:73:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/modifies_value_receiver.go:59:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/filename_format.go:43:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ * refactor: fix revive issues about unused-parameter revivelib/core_test.go:81:24 revive unused-parameter: parameter 'file' seems to be unused, consider removing or renaming it as _ revivelib/core_test.go:81:41 revive unused-parameter: parameter 'arguments' seems to be unused, consider removing or renaming it as _ * refactor: fix gocritic issues about commentedOutCode test/utils_test.go:119:5 gocritic commentedOutCode: may want to remove commented-out code rule/unreachable_code.go:65:3 gocritic commentedOutCode: may want to remove commented-out code
2024-12-12 08:42:41 +01:00
t.Helper()
2024-11-04 14:25:03 +02:00
l := lint.New(os.ReadFile, 0)
2018-01-23 18:46:13 -08:00
ins := parseInstructions(t, filepath.Join(baseDir, fi.Name()), src)
ps, err := l.Lint([][]string{{filepath.Join(baseDir, fi.Name())}}, rules, lint.Config{
2018-01-27 16:37:30 -08:00
Rules: config,
})
if err != nil {
return err
}
2018-01-23 18:46:13 -08:00
failures := []lint.Failure{}
for f := range ps {
failures = append(failures, f)
}
for _, in := range ins {
ok := false
for i, p := range failures {
if p.Position.Start.Line != in.Line {
continue
2018-01-23 18:46:13 -08:00
}
if in.Match == p.Failure {
// check replacement if we are expecting one
if in.Replacement != "" {
// ignore any inline comments, since that would be recursive
r := p.ReplacementLine
if i := strings.Index(r, " //"); i >= 0 {
r = r[:i]
}
if r != in.Replacement {
t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement)
}
}
if in.Confidence > 0 {
if in.Confidence != p.Confidence {
t.Errorf("Lint failed at %s:%d; got confidence %f, want %f", fi.Name(), in.Line, p.Confidence, in.Confidence)
}
}
// remove this problem from ps
copy(failures[i:], failures[i+1:])
failures = failures[:len(failures)-1]
ok = true
break
2018-01-23 18:46:13 -08:00
}
}
if !ok {
t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match)
2018-01-23 18:46:13 -08:00
}
}
for _, p := range failures {
t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Start.Line, p.Failure)
}
return nil
2018-01-23 18:46:13 -08:00
}
type instruction struct {
Line int // the line number this applies to
Match string // which pattern to match
Replacement string // what the suggested replacement line should be
RuleName string // what rule we use
Category string // which category
Confidence float64 // confidence level
}
2024-09-14 22:38:06 +03:00
// JSONInstruction structure used when we parse json object instead of classic MATCH string
type JSONInstruction struct {
Match string `json:"MATCH"`
Category string `json:"Category"`
Confidence float64 `json:"Confidence"`
2018-01-23 18:46:13 -08:00
}
// parseInstructions parses instructions from the comments in a Go source file.
// It returns nil if none were parsed.
func parseInstructions(t *testing.T, filename string, src []byte) []instruction {
refactor: fix linting issues (#1188) * refactor: fix thelper issues test/utils_test.go:19:6 thelper test helper function should start from t.Helper() test/utils_test.go:42:6 thelper test helper function should start from t.Helper() test/utils_test.go:63:6 thelper test helper function should start from t.Helper() test/utils_test.go:146:6 thelper test helper function should start from t.Helper() * refactor: fix govet issues rule/error_strings.go:50:21 govet printf: non-constant format string in call to fmt.Errorf * refactor: fix gosimple issue rule/bare_return.go:52:9 gosimple S1016: should convert w (type lintBareReturnRule) to bareReturnFinder instead of using struct literal * refactor: fix errorlint issues lint/filefilter.go:70:86 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:113:104 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:125:89 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:166:72 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:171:73 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:174:57 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:179:64 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors * refactor: fix revive issue about comment spacing cli/main.go:31:2 revive comment-spacings: no space between comment delimiter and comment text * refactor: fix revive issue about unused-receiver revivelib/core_test.go:77:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ revivelib/core_test.go:81:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/context_as_argument.go:76:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/var_naming.go:73:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/modifies_value_receiver.go:59:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/filename_format.go:43:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ * refactor: fix revive issues about unused-parameter revivelib/core_test.go:81:24 revive unused-parameter: parameter 'file' seems to be unused, consider removing or renaming it as _ revivelib/core_test.go:81:41 revive unused-parameter: parameter 'arguments' seems to be unused, consider removing or renaming it as _ * refactor: fix gocritic issues about commentedOutCode test/utils_test.go:119:5 gocritic commentedOutCode: may want to remove commented-out code rule/unreachable_code.go:65:3 gocritic commentedOutCode: may want to remove commented-out code
2024-12-12 08:42:41 +01:00
t.Helper()
2018-01-23 18:46:13 -08:00
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
if err != nil {
t.Fatalf("Test file %v does not parse: %v", filename, err)
}
var ins []instruction
for _, cg := range f.Comments {
ln := fset.Position(cg.Pos()).Line
raw := cg.Text()
for _, line := range strings.Split(raw, "\n") {
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "ignore") {
2018-01-23 18:46:13 -08:00
continue
}
if line == "OK" && ins == nil {
// so our return value will be non-nil
ins = []instruction{}
2018-01-23 18:46:13 -08:00
continue
}
switch extractDataMode(line) {
case "json":
jsonInst, err := extractInstructionFromJSON(strings.TrimPrefix(line, "json:"), ln)
if err != nil {
t.Fatalf("At %v:%d: %v", filename, ln, err)
}
ins = append(ins, jsonInst)
case "classic":
2018-01-23 18:46:13 -08:00
match, err := extractPattern(line)
if err != nil {
t.Fatalf("At %v:%d: %v", filename, ln, err)
}
matchLine := ln
if i := strings.Index(line, "MATCH:"); i >= 0 {
// This is a match for a different line.
lns := strings.TrimPrefix(line[i:], "MATCH:")
lns = lns[:strings.Index(lns, " ")]
matchLine, err = strconv.Atoi(lns)
if err != nil {
t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err)
}
}
var repl string
if r, ok := extractReplacement(line); ok {
repl = r
}
ins = append(ins, instruction{
Line: matchLine,
Match: match,
Replacement: repl,
})
}
}
}
return ins
}
func extractInstructionFromJSON(line string, lineNumber int) (instruction, error) {
// Use the json.Unmarshal function to parse the JSON into the struct
var jsonInst JSONInstruction
if err := json.Unmarshal([]byte(line), &jsonInst); err != nil {
return instruction{}, fmt.Errorf("parsing json instruction: %w", err)
}
ins := instruction{
Match: jsonInst.Match,
Confidence: jsonInst.Confidence,
Category: jsonInst.Category,
Line: lineNumber,
}
return ins, nil
}
func extractDataMode(line string) string {
if strings.HasPrefix(line, "json") {
return "json"
}
if strings.Contains(line, "MATCH") {
return "classic"
}
return ""
}
2018-01-23 18:46:13 -08:00
func extractPattern(line string) (string, error) {
a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
if a == -1 || a == b {
return "", fmt.Errorf("malformed match instruction %q", line)
}
return line[a+1 : b], nil
}
func extractReplacement(line string) (string, bool) {
// Look for this: / -> `
// (the end of a match and start of a backtick string),
// and then the closing backtick.
const start = "/ -> `"
a, b := strings.Index(line, start), strings.LastIndex(line, "`")
if a < 0 || a > b {
return "", false
}
return line[a+len(start) : b], true
}
func srcLine(src []byte, p token.Position) string {
// Run to end of line in both directions if not at line start/end.
lo, hi := p.Offset, p.Offset+1
for lo > 0 && src[lo-1] != '\n' {
lo--
}
for hi < len(src) && src[hi-1] != '\n' {
hi++
}
return string(src[lo:hi])
}
// TestLine tests srcLine function
func TestLine(t *testing.T) {
2018-01-23 18:46:13 -08:00
tests := []struct {
src string
offset int
want string
}{
{"single line file", 5, "single line file"},
{"single line file with newline\n", 5, "single line file with newline\n"},
{"first\nsecond\nthird\n", 2, "first\n"},
{"first\nsecond\nthird\n", 9, "second\n"},
{"first\nsecond\nthird\n", 14, "third\n"},
{"first\nsecond\nthird with no newline", 16, "third with no newline"},
{"first byte\n", 0, "first byte\n"},
}
for _, test := range tests {
got := srcLine([]byte(test.src), token.Position{Offset: test.offset})
if got != test.want {
t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want)
}
}
}
// exportedType reports whether typ is an exported type.
// It is imprecise, and will err on the side of returning true,
// such as for composite types.
func exportedType(typ types.Type) bool {
2022-04-10 11:55:13 +02:00
switch t := typ.(type) {
2018-01-23 18:46:13 -08:00
case *types.Named:
// Builtin types have no package.
2022-04-10 11:55:13 +02:00
return t.Obj().Pkg() == nil || t.Obj().Exported()
2018-01-23 18:46:13 -08:00
case *types.Map:
2022-04-10 11:55:13 +02:00
return exportedType(t.Key()) && exportedType(t.Elem())
2018-01-23 18:46:13 -08:00
case interface {
Elem() types.Type
}: // array, slice, pointer, chan
2022-04-10 11:55:13 +02:00
return exportedType(t.Elem())
2018-01-23 18:46:13 -08:00
}
// Be conservative about other types, such as struct, interface, etc.
return true
}
// TestExportedType tests exportedType function
func TestExportedType(t *testing.T) {
2018-01-23 18:46:13 -08:00
tests := []struct {
typString string
exp bool
}{
{"int", true},
{"string", false}, // references the shadowed builtin "string"
{"T", true},
{"t", false},
{"*T", true},
{"*t", false},
{"map[int]complex128", true},
}
for _, test := range tests {
src := `package foo; type T int; type t int; type string struct{}`
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "foo.go", src, 0)
if err != nil {
t.Fatalf("Parsing %q: %v", src, err)
}
// use the package name as package path
config := &types.Config{}
pkg, err := config.Check(file.Name.Name, fset, []*ast.File{file}, nil)
if err != nil {
t.Fatalf("Type checking %q: %v", src, err)
}
tv, err := types.Eval(fset, pkg, token.NoPos, test.typString)
if err != nil {
t.Errorf("types.Eval(%q): %v", test.typString, err)
continue
}
if got := exportedType(tv.Type); got != test.exp {
t.Errorf("exportedType(%v) = %t, want %t", tv.Type, got, test.exp)
}
}
}