| // Copyright 2017 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 syntax | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"strings" | 
 | 	"testing" | 
 | ) | 
 |  | 
 | // A test is a source code snippet of a particular node type. | 
 | // In the snippet, a '@' indicates the position recorded by | 
 | // the parser when creating the respective node. | 
 | type test struct { | 
 | 	nodetyp string | 
 | 	snippet string | 
 | } | 
 |  | 
 | var decls = []test{ | 
 | 	// The position of declarations is always the | 
 | 	// position of the first token of an individual | 
 | 	// declaration, independent of grouping. | 
 | 	{"ImportDecl", `import @"math"`}, | 
 | 	{"ImportDecl", `import @mymath "math"`}, | 
 | 	{"ImportDecl", `import @. "math"`}, | 
 | 	{"ImportDecl", `import (@"math")`}, | 
 | 	{"ImportDecl", `import (@mymath "math")`}, | 
 | 	{"ImportDecl", `import (@. "math")`}, | 
 |  | 
 | 	{"ConstDecl", `const @x`}, | 
 | 	{"ConstDecl", `const @x = 0`}, | 
 | 	{"ConstDecl", `const @x, y, z = 0, 1, 2`}, | 
 | 	{"ConstDecl", `const (@x)`}, | 
 | 	{"ConstDecl", `const (@x = 0)`}, | 
 | 	{"ConstDecl", `const (@x, y, z = 0, 1, 2)`}, | 
 |  | 
 | 	{"TypeDecl", `type @T int`}, | 
 | 	{"TypeDecl", `type @T = int`}, | 
 | 	{"TypeDecl", `type (@T int)`}, | 
 | 	{"TypeDecl", `type (@T = int)`}, | 
 |  | 
 | 	{"VarDecl", `var @x int`}, | 
 | 	{"VarDecl", `var @x, y, z int`}, | 
 | 	{"VarDecl", `var @x int = 0`}, | 
 | 	{"VarDecl", `var @x, y, z int = 1, 2, 3`}, | 
 | 	{"VarDecl", `var @x = 0`}, | 
 | 	{"VarDecl", `var @x, y, z = 1, 2, 3`}, | 
 | 	{"VarDecl", `var (@x int)`}, | 
 | 	{"VarDecl", `var (@x, y, z int)`}, | 
 | 	{"VarDecl", `var (@x int = 0)`}, | 
 | 	{"VarDecl", `var (@x, y, z int = 1, 2, 3)`}, | 
 | 	{"VarDecl", `var (@x = 0)`}, | 
 | 	{"VarDecl", `var (@x, y, z = 1, 2, 3)`}, | 
 |  | 
 | 	{"FuncDecl", `func @f() {}`}, | 
 | 	{"FuncDecl", `func @(T) f() {}`}, | 
 | 	{"FuncDecl", `func @(x T) f() {}`}, | 
 | } | 
 |  | 
 | var exprs = []test{ | 
 | 	// The position of an expression is the position | 
 | 	// of the left-most token that identifies the | 
 | 	// kind of expression. | 
 | 	{"Name", `@x`}, | 
 |  | 
 | 	{"BasicLit", `@0`}, | 
 | 	{"BasicLit", `@0x123`}, | 
 | 	{"BasicLit", `@3.1415`}, | 
 | 	{"BasicLit", `@.2718`}, | 
 | 	{"BasicLit", `@1i`}, | 
 | 	{"BasicLit", `@'a'`}, | 
 | 	{"BasicLit", `@"abc"`}, | 
 | 	{"BasicLit", "@`abc`"}, | 
 |  | 
 | 	{"CompositeLit", `@{}`}, | 
 | 	{"CompositeLit", `T@{}`}, | 
 | 	{"CompositeLit", `struct{x, y int}@{}`}, | 
 |  | 
 | 	{"KeyValueExpr", `"foo"@: true`}, | 
 | 	{"KeyValueExpr", `"a"@: b`}, | 
 |  | 
 | 	{"FuncLit", `@func (){}`}, | 
 | 	{"ParenExpr", `@(x)`}, | 
 | 	{"SelectorExpr", `a@.b`}, | 
 | 	{"IndexExpr", `a@[i]`}, | 
 |  | 
 | 	{"SliceExpr", `a@[:]`}, | 
 | 	{"SliceExpr", `a@[i:]`}, | 
 | 	{"SliceExpr", `a@[:j]`}, | 
 | 	{"SliceExpr", `a@[i:j]`}, | 
 | 	{"SliceExpr", `a@[i:j:k]`}, | 
 |  | 
 | 	{"AssertExpr", `x@.(T)`}, | 
 |  | 
 | 	{"Operation", `@*b`}, | 
 | 	{"Operation", `@+b`}, | 
 | 	{"Operation", `@-b`}, | 
 | 	{"Operation", `@!b`}, | 
 | 	{"Operation", `@^b`}, | 
 | 	{"Operation", `@&b`}, | 
 | 	{"Operation", `@<-b`}, | 
 |  | 
 | 	{"Operation", `a @|| b`}, | 
 | 	{"Operation", `a @&& b`}, | 
 | 	{"Operation", `a @== b`}, | 
 | 	{"Operation", `a @+ b`}, | 
 | 	{"Operation", `a @* b`}, | 
 |  | 
 | 	{"CallExpr", `f@()`}, | 
 | 	{"CallExpr", `f@(x, y, z)`}, | 
 | 	{"CallExpr", `obj.f@(1, 2, 3)`}, | 
 | 	{"CallExpr", `func(x int) int { return x + 1 }@(y)`}, | 
 |  | 
 | 	// ListExpr: tested via multi-value const/var declarations | 
 | } | 
 |  | 
 | var types = []test{ | 
 | 	{"Operation", `@*T`}, | 
 | 	{"Operation", `@*struct{}`}, | 
 |  | 
 | 	{"ArrayType", `@[10]T`}, | 
 | 	{"ArrayType", `@[...]T`}, | 
 |  | 
 | 	{"SliceType", `@[]T`}, | 
 | 	{"DotsType", `@...T`}, | 
 | 	{"StructType", `@struct{}`}, | 
 | 	{"InterfaceType", `@interface{}`}, | 
 | 	{"FuncType", `func@()`}, | 
 | 	{"MapType", `@map[T]T`}, | 
 |  | 
 | 	{"ChanType", `@chan T`}, | 
 | 	{"ChanType", `@chan<- T`}, | 
 | 	{"ChanType", `@<-chan T`}, | 
 | } | 
 |  | 
 | var fields = []test{ | 
 | 	{"Field", `@T`}, | 
 | 	{"Field", `@(T)`}, | 
 | 	{"Field", `@x T`}, | 
 | 	{"Field", `@x *(T)`}, | 
 | 	{"Field", `@x, y, z T`}, | 
 | 	{"Field", `@x, y, z (*T)`}, | 
 | } | 
 |  | 
 | var stmts = []test{ | 
 | 	{"EmptyStmt", `@`}, | 
 |  | 
 | 	{"LabeledStmt", `L@:`}, | 
 | 	{"LabeledStmt", `L@: ;`}, | 
 | 	{"LabeledStmt", `L@: f()`}, | 
 |  | 
 | 	{"BlockStmt", `@{}`}, | 
 |  | 
 | 	// The position of an ExprStmt is the position of the expression. | 
 | 	{"ExprStmt", `@<-ch`}, | 
 | 	{"ExprStmt", `f@()`}, | 
 | 	{"ExprStmt", `append@(s, 1, 2, 3)`}, | 
 |  | 
 | 	{"SendStmt", `ch @<- x`}, | 
 |  | 
 | 	{"DeclStmt", `@const x = 0`}, | 
 | 	{"DeclStmt", `@const (x = 0)`}, | 
 | 	{"DeclStmt", `@type T int`}, | 
 | 	{"DeclStmt", `@type T = int`}, | 
 | 	{"DeclStmt", `@type (T1 = int; T2 = float32)`}, | 
 | 	{"DeclStmt", `@var x = 0`}, | 
 | 	{"DeclStmt", `@var x, y, z int`}, | 
 | 	{"DeclStmt", `@var (a, b = 1, 2)`}, | 
 |  | 
 | 	{"AssignStmt", `x @= y`}, | 
 | 	{"AssignStmt", `a, b, x @= 1, 2, 3`}, | 
 | 	{"AssignStmt", `x @+= y`}, | 
 | 	{"AssignStmt", `x @:= y`}, | 
 | 	{"AssignStmt", `x, ok @:= f()`}, | 
 | 	{"AssignStmt", `x@++`}, | 
 | 	{"AssignStmt", `a[i]@--`}, | 
 |  | 
 | 	{"BranchStmt", `@break`}, | 
 | 	{"BranchStmt", `@break L`}, | 
 | 	{"BranchStmt", `@continue`}, | 
 | 	{"BranchStmt", `@continue L`}, | 
 | 	{"BranchStmt", `@fallthrough`}, | 
 | 	{"BranchStmt", `@goto L`}, | 
 |  | 
 | 	{"CallStmt", `@defer f()`}, | 
 | 	{"CallStmt", `@go f()`}, | 
 |  | 
 | 	{"ReturnStmt", `@return`}, | 
 | 	{"ReturnStmt", `@return x`}, | 
 | 	{"ReturnStmt", `@return a, b, a + b*f(1, 2, 3)`}, | 
 |  | 
 | 	{"IfStmt", `@if cond {}`}, | 
 | 	{"IfStmt", `@if cond { f() } else {}`}, | 
 | 	{"IfStmt", `@if cond { f() } else { g(); h() }`}, | 
 | 	{"ForStmt", `@for {}`}, | 
 | 	{"ForStmt", `@for { f() }`}, | 
 | 	{"SwitchStmt", `@switch {}`}, | 
 | 	{"SwitchStmt", `@switch { default: }`}, | 
 | 	{"SwitchStmt", `@switch { default: x++ }`}, | 
 | 	{"SelectStmt", `@select {}`}, | 
 | 	{"SelectStmt", `@select { default: }`}, | 
 | 	{"SelectStmt", `@select { default: ch <- false }`}, | 
 | } | 
 |  | 
 | var ranges = []test{ | 
 | 	{"RangeClause", `@range s`}, | 
 | 	{"RangeClause", `i = @range s`}, | 
 | 	{"RangeClause", `i := @range s`}, | 
 | 	{"RangeClause", `_, x = @range s`}, | 
 | 	{"RangeClause", `i, x = @range s`}, | 
 | 	{"RangeClause", `_, x := @range s.f`}, | 
 | 	{"RangeClause", `i, x := @range f(i)`}, | 
 | } | 
 |  | 
 | var guards = []test{ | 
 | 	{"TypeSwitchGuard", `x@.(type)`}, | 
 | 	{"TypeSwitchGuard", `x := x@.(type)`}, | 
 | } | 
 |  | 
 | var cases = []test{ | 
 | 	{"CaseClause", `@case x:`}, | 
 | 	{"CaseClause", `@case x, y, z:`}, | 
 | 	{"CaseClause", `@case x == 1, y == 2:`}, | 
 | 	{"CaseClause", `@default:`}, | 
 | } | 
 |  | 
 | var comms = []test{ | 
 | 	{"CommClause", `@case <-ch:`}, | 
 | 	{"CommClause", `@case x <- ch:`}, | 
 | 	{"CommClause", `@case x = <-ch:`}, | 
 | 	{"CommClause", `@case x := <-ch:`}, | 
 | 	{"CommClause", `@case x, ok = <-ch: f(1, 2, 3)`}, | 
 | 	{"CommClause", `@case x, ok := <-ch: x++`}, | 
 | 	{"CommClause", `@default:`}, | 
 | 	{"CommClause", `@default: ch <- true`}, | 
 | } | 
 |  | 
 | func TestPos(t *testing.T) { | 
 | 	// TODO(gri) Once we have a general tree walker, we can use that to find | 
 | 	// the first occurrence of the respective node and we don't need to hand- | 
 | 	// extract the node for each specific kind of construct. | 
 |  | 
 | 	testPos(t, decls, "package p; ", "", | 
 | 		func(f *File) Node { return f.DeclList[0] }, | 
 | 	) | 
 |  | 
 | 	// embed expressions in a composite literal so we can test key:value and naked composite literals | 
 | 	testPos(t, exprs, "package p; var _ = T{ ", " }", | 
 | 		func(f *File) Node { return f.DeclList[0].(*VarDecl).Values.(*CompositeLit).ElemList[0] }, | 
 | 	) | 
 |  | 
 | 	// embed types in a function  signature so we can test ... types | 
 | 	testPos(t, types, "package p; func f(", ")", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Type.ParamList[0].Type }, | 
 | 	) | 
 |  | 
 | 	testPos(t, fields, "package p; func f(", ")", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Type.ParamList[0] }, | 
 | 	) | 
 |  | 
 | 	testPos(t, stmts, "package p; func _() { ", "; }", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body.List[0] }, | 
 | 	) | 
 |  | 
 | 	testPos(t, ranges, "package p; func _() { for ", " {} }", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body.List[0].(*ForStmt).Init.(*RangeClause) }, | 
 | 	) | 
 |  | 
 | 	testPos(t, guards, "package p; func _() { switch ", " {} }", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body.List[0].(*SwitchStmt).Tag.(*TypeSwitchGuard) }, | 
 | 	) | 
 |  | 
 | 	testPos(t, cases, "package p; func _() { switch { ", " } }", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body.List[0].(*SwitchStmt).Body[0] }, | 
 | 	) | 
 |  | 
 | 	testPos(t, comms, "package p; func _() { select { ", " } }", | 
 | 		func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body.List[0].(*SelectStmt).Body[0] }, | 
 | 	) | 
 | } | 
 |  | 
 | func testPos(t *testing.T, list []test, prefix, suffix string, extract func(*File) Node) { | 
 | 	for _, test := range list { | 
 | 		// complete source, compute @ position, and strip @ from source | 
 | 		src, index := stripAt(prefix + test.snippet + suffix) | 
 | 		if index < 0 { | 
 | 			t.Errorf("missing @: %s (%s)", src, test.nodetyp) | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// build syntax tree | 
 | 		file, err := Parse(nil, strings.NewReader(src), nil, nil, 0) | 
 | 		if err != nil { | 
 | 			t.Errorf("parse error: %s: %v (%s)", src, err, test.nodetyp) | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// extract desired node | 
 | 		node := extract(file) | 
 | 		if typ := typeOf(node); typ != test.nodetyp { | 
 | 			t.Errorf("type error: %s: type = %s, want %s", src, typ, test.nodetyp) | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// verify node position with expected position as indicated by @ | 
 | 		if pos := int(node.Pos().Col()); pos != index+colbase { | 
 | 			t.Errorf("pos error: %s: pos = %d, want %d (%s)", src, pos, index+colbase, test.nodetyp) | 
 | 			continue | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func stripAt(s string) (string, int) { | 
 | 	if i := strings.Index(s, "@"); i >= 0 { | 
 | 		return s[:i] + s[i+1:], i | 
 | 	} | 
 | 	return s, -1 | 
 | } | 
 |  | 
 | func typeOf(n Node) string { | 
 | 	const prefix = "*syntax." | 
 | 	k := fmt.Sprintf("%T", n) | 
 | 	if strings.HasPrefix(k, prefix) { | 
 | 		return k[len(prefix):] | 
 | 	} | 
 | 	return k | 
 | } |