зеркало из https://github.com/golang/tools.git
internal/expect: support named arguments f(a, b, c=d, e="f")
+ test Change-Id: I6c44e32d34bcdf1ca68e8989e99595f691c88329 Reviewed-on: https://go-review.googlesource.com/c/tools/+/626016 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Alan Donovan <adonovan@google.com>
This commit is contained in:
Родитель
0b9e4998c4
Коммит
1115af6fe6
|
@ -42,7 +42,11 @@ 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.
|
||||
Arguments may be positional, such as f(value), or named, such as f(name=value).
|
||||
Positional arguments must appear before named arguments.
|
||||
Names may not be repeated.
|
||||
|
||||
Argument values may be 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.
|
||||
|
@ -62,9 +66,10 @@ import (
|
|||
// 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
|
||||
Pos token.Pos // The position at which the note identifier appears
|
||||
Name string // the name associated with the note
|
||||
Args []any // positional arguments (non-nil if parens were present)
|
||||
NamedArgs map[string]any // named arguments (or nil if none)
|
||||
}
|
||||
|
||||
// ReadFile is the type of a function that can provide file contents for a
|
||||
|
@ -116,10 +121,3 @@ func MatchBefore(fset *token.FileSet, readFile ReadFile, end token.Pos, pattern
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"bytes"
|
||||
"go/token"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/expect"
|
||||
|
@ -18,11 +20,13 @@ func TestMarker(t *testing.T) {
|
|||
filename string
|
||||
expectNotes int
|
||||
expectMarkers map[string]string
|
||||
expectChecks map[string][]interface{}
|
||||
expectChecks map[string][]any
|
||||
// expectChecks holds {"id": values} for each call check(id, values...).
|
||||
// Any named k=v arguments become a final map[string]any argument.
|
||||
}{
|
||||
{
|
||||
filename: "testdata/test.go",
|
||||
expectNotes: 13,
|
||||
expectNotes: 14,
|
||||
expectMarkers: map[string]string{
|
||||
"αSimpleMarker": "α",
|
||||
"OffsetMarker": "β",
|
||||
|
@ -36,10 +40,15 @@ func TestMarker(t *testing.T) {
|
|||
"NonIdentifier": "+",
|
||||
"StringMarker": "\"hello\"",
|
||||
},
|
||||
expectChecks: map[string][]interface{}{
|
||||
expectChecks: map[string][]any{
|
||||
"αSimpleMarker": nil,
|
||||
"StringAndInt": {"Number %d", int64(12)},
|
||||
"Bool": {true},
|
||||
"NamedArgs": {int64(1), true, expect.Identifier("a"), map[string]any{
|
||||
"b": int64(1),
|
||||
"c": "3",
|
||||
"d": true,
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -79,7 +88,7 @@ func TestMarker(t *testing.T) {
|
|||
fset := token.NewFileSet()
|
||||
notes, err := expect.Parse(fset, tt.filename, content)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to extract notes: %v", err)
|
||||
t.Fatalf("Failed to extract notes:\n%v", err)
|
||||
}
|
||||
if len(notes) != tt.expectNotes {
|
||||
t.Errorf("Expected %v notes, got %v", tt.expectNotes, len(notes))
|
||||
|
@ -99,7 +108,7 @@ func TestMarker(t *testing.T) {
|
|||
}
|
||||
ident, ok := n.Args[0].(expect.Identifier)
|
||||
if !ok {
|
||||
t.Errorf("%v: identifier, got %T", fset.Position(n.Pos), n.Args[0])
|
||||
t.Errorf("%v: got %v (%T), want identifier", fset.Position(n.Pos), n.Args[0], n.Args[0])
|
||||
continue
|
||||
}
|
||||
checkMarker(t, fset, readFile, markers, n.Pos, string(ident), n.Args[1])
|
||||
|
@ -115,21 +124,27 @@ func TestMarker(t *testing.T) {
|
|||
}
|
||||
ident, ok := n.Args[0].(expect.Identifier)
|
||||
if !ok {
|
||||
t.Errorf("%v: identifier, got %T", fset.Position(n.Pos), n.Args[0])
|
||||
t.Errorf("%v: got %v (%T), want identifier", fset.Position(n.Pos), n.Args[0], n.Args[0])
|
||||
continue
|
||||
}
|
||||
args, ok := tt.expectChecks[string(ident)]
|
||||
wantArgs, ok := tt.expectChecks[string(ident)]
|
||||
if !ok {
|
||||
t.Errorf("%v: unexpected check %v", fset.Position(n.Pos), ident)
|
||||
continue
|
||||
}
|
||||
if len(n.Args) != len(args)+1 {
|
||||
t.Errorf("%v: expected %v args to check, got %v", fset.Position(n.Pos), len(args)+1, len(n.Args))
|
||||
gotArgs := n.Args[1:]
|
||||
if n.NamedArgs != nil {
|
||||
// Clip to avoid mutating Args' array.
|
||||
gotArgs = append(slices.Clip(gotArgs), n.NamedArgs)
|
||||
}
|
||||
|
||||
if len(gotArgs) != len(wantArgs) {
|
||||
t.Errorf("%v: expected %v args to check, got %v", fset.Position(n.Pos), len(wantArgs), len(gotArgs))
|
||||
continue
|
||||
}
|
||||
for i, got := range n.Args[1:] {
|
||||
if args[i] != got {
|
||||
t.Errorf("%v: arg %d expected %v, got %v", fset.Position(n.Pos), i, args[i], got)
|
||||
for i := range gotArgs {
|
||||
if !reflect.DeepEqual(wantArgs[i], gotArgs[i]) {
|
||||
t.Errorf("%v: arg %d: expected %#v, got %#v", fset.Position(n.Pos), i+1, wantArgs[i], gotArgs[i])
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -231,7 +231,7 @@ func parse(fset *token.FileSet, base token.Pos, text string) ([]*Note, error) {
|
|||
t := new(tokens).Init(base, text)
|
||||
notes := parseComment(t)
|
||||
if t.err != nil {
|
||||
return nil, fmt.Errorf("%v:%s", fset.Position(t.Pos()), t.err)
|
||||
return nil, fmt.Errorf("%v: %s", fset.Position(t.Pos()), t.err)
|
||||
}
|
||||
return notes, nil
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ func parseNote(t *tokens) *Note {
|
|||
// no argument list present
|
||||
return n
|
||||
case '(':
|
||||
n.Args = parseArgumentList(t)
|
||||
n.Args, n.NamedArgs = parseArgumentList(t)
|
||||
return n
|
||||
default:
|
||||
t.Errorf("unexpected %s parsing note", t.TokenString())
|
||||
|
@ -280,12 +280,30 @@ func parseNote(t *tokens) *Note {
|
|||
}
|
||||
}
|
||||
|
||||
func parseArgumentList(t *tokens) []interface{} {
|
||||
args := []interface{}{} // @name() is represented by a non-nil empty slice.
|
||||
t.Consume() // '('
|
||||
func parseArgumentList(t *tokens) (args []any, named map[string]any) {
|
||||
args = []any{} // @name() is represented by a non-nil empty slice.
|
||||
t.Consume() // '('
|
||||
t.Skip('\n')
|
||||
for t.Token() != ')' {
|
||||
args = append(args, parseArgument(t))
|
||||
name, arg := parseArgument(t)
|
||||
if name != "" {
|
||||
// f(k=v)
|
||||
if named == nil {
|
||||
named = make(map[string]any)
|
||||
}
|
||||
if _, dup := named[name]; dup {
|
||||
t.Errorf("duplicate named argument %q", name)
|
||||
return nil, nil
|
||||
}
|
||||
named[name] = arg
|
||||
} else {
|
||||
// f(v)
|
||||
if named != nil {
|
||||
t.Errorf("positional argument follows named argument")
|
||||
return nil, nil
|
||||
}
|
||||
args = append(args, arg)
|
||||
}
|
||||
if t.Token() != ',' {
|
||||
break
|
||||
}
|
||||
|
@ -294,42 +312,50 @@ func parseArgumentList(t *tokens) []interface{} {
|
|||
}
|
||||
if t.Token() != ')' {
|
||||
t.Errorf("unexpected %s parsing argument list", t.TokenString())
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
t.Consume() // ')'
|
||||
return args
|
||||
return args, named
|
||||
}
|
||||
|
||||
func parseArgument(t *tokens) interface{} {
|
||||
// parseArgument returns the value of the argument ("f(value)"),
|
||||
// and its name if named "f(name=value)".
|
||||
func parseArgument(t *tokens) (name string, value any) {
|
||||
again:
|
||||
switch t.Token() {
|
||||
case scanner.Ident:
|
||||
v := t.Consume()
|
||||
switch v {
|
||||
case "true":
|
||||
return true
|
||||
value = true
|
||||
case "false":
|
||||
return false
|
||||
value = false
|
||||
case "nil":
|
||||
return nil
|
||||
value = nil
|
||||
case "re":
|
||||
if t.Token() != scanner.String && t.Token() != scanner.RawString {
|
||||
t.Errorf("re must be followed by string, got %s", t.TokenString())
|
||||
return nil
|
||||
return
|
||||
}
|
||||
pattern, _ := strconv.Unquote(t.Consume()) // can't fail
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
t.Errorf("invalid regular expression %s: %v", pattern, err)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
return re
|
||||
value = re
|
||||
default:
|
||||
return Identifier(v)
|
||||
// f(name=value)?
|
||||
if name == "" && t.Token() == '=' {
|
||||
t.Consume() // '='
|
||||
name = v
|
||||
goto again
|
||||
}
|
||||
value = Identifier(v)
|
||||
}
|
||||
|
||||
case scanner.String, scanner.RawString:
|
||||
v, _ := strconv.Unquote(t.Consume()) // can't fail
|
||||
return v
|
||||
value, _ = strconv.Unquote(t.Consume()) // can't fail
|
||||
|
||||
case scanner.Int:
|
||||
s := t.Consume()
|
||||
|
@ -337,7 +363,7 @@ func parseArgument(t *tokens) interface{} {
|
|||
if err != nil {
|
||||
t.Errorf("cannot convert %v to int: %v", s, err)
|
||||
}
|
||||
return v
|
||||
value = v
|
||||
|
||||
case scanner.Float:
|
||||
s := t.Consume()
|
||||
|
@ -345,14 +371,13 @@ func parseArgument(t *tokens) interface{} {
|
|||
if err != nil {
|
||||
t.Errorf("cannot convert %v to float: %v", s, err)
|
||||
}
|
||||
return v
|
||||
value = v
|
||||
|
||||
case scanner.Char:
|
||||
t.Errorf("unexpected char literal %s", t.Consume())
|
||||
return nil
|
||||
|
||||
default:
|
||||
t.Errorf("unexpected %s parsing argument", t.TokenString())
|
||||
return nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -37,4 +37,6 @@ check(StringAndInt,
|
|||
)
|
||||
|
||||
check(Bool, true)
|
||||
|
||||
check(NamedArgs, 1, true, a, b=1, c="3", d=true)
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче