github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/cmd/tast-lint/internal/check/check_informational.go (about) 1 // Copyright 2019 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package check 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/token" 11 ) 12 13 const ( 14 shouldBeInformationalMsg = `Newly added tests should be marked as 'informational'.` 15 ) 16 17 // VerifyInformationalAttr checks if a newly added test has 'informational' attribute. 18 func VerifyInformationalAttr(fs *token.FileSet, f *ast.File) []*Issue { 19 var issues []*Issue 20 21 for _, decl := range f.Decls { 22 fn, ok := decl.(*ast.FuncDecl) 23 if !ok || fn.Recv != nil || fn.Name.Name != "init" { 24 // Not an init() function declaration. Skip. 25 continue 26 } 27 for _, stmt := range fn.Body.List { 28 estmt, ok := stmt.(*ast.ExprStmt) 29 if !ok || !isTestingAddTestCall(estmt.X) { 30 continue 31 } 32 call := estmt.X.(*ast.CallExpr) 33 if len(call.Args) != 1 { 34 // This should be checked by a compiler, so skipped. 35 continue 36 } 37 // Verify the argument is "&testing.Test{...}" 38 arg, ok := call.Args[0].(*ast.UnaryExpr) 39 if !ok || arg.Op != token.AND { 40 continue 41 } 42 comp, ok := arg.X.(*ast.CompositeLit) 43 if !ok { 44 continue 45 } 46 47 var attrs []string 48 var attrPos token.Pos 49 var paramNode ast.Node 50 for _, el := range comp.Elts { 51 identName, value, pos, err := decomposeKVNode(el) 52 if err != nil { 53 continue 54 } 55 if identName == "Attr" { 56 attrs = makeSliceAttrs(value) 57 attrPos = pos 58 } 59 if identName == "Params" { 60 paramNode = value 61 } 62 } 63 64 if paramNode != nil { 65 comp, ok := paramNode.(*ast.CompositeLit) 66 if !ok { 67 // This should be checked by another lint, so skipped. 68 continue 69 } 70 for _, el := range comp.Elts { 71 comp, ok := el.(*ast.CompositeLit) 72 if !ok { 73 continue 74 } 75 var exAttrs []string 76 var exAttrPos token.Pos 77 for _, el := range comp.Elts { 78 identName, value, pos, err := decomposeKVNode(el) 79 if err != nil { 80 continue 81 } 82 if identName == "ExtraAttr" { 83 exAttrs = makeSliceAttrs(value) 84 exAttrPos = pos 85 } 86 } 87 exAttrs = append(exAttrs, attrs...) 88 if isCriticalTest(exAttrs) { 89 issues = append(issues, &Issue{ 90 Pos: fs.Position(exAttrPos), 91 Msg: shouldBeInformationalMsg, 92 Link: testRegistrationURL, 93 }) 94 } 95 } 96 } else { 97 if isCriticalTest(attrs) { 98 issues = append(issues, &Issue{ 99 Pos: fs.Position(attrPos), 100 Msg: shouldBeInformationalMsg, 101 Link: testRegistrationURL, 102 }) 103 } 104 } 105 } 106 } 107 return issues 108 } 109 110 // decomposeKVNode returns the name of identifier, node of keyvalue and its position. 111 func decomposeKVNode(el ast.Node) (string, ast.Node, token.Pos, error) { 112 kv, ok := el.(*ast.KeyValueExpr) 113 if !ok { 114 return "", nil, token.NoPos, fmt.Errorf("unexpected input") 115 } 116 ident, ok := kv.Key.(*ast.Ident) 117 if !ok { 118 return "", kv.Value, kv.Pos(), fmt.Errorf("unexpected input") 119 } 120 return ident.Name, kv.Value, kv.Pos(), nil 121 } 122 123 // makeSliceAttrs make a string slice of elements in attribute. 124 // We do not check the non-string-literal elements. 125 func makeSliceAttrs(node ast.Node) []string { 126 var attrs []string 127 comp, ok := node.(*ast.CompositeLit) 128 if !ok { 129 return attrs 130 } 131 for _, el := range comp.Elts { 132 s, ok := toString(el) 133 if !ok { 134 continue 135 } 136 attrs = append(attrs, s) 137 } 138 return attrs 139 } 140 141 // isCriticalTest returns true if there are no 'informational' attribute 142 // in existing attributes and it is mainline test. 143 func isCriticalTest(attrs []string) bool { 144 isMainlineTest := false 145 isInformational := false 146 for _, attr := range attrs { 147 if attr == "informational" { 148 isInformational = true 149 } else if attr == "group:mainline" { 150 isMainlineTest = true 151 } 152 } 153 return isMainlineTest && !isInformational 154 }