GoPLS Viewer

Home|gopls/go/expect/expect.go
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/*
6Package expect provides support for interpreting structured comments in Go
7source code as test expectations.
8
9This is primarily intended for writing tests of things that process Go source
10files, although it does not directly depend on the testing package.
11
12Collect notes with the Extract or Parse functions, and use the
13MatchBefore function to find matches within the lines the comments were on.
14
15The interpretation of the notes depends on the application.
16For example, the test suite for a static checking tool might
17use a @diag note to indicate an expected diagnostic:
18
19    fmt.Printf("%s", 1) //@ diag("%s wants a string, got int")
20
21By contrast, the test suite for a source code navigation tool
22might use notes to indicate the positions of features of
23interest, the actions to be performed by the test,
24and their expected outcomes:
25
26    var x = 1 //@ x_decl
27    ...
28    print(x) //@ definition("x", x_decl)
29    print(x) //@ typeof("x", "int")
30
31# Note comment syntax
32
33Note comments always start with the special marker @, which must be the
34very first character after the comment opening pair, so //@ or /*@ with no
35spaces.
36
37This is followed by a comma separated list of notes.
38
39A note always starts with an identifier, which is optionally followed by an
40argument list. The argument list is surrounded with parentheses and contains a
41comma-separated list of arguments.
42The empty parameter list and the missing parameter list are distinguishable if
43needed; they result in a nil or an empty list in the Args parameter respectively.
44
45Arguments are either identifiers or literals.
46The literals supported are the basic value literals, of string, float, integer
47true, false or nil. All the literals match the standard go conventions, with
48all bases of integers, and both quote and backtick strings.
49There is one extra literal type, which is a string literal preceded by the
50identifier "re" which is compiled to a regular expression.
51*/
52package expect
53
54import (
55    "bytes"
56    "fmt"
57    "go/token"
58    "regexp"
59)
60
61// Note is a parsed note from an expect comment.
62// It knows the position of the start of the comment, and the name and
63// arguments that make up the note.
64type Note struct {
65    Pos  token.Pos     // The position at which the note identifier appears
66    Name string        // the name associated with the note
67    Args []interface{} // the arguments for the note
68}
69
70// ReadFile is the type of a function that can provide file contents for a
71// given filename.
72// This is used in MatchBefore to look up the content of the file in order to
73// find the line to match the pattern against.
74type ReadFile func(filename string) ([]byteerror)
75
76// MatchBefore attempts to match a pattern in the line before the supplied pos.
77// It uses the FileSet and the ReadFile to work out the contents of the line
78// that end is part of, and then matches the pattern against the content of the
79// start of that line up to the supplied position.
80// The pattern may be either a simple string, []byte or a *regexp.Regexp.
81// MatchBefore returns the range of the line that matched the pattern, and
82// invalid positions if there was no match, or an error if the line could not be
83// found.
84func MatchBefore(fset *token.FileSetreadFile ReadFileend token.Pospattern interface{}) (token.Postoken.Poserror) {
85    f := fset.File(end)
86    contenterr := readFile(f.Name())
87    if err != nil {
88        return token.NoPostoken.NoPosfmt.Errorf("invalid file: %v"err)
89    }
90    position := f.Position(end)
91    startOffset := f.Offset(f.LineStart(position.Line))
92    endOffset := f.Offset(end)
93    line := content[startOffset:endOffset]
94    matchStartmatchEnd := -1, -1
95    switch pattern := pattern.(type) {
96    case string:
97        bytePattern := []byte(pattern)
98        matchStart = bytes.Index(linebytePattern)
99        if matchStart >= 0 {
100            matchEnd = matchStart + len(bytePattern)
101        }
102    case []byte:
103        matchStart = bytes.Index(linepattern)
104        if matchStart >= 0 {
105            matchEnd = matchStart + len(pattern)
106        }
107    case *regexp.Regexp:
108        match := pattern.FindIndex(line)
109        if len(match) > 0 {
110            matchStart = match[0]
111            matchEnd = match[1]
112        }
113    }
114    if matchStart < 0 {
115        return token.NoPostoken.NoPosnil
116    }
117    return f.Pos(startOffset + matchStart), f.Pos(startOffset + matchEnd), nil
118}
119
120func lineEnd(f *token.Fileline inttoken.Pos {
121    if line >= f.LineCount() {
122        return token.Pos(f.Base() + f.Size())
123    }
124    return f.LineStart(line + 1)
125}
126
MembersX
ReadFile
MatchBefore.end
MatchBefore.startOffset
MatchBefore.BlockStmt.bytePattern
lineEnd.f
fmt
regexp
Note
Note.Name
Note.Args
MatchBefore.pattern
MatchBefore.content
MatchBefore.endOffset
bytes
MatchBefore.readFile
MatchBefore.f
MatchBefore.err
lineEnd.line
Note.Pos
MatchBefore
MatchBefore.fset
MatchBefore.position
MatchBefore.BlockStmt.match
lineEnd
token
Members
X