|  | // Copyright 2018 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | /* | 
|  | Package expect provides support for interpreting structured comments in Go | 
|  | source code as test expectations. | 
|  |  | 
|  | This is primarily intended for writing tests of things that process Go source | 
|  | files, although it does not directly depend on the testing package. | 
|  |  | 
|  | Collect notes with the Extract or Parse functions, and use the | 
|  | MatchBefore function to find matches within the lines the comments were on. | 
|  |  | 
|  | The interpretation of the notes depends on the application. | 
|  | For example, the test suite for a static checking tool might | 
|  | use a @diag note to indicate an expected diagnostic: | 
|  |  | 
|  | fmt.Printf("%s", 1) //@ diag("%s wants a string, got int") | 
|  |  | 
|  | By contrast, the test suite for a source code navigation tool | 
|  | might use notes to indicate the positions of features of | 
|  | interest, the actions to be performed by the test, | 
|  | and their expected outcomes: | 
|  |  | 
|  | var x = 1 //@ x_decl | 
|  | ... | 
|  | print(x) //@ definition("x", x_decl) | 
|  | print(x) //@ typeof("x", "int") | 
|  |  | 
|  | # Note comment syntax | 
|  |  | 
|  | Note comments always start with the special marker @, which must be the | 
|  | very first character after the comment opening pair, so //@ or /*@ with no | 
|  | spaces. | 
|  |  | 
|  | This is followed by a comma separated list of notes. | 
|  |  | 
|  | A note always starts with an identifier, which is optionally followed by an | 
|  | argument list. The argument list is surrounded with parentheses and contains a | 
|  | comma-separated list of arguments. | 
|  | The empty parameter list and the missing parameter list are distinguishable if | 
|  | needed; they result in a nil or an empty list in the Args parameter respectively. | 
|  |  | 
|  | Arguments are either identifiers or literals. | 
|  | The literals supported are the basic value literals, of string, float, integer | 
|  | true, false or nil. All the literals match the standard go conventions, with | 
|  | all bases of integers, and both quote and backtick strings. | 
|  | There is one extra literal type, which is a string literal preceded by the | 
|  | identifier "re" which is compiled to a regular expression. | 
|  | */ | 
|  | package expect | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "go/token" | 
|  | "regexp" | 
|  | ) | 
|  |  | 
|  | // Note is a parsed note from an expect comment. | 
|  | // It knows the position of the start of the comment, and the name and | 
|  | // arguments that make up the note. | 
|  | type Note struct { | 
|  | Pos  token.Pos     // The position at which the note identifier appears | 
|  | Name string        // the name associated with the note | 
|  | Args []interface{} // the arguments for the note | 
|  | } | 
|  |  | 
|  | // ReadFile is the type of a function that can provide file contents for a | 
|  | // given filename. | 
|  | // This is used in MatchBefore to look up the content of the file in order to | 
|  | // find the line to match the pattern against. | 
|  | type ReadFile func(filename string) ([]byte, error) | 
|  |  | 
|  | // MatchBefore attempts to match a pattern in the line before the supplied pos. | 
|  | // It uses the FileSet and the ReadFile to work out the contents of the line | 
|  | // that end is part of, and then matches the pattern against the content of the | 
|  | // start of that line up to the supplied position. | 
|  | // The pattern may be either a simple string, []byte or a *regexp.Regexp. | 
|  | // MatchBefore returns the range of the line that matched the pattern, and | 
|  | // invalid positions if there was no match, or an error if the line could not be | 
|  | // found. | 
|  | func MatchBefore(fset *token.FileSet, readFile ReadFile, end token.Pos, pattern interface{}) (token.Pos, token.Pos, error) { | 
|  | f := fset.File(end) | 
|  | content, err := readFile(f.Name()) | 
|  | if err != nil { | 
|  | return token.NoPos, token.NoPos, fmt.Errorf("invalid file: %v", err) | 
|  | } | 
|  | position := f.Position(end) | 
|  | startOffset := f.Offset(f.LineStart(position.Line)) | 
|  | endOffset := f.Offset(end) | 
|  | line := content[startOffset:endOffset] | 
|  | matchStart, matchEnd := -1, -1 | 
|  | switch pattern := pattern.(type) { | 
|  | case string: | 
|  | bytePattern := []byte(pattern) | 
|  | matchStart = bytes.Index(line, bytePattern) | 
|  | if matchStart >= 0 { | 
|  | matchEnd = matchStart + len(bytePattern) | 
|  | } | 
|  | case []byte: | 
|  | matchStart = bytes.Index(line, pattern) | 
|  | if matchStart >= 0 { | 
|  | matchEnd = matchStart + len(pattern) | 
|  | } | 
|  | case *regexp.Regexp: | 
|  | match := pattern.FindIndex(line) | 
|  | if len(match) > 0 { | 
|  | matchStart = match[0] | 
|  | matchEnd = match[1] | 
|  | } | 
|  | } | 
|  | if matchStart < 0 { | 
|  | return token.NoPos, token.NoPos, nil | 
|  | } | 
|  | return f.Pos(startOffset + matchStart), f.Pos(startOffset + matchEnd), nil | 
|  | } | 
|  |  | 
|  | func lineEnd(f *token.File, line int) token.Pos { | 
|  | if line >= f.LineCount() { | 
|  | return token.Pos(f.Base() + f.Size()) | 
|  | } | 
|  | return f.LineStart(line + 1) | 
|  | } |