github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/cmd/tast-lint/internal/check/common.go (about) 1 // Copyright 2018 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 "bytes" 9 "fmt" 10 "go/ast" 11 "go/format" 12 "go/parser" 13 "go/token" 14 "os" 15 "path" 16 "path/filepath" 17 "regexp" 18 "runtime" 19 "strconv" 20 "strings" 21 ) 22 23 // entryPathRegexp matches a file name of an entry file. 24 // verifier is a global singleton to check if AddTest() is used as designed. 25 var tastTestsPattern = `/src/go\.chromium\.org/tast-tests/cros/(local|remote)/bundles/[^/]+/[^/]+/[^/]+\.go$` 26 var tastTestsPrivatePattern = `/src/go\.chromium\.org/tast-tests-private/crosint/(local|remote)/bundles/[^/]+/[^/]+/[^/]+\.go$` 27 28 var entryPathRegexp = regexp.MustCompile(fmt.Sprintf("(%s)|(%s)", tastTestsPattern, tastTestsPrivatePattern)) 29 30 func fileExists(path string) bool { 31 _, err := os.Stat(path) 32 return !os.IsNotExist(err) 33 } 34 35 // isEntryFile checks if path is an entry file. 36 func isEntryFile(path string) bool { 37 var err error 38 exPath := path 39 40 // Unit tests rely on providing fake paths. EvalSymlinks returns an error 41 // if the path doesn't exist. To work around this, only evaluate symlinks 42 // if the path exists 43 if fileExists(exPath) { 44 exPath, err = filepath.EvalSymlinks(exPath) 45 if err != nil { 46 return false 47 } 48 } 49 50 exPath, err = filepath.Abs(exPath) 51 if err != nil { 52 return false 53 } 54 55 return entryPathRegexp.MatchString(exPath) && 56 !isUnitTestFile(exPath) && 57 filepath.Base(exPath) != "doc.go" // exclude package documentation 58 } 59 60 // isUnitTestFile returns true if the supplied path corresponds to a unit test file. 61 func isUnitTestFile(path string) bool { 62 return strings.HasSuffix(path, "_test.go") 63 } 64 65 // funcVisitor is an implementation of ast.Visitor to scan all nodes. 66 type funcVisitor func(node ast.Node) 67 68 func (v funcVisitor) Visit(node ast.Node) ast.Visitor { 69 if node == nil { 70 return nil 71 } 72 v(node) 73 return v 74 } 75 76 // toQualifiedName stringifies the given node, which is either 77 // - an ast.Ident node 78 // - an ast.SelectorExpr node whose .X node is convertible by toQualifiedName. 79 // If failed, returns an empty string. 80 func toQualifiedName(node ast.Node) string { 81 var comp []string 82 for { 83 s, ok := node.(*ast.SelectorExpr) 84 if !ok { 85 break 86 } 87 comp = append(comp, s.Sel.Name) 88 node = s.X 89 } 90 91 id, ok := node.(*ast.Ident) 92 if !ok { 93 return "" 94 } 95 comp = append(comp, id.Name) 96 97 // Reverse the comp, then join with '.'. 98 for i, j := 0, len(comp)-1; i < j; i, j = i+1, j-1 { 99 comp[i], comp[j] = comp[j], comp[i] 100 } 101 return strings.Join(comp, ".") 102 } 103 104 // stringLitType represent raw string literal or interpreted string literal 105 // as enumerated value. 106 type stringLitType int 107 108 const ( 109 rawStringLit stringLitType = iota 110 interpretedStringLit 111 ) 112 113 // stringLitTypeOf returns the string literal type of s. If s does not belong 114 // to either raw string literal or interpreted string literal, returns false for ok. 115 func stringLitTypeOf(s string) (strtype stringLitType, ok bool) { 116 if s == "" { 117 return 0, false 118 } 119 quote := s[0] 120 if quote != s[len(s)-1] { 121 return 0, false 122 } 123 if quote == '`' { 124 return rawStringLit, true 125 } else if quote == '"' { 126 return interpretedStringLit, true 127 } 128 return 0, false 129 } 130 131 // quoteAs quotes given unquoted string with double quote or back quote, 132 // based on stringLiteralType value. 133 // If specified to be backquoted, but it is impossible, this function 134 // falls back to the quoting by double-quotes. 135 func quoteAs(s string, t stringLitType) string { 136 if t == rawStringLit && strconv.CanBackquote(s) { 137 return "`" + s + "`" 138 } 139 return strconv.Quote(s) 140 } 141 142 // formatASTNode returns the byte slice of source code from given file nodes. 143 func formatASTNode(fs *token.FileSet, f *ast.File) ([]byte, error) { 144 var buf bytes.Buffer 145 if err := format.Node(&buf, fs, f); err != nil { 146 return nil, err 147 } 148 return buf.Bytes(), nil 149 } 150 151 // policyNames retrieves the policy names from the defs.go definition file. It 152 // returns a map where they key represent the name of the policy, and the value 153 // it a bool which is true for those policies that have the Name function 154 // defined, false otherwise. 155 func policyNames() map[string]bool { 156 // Get the path to the common file. 157 _, filename, _, ok := runtime.Caller(0) 158 if !ok { 159 panic("Could not get caller information") 160 } 161 162 src := strings.Split(filename, "platform")[0] 163 // Build defs path. 164 defs := path.Join(src, "platform/tast-tests/src/go.chromium.org/tast-tests/cros/common/policy/defs.go") 165 if !fileExists(defs) { 166 panic(fmt.Sprintf("Path %s does not exist", defs)) 167 } 168 169 fs := token.NewFileSet() 170 file, err := parser.ParseFile(fs, defs, nil, parser.ParseComments) 171 if err != nil { 172 panic(err) 173 } 174 175 m := make(map[string]bool) 176 177 for _, decl := range file.Decls { 178 genDecl, ok := decl.(*ast.GenDecl) 179 // Check if declaration is a type definition. 180 if ok && genDecl.Tok == token.TYPE && len(genDecl.Specs) == 1 { 181 typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec) 182 if ok { 183 m[typeSpec.Name.Name] = false 184 } 185 186 continue 187 } 188 189 funcDecl, ok := decl.(*ast.FuncDecl) 190 // Check if the declaration is the Name function definition implemented for 191 // each policy. 192 if ok && funcDecl.Name.Name == "Name" && len(funcDecl.Body.List) == 1 { 193 returnStmt, ok := funcDecl.Body.List[0].(*ast.ReturnStmt) 194 if ok && len(returnStmt.Results) == 1 { 195 basicLit, ok := returnStmt.Results[0].(*ast.BasicLit) 196 if ok && basicLit.Kind == token.STRING { 197 policyName := basicLit.Value[1 : len(basicLit.Value)-1] 198 _, ok = m[policyName] 199 if ok { 200 m[policyName] = true 201 } 202 } 203 } 204 } 205 } 206 207 return m 208 } 209 210 // union adds all key-value pairs from maps a and b. If a and b have an equal key 211 // then the value from a will be kept. 212 func union[K comparable, V any](a, b map[K]V) map[K]V { 213 c := make(map[K]V) 214 for k, v := range a { 215 c[k] = v 216 } 217 218 for k, v := range b { 219 _, present := c[k] 220 if !present { 221 c[k] = v 222 } 223 } 224 225 return c 226 }