gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/go/expect/extract.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package expect 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/parser" 11 "go/token" 12 "regexp" 13 "strconv" 14 "strings" 15 "text/scanner" 16 ) 17 18 const ( 19 commentStart = "@" 20 ) 21 22 // Identifier is the type for an identifier in an Note argument list. 23 type Identifier string 24 25 // Parse collects all the notes present in a file. 26 // If content is nil, the filename specified is read and parsed, otherwise the 27 // content is used and the filename is used for positions and error messages. 28 // Each comment whose text starts with @ is parsed as a comma-separated 29 // sequence of notes. 30 // See the package documentation for details about the syntax of those 31 // notes. 32 func Parse(fset *token.FileSet, filename string, content []byte) ([]*Note, error) { 33 var src interface{} 34 if content != nil { 35 src = content 36 } 37 // TODO: We should write this in terms of the scanner. 38 // there are ways you can break the parser such that it will not add all the 39 // comments to the ast, which may result in files where the tests are silently 40 // not run. 41 file, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 42 if file == nil { 43 return nil, err 44 } 45 return Extract(fset, file) 46 } 47 48 // Extract collects all the notes present in an AST. 49 // Each comment whose text starts with @ is parsed as a comma-separated 50 // sequence of notes. 51 // See the package documentation for details about the syntax of those 52 // notes. 53 func Extract(fset *token.FileSet, file *ast.File) ([]*Note, error) { 54 var notes []*Note 55 for _, g := range file.Comments { 56 for _, c := range g.List { 57 text := c.Text 58 if strings.HasPrefix(text, "/*") { 59 text = strings.TrimSuffix(text, "*/") 60 } 61 text = text[2:] // remove "//" or "/*" prefix 62 if !strings.HasPrefix(text, commentStart) { 63 continue 64 } 65 text = text[len(commentStart):] 66 parsed, err := parse(fset, c.Pos()+4, text) 67 if err != nil { 68 return nil, err 69 } 70 notes = append(notes, parsed...) 71 } 72 } 73 return notes, nil 74 } 75 76 const invalidToken rune = 0 77 78 type tokens struct { 79 scanner scanner.Scanner 80 current rune 81 err error 82 base token.Pos 83 } 84 85 func (t *tokens) Init(base token.Pos, text string) *tokens { 86 t.base = base 87 t.scanner.Init(strings.NewReader(text)) 88 t.scanner.Mode = scanner.GoTokens 89 t.scanner.Whitespace ^= 1 << '\n' // don't skip new lines 90 t.scanner.Error = func(s *scanner.Scanner, msg string) { 91 t.Errorf("%v", msg) 92 } 93 return t 94 } 95 96 func (t *tokens) Consume() string { 97 t.current = invalidToken 98 return t.scanner.TokenText() 99 } 100 101 func (t *tokens) Token() rune { 102 if t.err != nil { 103 return scanner.EOF 104 } 105 if t.current == invalidToken { 106 t.current = t.scanner.Scan() 107 } 108 return t.current 109 } 110 111 func (t *tokens) Skip(r rune) int { 112 i := 0 113 for t.Token() == '\n' { 114 t.Consume() 115 i++ 116 } 117 return i 118 } 119 120 func (t *tokens) TokenString() string { 121 return scanner.TokenString(t.Token()) 122 } 123 124 func (t *tokens) Pos() token.Pos { 125 return t.base + token.Pos(t.scanner.Position.Offset) 126 } 127 128 func (t *tokens) Errorf(msg string, args ...interface{}) { 129 if t.err != nil { 130 return 131 } 132 t.err = fmt.Errorf(msg, args...) 133 } 134 135 func parse(fset *token.FileSet, base token.Pos, text string) ([]*Note, error) { 136 t := new(tokens).Init(base, text) 137 notes := parseComment(t) 138 if t.err != nil { 139 return nil, fmt.Errorf("%v:%s", fset.Position(t.Pos()), t.err) 140 } 141 return notes, nil 142 } 143 144 func parseComment(t *tokens) []*Note { 145 var notes []*Note 146 for { 147 t.Skip('\n') 148 switch t.Token() { 149 case scanner.EOF: 150 return notes 151 case scanner.Ident: 152 notes = append(notes, parseNote(t)) 153 default: 154 t.Errorf("unexpected %s parsing comment, expect identifier", t.TokenString()) 155 return nil 156 } 157 switch t.Token() { 158 case scanner.EOF: 159 return notes 160 case ',', '\n': 161 t.Consume() 162 default: 163 t.Errorf("unexpected %s parsing comment, expect separator", t.TokenString()) 164 return nil 165 } 166 } 167 } 168 169 func parseNote(t *tokens) *Note { 170 n := &Note{ 171 Pos: t.Pos(), 172 Name: t.Consume(), 173 } 174 175 switch t.Token() { 176 case ',', '\n', scanner.EOF: 177 // no argument list present 178 return n 179 case '(': 180 n.Args = parseArgumentList(t) 181 return n 182 default: 183 t.Errorf("unexpected %s parsing note", t.TokenString()) 184 return nil 185 } 186 } 187 188 func parseArgumentList(t *tokens) []interface{} { 189 args := []interface{}{} // @name() is represented by a non-nil empty slice. 190 t.Consume() // '(' 191 t.Skip('\n') 192 for t.Token() != ')' { 193 args = append(args, parseArgument(t)) 194 if t.Token() != ',' { 195 break 196 } 197 t.Consume() 198 t.Skip('\n') 199 } 200 if t.Token() != ')' { 201 t.Errorf("unexpected %s parsing argument list", t.TokenString()) 202 return nil 203 } 204 t.Consume() // ')' 205 return args 206 } 207 208 func parseArgument(t *tokens) interface{} { 209 switch t.Token() { 210 case scanner.Ident: 211 v := t.Consume() 212 switch v { 213 case "true": 214 return true 215 case "false": 216 return false 217 case "nil": 218 return nil 219 case "re": 220 if t.Token() != scanner.String && t.Token() != scanner.RawString { 221 t.Errorf("re must be followed by string, got %s", t.TokenString()) 222 return nil 223 } 224 pattern, _ := strconv.Unquote(t.Consume()) // can't fail 225 re, err := regexp.Compile(pattern) 226 if err != nil { 227 t.Errorf("invalid regular expression %s: %v", pattern, err) 228 return nil 229 } 230 return re 231 default: 232 return Identifier(v) 233 } 234 235 case scanner.String, scanner.RawString: 236 v, _ := strconv.Unquote(t.Consume()) // can't fail 237 return v 238 239 case scanner.Int: 240 s := t.Consume() 241 v, err := strconv.ParseInt(s, 0, 0) 242 if err != nil { 243 t.Errorf("cannot convert %v to int: %v", s, err) 244 } 245 return v 246 247 case scanner.Float: 248 s := t.Consume() 249 v, err := strconv.ParseFloat(s, 64) 250 if err != nil { 251 t.Errorf("cannot convert %v to float: %v", s, err) 252 } 253 return v 254 255 case scanner.Char: 256 t.Errorf("unexpected char literal %s", t.Consume()) 257 return nil 258 259 default: 260 t.Errorf("unexpected %s parsing argument", t.TokenString()) 261 return nil 262 } 263 }