bench-forgejo/vendor/github.com/mgechev/revive/rule/struct-tag.go
6543 792b4dba2c
[Vendor] Update directly used dependencys (#15593)
* update github.com/blevesearch/bleve v2.0.2 -> v2.0.3

* github.com/denisenkom/go-mssqldb v0.9.0 -> v0.10.0

* github.com/editorconfig/editorconfig-core-go v2.4.1 -> v2.4.2

* github.com/go-chi/cors v1.1.1 -> v1.2.0

* github.com/go-git/go-billy v5.0.0 -> v5.1.0

* github.com/go-git/go-git v5.2.0 -> v5.3.0

* github.com/go-ldap/ldap v3.2.4 -> v3.3.0

* github.com/go-redis/redis v8.6.0 -> v8.8.2

* github.com/go-sql-driver/mysql v1.5.0 -> v1.6.0

* github.com/go-swagger/go-swagger v0.26.1 -> v0.27.0

* github.com/lib/pq v1.9.0 -> v1.10.1

* github.com/mattn/go-sqlite3 v1.14.6 -> v1.14.7

* github.com/go-testfixtures/testfixtures v3.5.0 -> v3.6.0

* github.com/issue9/identicon v1.0.1 -> v1.2.0

* github.com/klauspost/compress v1.11.8 -> v1.12.1

* github.com/mgechev/revive v1.0.3 -> v1.0.6

* github.com/microcosm-cc/bluemonday v1.0.7 -> v1.0.8

* github.com/niklasfasching/go-org v1.4.0 -> v1.5.0

* github.com/olivere/elastic v7.0.22 -> v7.0.24

* github.com/pelletier/go-toml v1.8.1 -> v1.9.0

* github.com/prometheus/client_golang v1.9.0 -> v1.10.0

* github.com/xanzy/go-gitlab v0.44.0 -> v0.48.0

* github.com/yuin/goldmark v1.3.3 -> v1.3.5

* github.com/6543/go-version v1.2.4 -> v1.3.1

* do github.com/lib/pq v1.10.0 -> v1.10.1 again ...
2021-04-22 20:08:53 -04:00

237 lines
5.2 KiB
Go
Vendored

package rule
import (
"fmt"
"go/ast"
"strconv"
"strings"
"github.com/fatih/structtag"
"github.com/mgechev/revive/lint"
)
// StructTagRule lints struct tags.
type StructTagRule struct{}
// Apply applies the rule to given file.
func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintStructTagRule{onFailure: onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *StructTagRule) Name() string {
return "struct-tag"
}
type lintStructTagRule struct {
onFailure func(lint.Failure)
usedTagNbr map[string]bool // list of used tag numbers
}
func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.StructType:
if n.Fields == nil || n.Fields.NumFields() < 1 {
return nil // skip empty structs
}
w.usedTagNbr = map[string]bool{} // init
for _, f := range n.Fields.List {
if f.Tag != nil {
w.checkTaggedField(f)
}
}
}
return w
}
// checkTaggedField checks the tag of the given field.
// precondition: the field has a tag
func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
if len(f.Names) > 0 && !f.Names[0].IsExported() {
w.addFailure(f, "tag on not-exported field "+f.Names[0].Name)
}
tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
if err != nil || tags == nil {
w.addFailure(f.Tag, "malformed tag")
return
}
for _, tag := range tags.Tags() {
switch key := tag.Key; key {
case "asn1":
msg, ok := w.checkASN1Tag(f.Type, tag)
if !ok {
w.addFailure(f.Tag, msg)
}
case "bson":
msg, ok := w.checkBSONTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
case "default":
if !w.typeValueMatch(f.Type, tag.Name) {
w.addFailure(f.Tag, "field's type and default value's type mismatch")
}
case "json":
msg, ok := w.checkJSONTag(tag.Name, tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
case "protobuf":
// Not implemented yet
case "required":
if tag.Name != "true" && tag.Name != "false" {
w.addFailure(f.Tag, "required should be 'true' or 'false'")
}
case "xml":
msg, ok := w.checkXMLTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
case "yaml":
msg, ok := w.checkYAMLTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
default:
// unknown key
}
}
}
func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string, bool) {
checkList := append(tag.Options, tag.Name)
for _, opt := range checkList {
switch opt {
case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8":
default:
if strings.HasPrefix(opt, "tag:") {
parts := strings.Split(opt, ":")
tagNumber := parts[1]
if w.usedTagNbr[tagNumber] {
return fmt.Sprintf("duplicated tag number %s", tagNumber), false
}
w.usedTagNbr[tagNumber] = true
continue
}
if strings.HasPrefix(opt, "default:") {
parts := strings.Split(opt, ":")
if len(parts) < 2 {
return "malformed default for ASN1 tag", false
}
if !w.typeValueMatch(t, parts[1]) {
return "field's type and default value's type mismatch", false
}
continue
}
return fmt.Sprintf("unknown option '%s' in ASN1 tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkBSONTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "inline", "minsize", "omitempty":
default:
return fmt.Sprintf("unknown option '%s' in BSON tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkJSONTag(name string, options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "omitempty", "string":
case "":
// special case for JSON key "-"
if name != "-" {
return "option can not be empty in JSON tag", false
}
default:
return fmt.Sprintf("unknown option '%s' in JSON tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkXMLTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr":
default:
return fmt.Sprintf("unknown option '%s' in XML tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "flow", "inline", "omitempty":
default:
return fmt.Sprintf("unknown option '%s' in YAML tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
tID, ok := t.(*ast.Ident)
if !ok {
return true
}
typeMatches := true
switch tID.Name {
case "bool":
typeMatches = val == "true" || val == "false"
case "float64":
_, err := strconv.ParseFloat(val, 64)
typeMatches = err == nil
case "int":
_, err := strconv.ParseInt(val, 10, 64)
typeMatches = err == nil
case "string":
case "nil":
default:
// unchecked type
}
return typeMatches
}
func (w lintStructTagRule) addFailure(n ast.Node, msg string) {
w.onFailure(lint.Failure{
Node: n,
Failure: msg,
Confidence: 1,
})
}